b3ea3170b3
When hostname is not set, ftpd core dumps, because there is no NULL check for freeing name resolving information for its own hostname. So the check is added. Approved by: jkh
2746 lines
60 KiB
C
2746 lines
60 KiB
C
/*
|
|
* Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the University of
|
|
* California, Berkeley and its contributors.
|
|
* 4. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#if 0
|
|
#ifndef lint
|
|
static char copyright[] =
|
|
"@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994\n\
|
|
The Regents of the University of California. All rights reserved.\n";
|
|
#endif /* not lint */
|
|
#endif
|
|
|
|
#ifndef lint
|
|
#if 0
|
|
static char sccsid[] = "@(#)ftpd.c 8.4 (Berkeley) 4/16/94";
|
|
#endif
|
|
static const char rcsid[] =
|
|
"$FreeBSD$";
|
|
#endif /* not lint */
|
|
|
|
/*
|
|
* FTP server.
|
|
*/
|
|
#include <sys/param.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/mman.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/tcp.h>
|
|
|
|
#define FTP_NAMES
|
|
#include <arpa/ftp.h>
|
|
#include <arpa/inet.h>
|
|
#include <arpa/telnet.h>
|
|
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <glob.h>
|
|
#include <limits.h>
|
|
#include <netdb.h>
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
#include <setjmp.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <libutil.h>
|
|
#ifdef LOGIN_CAP
|
|
#include <login_cap.h>
|
|
#endif
|
|
|
|
#ifdef SKEY
|
|
#include <skey.h>
|
|
#endif
|
|
|
|
#if !defined(NOPAM)
|
|
#include <security/pam_appl.h>
|
|
#endif
|
|
|
|
#include "pathnames.h"
|
|
#include "extern.h"
|
|
|
|
#if __STDC__
|
|
#include <stdarg.h>
|
|
#else
|
|
#include <varargs.h>
|
|
#endif
|
|
|
|
static char version[] = "Version 6.00LS";
|
|
#undef main
|
|
|
|
/* wrapper for KAME-special getnameinfo() */
|
|
#ifndef NI_WITHSCOPEID
|
|
#define NI_WITHSCOPEID 0
|
|
#endif
|
|
|
|
extern off_t restart_point;
|
|
extern char cbuf[];
|
|
|
|
union sockunion server_addr;
|
|
union sockunion ctrl_addr;
|
|
union sockunion data_source;
|
|
union sockunion data_dest;
|
|
union sockunion his_addr;
|
|
union sockunion pasv_addr;
|
|
|
|
int daemon_mode;
|
|
int data;
|
|
jmp_buf errcatch, urgcatch;
|
|
int logged_in;
|
|
struct passwd *pw;
|
|
int debug;
|
|
int timeout = 900; /* timeout after 15 minutes of inactivity */
|
|
int maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */
|
|
int logging;
|
|
int restricted_data_ports = 1;
|
|
int paranoid = 1; /* be extra careful about security */
|
|
int anon_only = 0; /* Only anonymous ftp allowed */
|
|
int guest;
|
|
int dochroot;
|
|
int stats;
|
|
int statfd = -1;
|
|
int type;
|
|
int form;
|
|
int stru; /* avoid C keyword */
|
|
int mode;
|
|
int usedefault = 1; /* for data transfers */
|
|
int pdata = -1; /* for passive mode */
|
|
sig_atomic_t transflag;
|
|
off_t file_size;
|
|
off_t byte_count;
|
|
#if !defined(CMASK) || CMASK == 0
|
|
#undef CMASK
|
|
#define CMASK 027
|
|
#endif
|
|
int defumask = CMASK; /* default umask value */
|
|
char tmpline[7];
|
|
char *hostname;
|
|
#ifdef VIRTUAL_HOSTING
|
|
char *ftpuser;
|
|
|
|
int epsvall = 0;
|
|
|
|
static struct ftphost {
|
|
struct ftphost *next;
|
|
union sockunion hostaddr;
|
|
char *hostname;
|
|
char *anonuser;
|
|
char *statfile;
|
|
char *welcome;
|
|
char *loginmsg;
|
|
} *thishost, *firsthost;
|
|
|
|
#endif
|
|
char remotehost[MAXHOSTNAMELEN];
|
|
char *ident = NULL;
|
|
|
|
static char ttyline[20];
|
|
char *tty = ttyline; /* for klogin */
|
|
|
|
#if !defined(NOPAM)
|
|
static int auth_pam __P((struct passwd**, const char*));
|
|
#endif
|
|
|
|
char *pid_file = NULL;
|
|
|
|
/*
|
|
* Timeout intervals for retrying connections
|
|
* to hosts that don't accept PORT cmds. This
|
|
* is a kludge, but given the problems with TCP...
|
|
*/
|
|
#define SWAITMAX 90 /* wait at most 90 seconds */
|
|
#define SWAITINT 5 /* interval between retries */
|
|
|
|
int swaitmax = SWAITMAX;
|
|
int swaitint = SWAITINT;
|
|
|
|
#ifdef SETPROCTITLE
|
|
#ifdef OLD_SETPROCTITLE
|
|
char **Argv = NULL; /* pointer to argument vector */
|
|
char *LastArgv = NULL; /* end of argv */
|
|
#endif /* OLD_SETPROCTITLE */
|
|
char proctitle[LINE_MAX]; /* initial part of title */
|
|
#endif /* SETPROCTITLE */
|
|
|
|
#ifdef SKEY
|
|
int pwok = 0;
|
|
char addr_string[INET6_ADDRSTRLEN]; /* XXX */
|
|
#endif
|
|
|
|
#define LOGCMD(cmd, file) \
|
|
if (logging > 1) \
|
|
syslog(LOG_INFO,"%s %s%s", cmd, \
|
|
*(file) == '/' ? "" : curdir(), file);
|
|
#define LOGCMD2(cmd, file1, file2) \
|
|
if (logging > 1) \
|
|
syslog(LOG_INFO,"%s %s%s %s%s", cmd, \
|
|
*(file1) == '/' ? "" : curdir(), file1, \
|
|
*(file2) == '/' ? "" : curdir(), file2);
|
|
#define LOGBYTES(cmd, file, cnt) \
|
|
if (logging > 1) { \
|
|
if (cnt == (off_t)-1) \
|
|
syslog(LOG_INFO,"%s %s%s", cmd, \
|
|
*(file) == '/' ? "" : curdir(), file); \
|
|
else \
|
|
syslog(LOG_INFO, "%s %s%s = %qd bytes", \
|
|
cmd, (*(file) == '/') ? "" : curdir(), file, cnt); \
|
|
}
|
|
|
|
#ifdef VIRTUAL_HOSTING
|
|
static void inithosts __P((void));
|
|
static void selecthost __P((union sockunion *));
|
|
#endif
|
|
static void ack __P((char *));
|
|
static void myoob __P((int));
|
|
static int checkuser __P((char *, char *, int));
|
|
static FILE *dataconn __P((char *, off_t, char *));
|
|
static void dolog __P((struct sockaddr *));
|
|
static char *curdir __P((void));
|
|
static void end_login __P((void));
|
|
static FILE *getdatasock __P((char *));
|
|
static char *gunique __P((char *));
|
|
static void lostconn __P((int));
|
|
static int receive_data __P((FILE *, FILE *));
|
|
static void send_data __P((FILE *, FILE *, off_t, off_t, int));
|
|
static struct passwd *
|
|
sgetpwnam __P((char *));
|
|
static char *sgetsave __P((char *));
|
|
static void reapchild __P((int));
|
|
static void logxfer __P((char *, long, long));
|
|
|
|
static char *
|
|
curdir()
|
|
{
|
|
static char path[MAXPATHLEN+1+1]; /* path + '/' + '\0' */
|
|
|
|
if (getcwd(path, sizeof(path)-2) == NULL)
|
|
return ("");
|
|
if (path[1] != '\0') /* special case for root dir. */
|
|
strcat(path, "/");
|
|
/* For guest account, skip / since it's chrooted */
|
|
return (guest ? path+1 : path);
|
|
}
|
|
|
|
int
|
|
main(argc, argv, envp)
|
|
int argc;
|
|
char *argv[];
|
|
char **envp;
|
|
{
|
|
int addrlen, ch, on = 1, tos;
|
|
char *cp, line[LINE_MAX];
|
|
FILE *fd;
|
|
int error;
|
|
char *bindname = NULL;
|
|
int family = AF_UNSPEC;
|
|
int enable_v4 = 0;
|
|
|
|
tzset(); /* in case no timezone database in ~ftp */
|
|
|
|
#ifdef OLD_SETPROCTITLE
|
|
/*
|
|
* Save start and extent of argv for setproctitle.
|
|
*/
|
|
Argv = argv;
|
|
while (*envp)
|
|
envp++;
|
|
LastArgv = envp[-1] + strlen(envp[-1]);
|
|
#endif /* OLD_SETPROCTITLE */
|
|
|
|
|
|
while ((ch = getopt(argc, argv, "AdlDSURt:T:u:va:p:46")) != -1) {
|
|
switch (ch) {
|
|
case 'D':
|
|
daemon_mode++;
|
|
break;
|
|
|
|
case 'd':
|
|
debug++;
|
|
break;
|
|
|
|
case 'l':
|
|
logging++; /* > 1 == extra logging */
|
|
break;
|
|
|
|
case 'R':
|
|
paranoid = 0;
|
|
break;
|
|
|
|
case 'S':
|
|
stats++;
|
|
break;
|
|
|
|
case 'T':
|
|
maxtimeout = atoi(optarg);
|
|
if (timeout > maxtimeout)
|
|
timeout = maxtimeout;
|
|
break;
|
|
|
|
case 't':
|
|
timeout = atoi(optarg);
|
|
if (maxtimeout < timeout)
|
|
maxtimeout = timeout;
|
|
break;
|
|
|
|
case 'U':
|
|
restricted_data_ports = 0;
|
|
break;
|
|
|
|
case 'a':
|
|
bindname = optarg;
|
|
break;
|
|
|
|
case 'p':
|
|
pid_file = optarg;
|
|
break;
|
|
|
|
case 'u':
|
|
{
|
|
long val = 0;
|
|
|
|
val = strtol(optarg, &optarg, 8);
|
|
if (*optarg != '\0' || val < 0)
|
|
warnx("bad value for -u");
|
|
else
|
|
defumask = val;
|
|
break;
|
|
}
|
|
case 'A':
|
|
anon_only = 1;
|
|
break;
|
|
|
|
case 'v':
|
|
debug = 1;
|
|
break;
|
|
|
|
case '4':
|
|
enable_v4 = 1;
|
|
if (family == AF_UNSPEC)
|
|
family = AF_INET;
|
|
break;
|
|
|
|
case '6':
|
|
family = AF_INET6;
|
|
break;
|
|
|
|
default:
|
|
warnx("unknown flag -%c ignored", optopt);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef VIRTUAL_HOSTING
|
|
inithosts();
|
|
#endif
|
|
(void) freopen(_PATH_DEVNULL, "w", stderr);
|
|
|
|
/*
|
|
* LOG_NDELAY sets up the logging connection immediately,
|
|
* necessary for anonymous ftp's that chroot and can't do it later.
|
|
*/
|
|
openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
|
|
|
|
if (daemon_mode) {
|
|
int ctl_sock, fd;
|
|
struct addrinfo hints, *res;
|
|
|
|
/*
|
|
* Detach from parent.
|
|
*/
|
|
if (daemon(1, 1) < 0) {
|
|
syslog(LOG_ERR, "failed to become a daemon");
|
|
exit(1);
|
|
}
|
|
(void) signal(SIGCHLD, reapchild);
|
|
/* init bind_sa */
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
hints.ai_family = family == AF_UNSPEC ? AF_INET : family;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = 0;
|
|
hints.ai_flags = AI_PASSIVE;
|
|
error = getaddrinfo(bindname, "ftp", &hints, &res);
|
|
if (error) {
|
|
if (family == AF_UNSPEC) {
|
|
hints.ai_family = AF_UNSPEC;
|
|
error = getaddrinfo(bindname, "ftp", &hints,
|
|
&res);
|
|
}
|
|
if (error == 0 && res->ai_addr != NULL)
|
|
family = res->ai_addr->sa_family;
|
|
}
|
|
if (error) {
|
|
syslog(LOG_ERR, gai_strerror(error));
|
|
if (error == EAI_SYSTEM)
|
|
syslog(LOG_ERR, strerror(errno));
|
|
exit(1);
|
|
}
|
|
if (res->ai_addr == NULL) {
|
|
syslog(LOG_ERR, "-a %s: getaddrinfo failed", hostname);
|
|
exit(1);
|
|
}
|
|
/*
|
|
* Open a socket, bind it to the FTP port, and start
|
|
* listening.
|
|
*/
|
|
ctl_sock = socket(family, SOCK_STREAM, 0);
|
|
if (ctl_sock < 0) {
|
|
syslog(LOG_ERR, "control socket: %m");
|
|
exit(1);
|
|
}
|
|
if (setsockopt(ctl_sock, SOL_SOCKET, SO_REUSEADDR,
|
|
(char *)&on, sizeof(on)) < 0)
|
|
syslog(LOG_ERR, "control setsockopt: %m");
|
|
#ifdef IPV6_BINDV6ONLY
|
|
if (family == AF_INET6 && enable_v4 == 0) {
|
|
if (setsockopt(ctl_sock, IPPROTO_IPV6, IPV6_BINDV6ONLY,
|
|
(char *)&on, sizeof (on)) < 0)
|
|
syslog(LOG_ERR,
|
|
"control setsockopt(IPV6_BINDV6ONLY): %m");
|
|
}
|
|
#endif /* IPV6_BINDV6ONLY */
|
|
memcpy(&server_addr, res->ai_addr, res->ai_addr->sa_len);
|
|
if (bind(ctl_sock, (struct sockaddr *)&server_addr,
|
|
server_addr.su_len) < 0) {
|
|
syslog(LOG_ERR, "control bind: %m");
|
|
exit(1);
|
|
}
|
|
if (listen(ctl_sock, 32) < 0) {
|
|
syslog(LOG_ERR, "control listen: %m");
|
|
exit(1);
|
|
}
|
|
/*
|
|
* Atomically write process ID
|
|
*/
|
|
if (pid_file)
|
|
{
|
|
int fd;
|
|
char buf[20];
|
|
|
|
fd = open(pid_file, O_CREAT | O_WRONLY | O_TRUNC
|
|
| O_NONBLOCK | O_EXLOCK, 0644);
|
|
if (fd < 0) {
|
|
if (errno == EAGAIN)
|
|
errx(1, "%s: file locked", pid_file);
|
|
else
|
|
err(1, "%s", pid_file);
|
|
}
|
|
snprintf(buf, sizeof(buf),
|
|
"%lu\n", (unsigned long) getpid());
|
|
if (write(fd, buf, strlen(buf)) < 0)
|
|
err(1, "%s: write", pid_file);
|
|
/* Leave the pid file open and locked */
|
|
}
|
|
/*
|
|
* Loop forever accepting connection requests and forking off
|
|
* children to handle them.
|
|
*/
|
|
while (1) {
|
|
addrlen = server_addr.su_len;
|
|
fd = accept(ctl_sock, (struct sockaddr *)&his_addr, &addrlen);
|
|
if (fork() == 0) {
|
|
/* child */
|
|
(void) dup2(fd, 0);
|
|
(void) dup2(fd, 1);
|
|
close(ctl_sock);
|
|
break;
|
|
}
|
|
close(fd);
|
|
}
|
|
} else {
|
|
addrlen = sizeof(his_addr);
|
|
if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) {
|
|
syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
(void) signal(SIGCHLD, SIG_IGN);
|
|
(void) signal(SIGPIPE, lostconn);
|
|
if (signal(SIGURG, myoob) == SIG_ERR)
|
|
syslog(LOG_ERR, "signal: %m");
|
|
|
|
#ifdef SKEY
|
|
getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
|
|
addr_string, sizeof(addr_string) - 1, NULL, 0,
|
|
NI_NUMERICHOST|NI_WITHSCOPEID);
|
|
#endif
|
|
addrlen = sizeof(ctrl_addr);
|
|
if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
|
|
syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
|
|
exit(1);
|
|
}
|
|
#ifdef VIRTUAL_HOSTING
|
|
/* select our identity from virtual host table */
|
|
selecthost(&ctrl_addr);
|
|
#endif
|
|
#ifdef IP_TOS
|
|
if (ctrl_addr.su_family == AF_INET)
|
|
{
|
|
tos = IPTOS_LOWDELAY;
|
|
if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)) < 0)
|
|
syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
|
|
}
|
|
#endif
|
|
/*
|
|
* Disable Nagle on the control channel so that we don't have to wait
|
|
* for peer's ACK before issuing our next reply.
|
|
*/
|
|
if (setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0)
|
|
syslog(LOG_WARNING, "control setsockopt TCP_NODELAY: %m");
|
|
|
|
data_source.su_port = htons(ntohs(ctrl_addr.su_port) - 1);
|
|
|
|
/* set this here so klogin can use it... */
|
|
(void)snprintf(ttyline, sizeof(ttyline), "ftp%d", getpid());
|
|
|
|
/* Try to handle urgent data inline */
|
|
#ifdef SO_OOBINLINE
|
|
if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0)
|
|
syslog(LOG_ERR, "setsockopt: %m");
|
|
#endif
|
|
|
|
#ifdef F_SETOWN
|
|
if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
|
|
syslog(LOG_ERR, "fcntl F_SETOWN: %m");
|
|
#endif
|
|
dolog((struct sockaddr *)&his_addr);
|
|
/*
|
|
* Set up default state
|
|
*/
|
|
data = -1;
|
|
type = TYPE_A;
|
|
form = FORM_N;
|
|
stru = STRU_F;
|
|
mode = MODE_S;
|
|
tmpline[0] = '\0';
|
|
|
|
/* If logins are disabled, print out the message. */
|
|
if ((fd = fopen(_PATH_NOLOGIN,"r")) != NULL) {
|
|
while (fgets(line, sizeof(line), fd) != NULL) {
|
|
if ((cp = strchr(line, '\n')) != NULL)
|
|
*cp = '\0';
|
|
lreply(530, "%s", line);
|
|
}
|
|
(void) fflush(stdout);
|
|
(void) fclose(fd);
|
|
reply(530, "System not available.");
|
|
exit(0);
|
|
}
|
|
#ifdef VIRTUAL_HOSTING
|
|
if ((fd = fopen(thishost->welcome, "r")) != NULL) {
|
|
#else
|
|
if ((fd = fopen(_PATH_FTPWELCOME, "r")) != NULL) {
|
|
#endif
|
|
while (fgets(line, sizeof(line), fd) != NULL) {
|
|
if ((cp = strchr(line, '\n')) != NULL)
|
|
*cp = '\0';
|
|
lreply(220, "%s", line);
|
|
}
|
|
(void) fflush(stdout);
|
|
(void) fclose(fd);
|
|
/* reply(220,) must follow */
|
|
}
|
|
#ifndef VIRTUAL_HOSTING
|
|
if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL)
|
|
fatal("Ran out of memory.");
|
|
(void) gethostname(hostname, MAXHOSTNAMELEN - 1);
|
|
hostname[MAXHOSTNAMELEN - 1] = '\0';
|
|
#endif
|
|
reply(220, "%s FTP server (%s) ready.", hostname, version);
|
|
(void) setjmp(errcatch);
|
|
for (;;)
|
|
(void) yyparse();
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
static void
|
|
lostconn(signo)
|
|
int signo;
|
|
{
|
|
|
|
if (debug)
|
|
syslog(LOG_DEBUG, "lost connection");
|
|
dologout(1);
|
|
}
|
|
|
|
#ifdef VIRTUAL_HOSTING
|
|
/*
|
|
* read in virtual host tables (if they exist)
|
|
*/
|
|
|
|
static void
|
|
inithosts()
|
|
{
|
|
FILE *fp;
|
|
char *cp;
|
|
struct ftphost *hrp, *lhrp;
|
|
char line[1024];
|
|
struct addrinfo hints, *res, *ai;
|
|
|
|
/*
|
|
* Fill in the default host information
|
|
*/
|
|
if (gethostname(line, sizeof(line)) < 0)
|
|
line[0] = '\0';
|
|
if ((hrp = malloc(sizeof(struct ftphost))) == NULL ||
|
|
(hrp->hostname = strdup(line)) == NULL)
|
|
fatal("Ran out of memory.");
|
|
memset(&hrp->hostaddr, 0, sizeof(hrp->hostaddr));
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_flags = AI_CANONNAME;
|
|
hints.ai_family = AF_UNSPEC;
|
|
getaddrinfo(hrp->hostname, NULL, &hints, &res);
|
|
if (res)
|
|
memcpy(&hrp->hostaddr, res->ai_addr, res->ai_addrlen);
|
|
hrp->statfile = _PATH_FTPDSTATFILE;
|
|
hrp->welcome = _PATH_FTPWELCOME;
|
|
hrp->loginmsg = _PATH_FTPLOGINMESG;
|
|
hrp->anonuser = "ftp";
|
|
hrp->next = NULL;
|
|
thishost = firsthost = lhrp = hrp;
|
|
if (res)
|
|
freeaddrinfo(res);
|
|
if ((fp = fopen(_PATH_FTPHOSTS, "r")) != NULL) {
|
|
int addrsize, error;
|
|
void *addr;
|
|
struct hostent *hp;
|
|
|
|
while (fgets(line, sizeof(line), fp) != NULL) {
|
|
int i, hp_error;
|
|
|
|
if ((cp = strchr(line, '\n')) == NULL) {
|
|
/* ignore long lines */
|
|
while (fgets(line, sizeof(line), fp) != NULL &&
|
|
strchr(line, '\n') == NULL)
|
|
;
|
|
continue;
|
|
}
|
|
*cp = '\0';
|
|
cp = strtok(line, " \t");
|
|
/* skip comments and empty lines */
|
|
if (cp == NULL || line[0] == '#')
|
|
continue;
|
|
|
|
hints.ai_flags = 0;
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_flags = AI_PASSIVE;
|
|
error = getaddrinfo(cp, NULL, &hints, &res);
|
|
if (error != NULL)
|
|
continue;
|
|
for (ai = res; ai != NULL && ai->ai_addr != NULL;
|
|
ai = ai->ai_next)
|
|
{
|
|
|
|
for (hrp = firsthost; hrp != NULL; hrp = hrp->next) {
|
|
if (memcmp(&hrp->hostaddr,
|
|
ai->ai_addr,
|
|
ai->ai_addr->sa_len) == 0)
|
|
break;
|
|
}
|
|
if (hrp == NULL) {
|
|
if ((hrp = malloc(sizeof(struct ftphost))) == NULL)
|
|
continue;
|
|
/* defaults */
|
|
hrp->statfile = _PATH_FTPDSTATFILE;
|
|
hrp->welcome = _PATH_FTPWELCOME;
|
|
hrp->loginmsg = _PATH_FTPLOGINMESG;
|
|
hrp->anonuser = "ftp";
|
|
hrp->next = NULL;
|
|
lhrp->next = hrp;
|
|
lhrp = hrp;
|
|
}
|
|
(void) memcpy(&hrp->hostaddr,
|
|
ai->ai_addr,
|
|
ai->ai_addr->sa_len);
|
|
/*
|
|
* determine hostname to use.
|
|
* force defined name if there is a valid alias
|
|
* otherwise fallback to primary hostname
|
|
*/
|
|
/* XXX: getaddrinfo() can't do alias check */
|
|
switch(hrp->hostaddr.su_family) {
|
|
case AF_INET:
|
|
addr = &((struct sockaddr_in *)&hrp->hostaddr)->sin_addr;
|
|
addrsize = sizeof(struct sockaddr_in);
|
|
break;
|
|
case AF_INET6:
|
|
addr = &((struct sockaddr_in6 *)&hrp->hostaddr)->sin6_addr;
|
|
addrsize = sizeof(struct sockaddr_in6);
|
|
break;
|
|
default:
|
|
/* should not reach here */
|
|
free(hrp);
|
|
continue;
|
|
/* NOTREACHED */
|
|
}
|
|
if ((hp = getipnodebyaddr((char*)addr, addrsize,
|
|
hrp->hostaddr.su_family,
|
|
&hp_error)) != NULL) {
|
|
if (strcmp(cp, hp->h_name) != 0) {
|
|
if (hp->h_aliases == NULL)
|
|
cp = hp->h_name;
|
|
else {
|
|
i = 0;
|
|
while (hp->h_aliases[i] &&
|
|
strcmp(cp, hp->h_aliases[i]) != 0)
|
|
++i;
|
|
if (hp->h_aliases[i] == NULL)
|
|
cp = hp->h_name;
|
|
}
|
|
}
|
|
}
|
|
hrp->hostname = strdup(cp);
|
|
freehostent(hp);
|
|
/* ok, now we now peel off the rest */
|
|
i = 0;
|
|
while (i < 4 && (cp = strtok(NULL, " \t")) != NULL) {
|
|
if (*cp != '-' && (cp = strdup(cp)) != NULL) {
|
|
switch (i) {
|
|
case 0: /* anon user permissions */
|
|
hrp->anonuser = cp;
|
|
break;
|
|
case 1: /* statistics file */
|
|
hrp->statfile = cp;
|
|
break;
|
|
case 2: /* welcome message */
|
|
hrp->welcome = cp;
|
|
break;
|
|
case 3: /* login message */
|
|
hrp->loginmsg = cp;
|
|
break;
|
|
}
|
|
}
|
|
++i;
|
|
}
|
|
/* XXX: re-initialization for getaddrinfo() loop */
|
|
cp = strtok(line, " \t");
|
|
}
|
|
}
|
|
(void) fclose(fp);
|
|
}
|
|
}
|
|
|
|
static void
|
|
selecthost(su)
|
|
union sockunion *su;
|
|
{
|
|
struct ftphost *hrp;
|
|
u_int16_t port;
|
|
#ifdef INET6
|
|
struct in6_addr *mapped_in6 = NULL;
|
|
#endif
|
|
|
|
#ifdef INET6
|
|
/*
|
|
* XXX IPv4 mapped IPv6 addr consideraton,
|
|
* specified in rfc2373.
|
|
*/
|
|
if (su->su_family == AF_INET6 &&
|
|
IN6_IS_ADDR_V4MAPPED(&su->su_sin6.sin6_addr))
|
|
mapped_in6 = &su->su_sin6.sin6_addr;
|
|
#endif
|
|
|
|
hrp = thishost = firsthost; /* default */
|
|
port = su->su_port;
|
|
su->su_port = 0;
|
|
while (hrp != NULL) {
|
|
if (memcmp(su, &hrp->hostaddr, sizeof(hrp->hostaddr)) == 0) {
|
|
thishost = hrp;
|
|
break;
|
|
}
|
|
#ifdef INET6
|
|
/* XXX IPv4 mapped IPv6 addr consideraton */
|
|
if (hrp->hostaddr.su_family == AF_INET && mapped_in6 != NULL &&
|
|
(memcmp(&mapped_in6->s6_addr[12],
|
|
&hrp->hostaddr.su_sin.sin_addr,
|
|
sizeof(struct in_addr)) == 0)) {
|
|
thishost = hrp;
|
|
break;
|
|
}
|
|
#endif
|
|
hrp = hrp->next;
|
|
}
|
|
su->su_port = port;
|
|
/* setup static variables as appropriate */
|
|
hostname = thishost->hostname;
|
|
ftpuser = thishost->anonuser;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Helper function for sgetpwnam().
|
|
*/
|
|
static char *
|
|
sgetsave(s)
|
|
char *s;
|
|
{
|
|
char *new = malloc((unsigned) strlen(s) + 1);
|
|
|
|
if (new == NULL) {
|
|
perror_reply(421, "Local resource failure: malloc");
|
|
dologout(1);
|
|
/* NOTREACHED */
|
|
}
|
|
(void) strcpy(new, s);
|
|
return (new);
|
|
}
|
|
|
|
/*
|
|
* Save the result of a getpwnam. Used for USER command, since
|
|
* the data returned must not be clobbered by any other command
|
|
* (e.g., globbing).
|
|
*/
|
|
static struct passwd *
|
|
sgetpwnam(name)
|
|
char *name;
|
|
{
|
|
static struct passwd save;
|
|
struct passwd *p;
|
|
|
|
if ((p = getpwnam(name)) == NULL)
|
|
return (p);
|
|
if (save.pw_name) {
|
|
free(save.pw_name);
|
|
free(save.pw_passwd);
|
|
free(save.pw_gecos);
|
|
free(save.pw_dir);
|
|
free(save.pw_shell);
|
|
}
|
|
save = *p;
|
|
save.pw_name = sgetsave(p->pw_name);
|
|
save.pw_passwd = sgetsave(p->pw_passwd);
|
|
save.pw_gecos = sgetsave(p->pw_gecos);
|
|
save.pw_dir = sgetsave(p->pw_dir);
|
|
save.pw_shell = sgetsave(p->pw_shell);
|
|
return (&save);
|
|
}
|
|
|
|
static int login_attempts; /* number of failed login attempts */
|
|
static int askpasswd; /* had user command, ask for passwd */
|
|
static char curname[10]; /* current USER name */
|
|
|
|
/*
|
|
* USER command.
|
|
* Sets global passwd pointer pw if named account exists and is acceptable;
|
|
* sets askpasswd if a PASS command is expected. If logged in previously,
|
|
* need to reset state. If name is "ftp" or "anonymous", the name is not in
|
|
* _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return.
|
|
* If account doesn't exist, ask for passwd anyway. Otherwise, check user
|
|
* requesting login privileges. Disallow anyone who does not have a standard
|
|
* shell as returned by getusershell(). Disallow anyone mentioned in the file
|
|
* _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
|
|
*/
|
|
void
|
|
user(name)
|
|
char *name;
|
|
{
|
|
char *cp, *shell;
|
|
|
|
if (logged_in) {
|
|
if (guest) {
|
|
reply(530, "Can't change user from guest login.");
|
|
return;
|
|
} else if (dochroot) {
|
|
reply(530, "Can't change user from chroot user.");
|
|
return;
|
|
}
|
|
end_login();
|
|
}
|
|
|
|
guest = 0;
|
|
if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
|
|
if (checkuser(_PATH_FTPUSERS, "ftp", 0) ||
|
|
checkuser(_PATH_FTPUSERS, "anonymous", 0))
|
|
reply(530, "User %s access denied.", name);
|
|
#ifdef VIRTUAL_HOSTING
|
|
else if ((pw = sgetpwnam(thishost->anonuser)) != NULL) {
|
|
#else
|
|
else if ((pw = sgetpwnam("ftp")) != NULL) {
|
|
#endif
|
|
guest = 1;
|
|
askpasswd = 1;
|
|
reply(331,
|
|
"Guest login ok, send your email address as password.");
|
|
} else
|
|
reply(530, "User %s unknown.", name);
|
|
if (!askpasswd && logging)
|
|
syslog(LOG_NOTICE,
|
|
"ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost);
|
|
return;
|
|
}
|
|
if (anon_only != 0) {
|
|
reply(530, "Sorry, only anonymous ftp allowed.");
|
|
return;
|
|
}
|
|
|
|
if ((pw = sgetpwnam(name))) {
|
|
if ((shell = pw->pw_shell) == NULL || *shell == 0)
|
|
shell = _PATH_BSHELL;
|
|
while ((cp = getusershell()) != NULL)
|
|
if (strcmp(cp, shell) == 0)
|
|
break;
|
|
endusershell();
|
|
|
|
if (cp == NULL || checkuser(_PATH_FTPUSERS, name, 1)) {
|
|
reply(530, "User %s access denied.", name);
|
|
if (logging)
|
|
syslog(LOG_NOTICE,
|
|
"FTP LOGIN REFUSED FROM %s, %s",
|
|
remotehost, name);
|
|
pw = (struct passwd *) NULL;
|
|
return;
|
|
}
|
|
}
|
|
if (logging)
|
|
strncpy(curname, name, sizeof(curname)-1);
|
|
#ifdef SKEY
|
|
pwok = skeyaccess(name, NULL, remotehost, addr_string);
|
|
reply(331, "%s", skey_challenge(name, pw, pwok));
|
|
#else
|
|
reply(331, "Password required for %s.", name);
|
|
#endif
|
|
askpasswd = 1;
|
|
/*
|
|
* Delay before reading passwd after first failed
|
|
* attempt to slow down passwd-guessing programs.
|
|
*/
|
|
if (login_attempts)
|
|
sleep((unsigned) login_attempts);
|
|
}
|
|
|
|
/*
|
|
* Check if a user is in the file "fname"
|
|
*/
|
|
static int
|
|
checkuser(fname, name, pwset)
|
|
char *fname;
|
|
char *name;
|
|
int pwset;
|
|
{
|
|
FILE *fd;
|
|
int found = 0;
|
|
char *p, line[BUFSIZ];
|
|
|
|
if ((fd = fopen(fname, "r")) != NULL) {
|
|
while (!found && fgets(line, sizeof(line), fd) != NULL)
|
|
if ((p = strchr(line, '\n')) != NULL) {
|
|
*p = '\0';
|
|
if (line[0] == '#')
|
|
continue;
|
|
/*
|
|
* if first chr is '@', check group membership
|
|
*/
|
|
if (line[0] == '@') {
|
|
int i = 0;
|
|
struct group *grp;
|
|
|
|
if ((grp = getgrnam(line+1)) == NULL)
|
|
continue;
|
|
/*
|
|
* Check user's default group
|
|
*/
|
|
if (pwset && grp->gr_gid == pw->pw_gid)
|
|
found = 1;
|
|
/*
|
|
* Check supplementary groups
|
|
*/
|
|
while (!found && grp->gr_mem[i])
|
|
found = strcmp(name,
|
|
grp->gr_mem[i++])
|
|
== 0;
|
|
}
|
|
/*
|
|
* Otherwise, just check for username match
|
|
*/
|
|
else
|
|
found = strcmp(line, name) == 0;
|
|
}
|
|
(void) fclose(fd);
|
|
}
|
|
return (found);
|
|
}
|
|
|
|
/*
|
|
* Terminate login as previous user, if any, resetting state;
|
|
* used when USER command is given or login fails.
|
|
*/
|
|
static void
|
|
end_login()
|
|
{
|
|
|
|
(void) seteuid((uid_t)0);
|
|
if (logged_in)
|
|
ftpd_logwtmp(ttyline, "", "");
|
|
pw = NULL;
|
|
#ifdef LOGIN_CAP
|
|
setusercontext(NULL, getpwuid(0), (uid_t)0,
|
|
LOGIN_SETPRIORITY|LOGIN_SETRESOURCES|LOGIN_SETUMASK);
|
|
#endif
|
|
logged_in = 0;
|
|
guest = 0;
|
|
dochroot = 0;
|
|
}
|
|
|
|
#if !defined(NOPAM)
|
|
|
|
/*
|
|
* the following code is stolen from imap-uw PAM authentication module and
|
|
* login.c
|
|
*/
|
|
#define COPY_STRING(s) (s ? strdup(s) : NULL)
|
|
|
|
struct cred_t {
|
|
const char *uname; /* user name */
|
|
const char *pass; /* password */
|
|
};
|
|
typedef struct cred_t cred_t;
|
|
|
|
static int
|
|
auth_conv(int num_msg, const struct pam_message **msg,
|
|
struct pam_response **resp, void *appdata)
|
|
{
|
|
int i;
|
|
cred_t *cred = (cred_t *) appdata;
|
|
struct pam_response *reply =
|
|
malloc(sizeof(struct pam_response) * num_msg);
|
|
|
|
for (i = 0; i < num_msg; i++) {
|
|
switch (msg[i]->msg_style) {
|
|
case PAM_PROMPT_ECHO_ON: /* assume want user name */
|
|
reply[i].resp_retcode = PAM_SUCCESS;
|
|
reply[i].resp = COPY_STRING(cred->uname);
|
|
/* PAM frees resp. */
|
|
break;
|
|
case PAM_PROMPT_ECHO_OFF: /* assume want password */
|
|
reply[i].resp_retcode = PAM_SUCCESS;
|
|
reply[i].resp = COPY_STRING(cred->pass);
|
|
/* PAM frees resp. */
|
|
break;
|
|
case PAM_TEXT_INFO:
|
|
case PAM_ERROR_MSG:
|
|
reply[i].resp_retcode = PAM_SUCCESS;
|
|
reply[i].resp = NULL;
|
|
break;
|
|
default: /* unknown message style */
|
|
free(reply);
|
|
return PAM_CONV_ERR;
|
|
}
|
|
}
|
|
|
|
*resp = reply;
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Attempt to authenticate the user using PAM. Returns 0 if the user is
|
|
* authenticated, or 1 if not authenticated. If some sort of PAM system
|
|
* error occurs (e.g., the "/etc/pam.conf" file is missing) then this
|
|
* function returns -1. This can be used as an indication that we should
|
|
* fall back to a different authentication mechanism.
|
|
*/
|
|
static int
|
|
auth_pam(struct passwd **ppw, const char *pass)
|
|
{
|
|
pam_handle_t *pamh = NULL;
|
|
const char *tmpl_user;
|
|
const void *item;
|
|
int rval;
|
|
int e;
|
|
cred_t auth_cred = { (*ppw)->pw_name, pass };
|
|
struct pam_conv conv = { &auth_conv, &auth_cred };
|
|
|
|
e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh);
|
|
if (e != PAM_SUCCESS) {
|
|
syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, e));
|
|
return -1;
|
|
}
|
|
|
|
e = pam_authenticate(pamh, 0);
|
|
switch (e) {
|
|
case PAM_SUCCESS:
|
|
/*
|
|
* With PAM we support the concept of a "template"
|
|
* user. The user enters a login name which is
|
|
* authenticated by PAM, usually via a remote service
|
|
* such as RADIUS or TACACS+. If authentication
|
|
* succeeds, a different but related "template" name
|
|
* is used for setting the credentials, shell, and
|
|
* home directory. The name the user enters need only
|
|
* exist on the remote authentication server, but the
|
|
* template name must be present in the local password
|
|
* database.
|
|
*
|
|
* This is supported by two various mechanisms in the
|
|
* individual modules. However, from the application's
|
|
* point of view, the template user is always passed
|
|
* back as a changed value of the PAM_USER item.
|
|
*/
|
|
if ((e = pam_get_item(pamh, PAM_USER, &item)) ==
|
|
PAM_SUCCESS) {
|
|
tmpl_user = (const char *) item;
|
|
if (strcmp((*ppw)->pw_name, tmpl_user) != 0)
|
|
*ppw = getpwnam(tmpl_user);
|
|
} else
|
|
syslog(LOG_ERR, "Couldn't get PAM_USER: %s",
|
|
pam_strerror(pamh, e));
|
|
rval = 0;
|
|
break;
|
|
|
|
case PAM_AUTH_ERR:
|
|
case PAM_USER_UNKNOWN:
|
|
case PAM_MAXTRIES:
|
|
rval = 1;
|
|
break;
|
|
|
|
default:
|
|
syslog(LOG_ERR, "auth_pam: %s", pam_strerror(pamh, e));
|
|
rval = -1;
|
|
break;
|
|
}
|
|
|
|
if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
|
|
syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
|
|
rval = -1;
|
|
}
|
|
return rval;
|
|
}
|
|
|
|
#endif /* !defined(NOPAM) */
|
|
|
|
void
|
|
pass(passwd)
|
|
char *passwd;
|
|
{
|
|
int rval;
|
|
FILE *fd;
|
|
#ifdef LOGIN_CAP
|
|
login_cap_t *lc = NULL;
|
|
#endif
|
|
|
|
if (logged_in || askpasswd == 0) {
|
|
reply(503, "Login with USER first.");
|
|
return;
|
|
}
|
|
askpasswd = 0;
|
|
if (!guest) { /* "ftp" is only account allowed no password */
|
|
if (pw == NULL) {
|
|
rval = 1; /* failure below */
|
|
goto skip;
|
|
}
|
|
#if !defined(NOPAM)
|
|
rval = auth_pam(&pw, passwd);
|
|
if (rval >= 0)
|
|
goto skip;
|
|
#endif
|
|
#ifdef SKEY
|
|
rval = strcmp(skey_crypt(passwd, pw->pw_passwd, pw, pwok),
|
|
pw->pw_passwd);
|
|
pwok = 0;
|
|
#else
|
|
rval = strcmp(crypt(passwd, pw->pw_passwd), pw->pw_passwd);
|
|
#endif
|
|
/* The strcmp does not catch null passwords! */
|
|
if (*pw->pw_passwd == '\0' ||
|
|
(pw->pw_expire && time(NULL) >= pw->pw_expire))
|
|
rval = 1; /* failure */
|
|
skip:
|
|
/*
|
|
* If rval == 1, the user failed the authentication check
|
|
* above. If rval == 0, either PAM or local authentication
|
|
* succeeded.
|
|
*/
|
|
if (rval) {
|
|
reply(530, "Login incorrect.");
|
|
if (logging)
|
|
syslog(LOG_NOTICE,
|
|
"FTP LOGIN FAILED FROM %s, %s",
|
|
remotehost, curname);
|
|
pw = NULL;
|
|
if (login_attempts++ >= 5) {
|
|
syslog(LOG_NOTICE,
|
|
"repeated login failures from %s",
|
|
remotehost);
|
|
exit(0);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
login_attempts = 0; /* this time successful */
|
|
if (setegid((gid_t)pw->pw_gid) < 0) {
|
|
reply(550, "Can't set gid.");
|
|
return;
|
|
}
|
|
/* May be overridden by login.conf */
|
|
(void) umask(defumask);
|
|
#ifdef LOGIN_CAP
|
|
if ((lc = login_getpwclass(pw)) != NULL) {
|
|
char remote_ip[MAXHOSTNAMELEN];
|
|
|
|
getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
|
|
remote_ip, sizeof(remote_ip) - 1, NULL, 0,
|
|
NI_NUMERICHOST|NI_WITHSCOPEID);
|
|
remote_ip[sizeof(remote_ip) - 1] = 0;
|
|
if (!auth_hostok(lc, remotehost, remote_ip)) {
|
|
syslog(LOG_INFO|LOG_AUTH,
|
|
"FTP LOGIN FAILED (HOST) as %s: permission denied.",
|
|
pw->pw_name);
|
|
reply(530, "Permission denied.\n");
|
|
pw = NULL;
|
|
return;
|
|
}
|
|
if (!auth_timeok(lc, time(NULL))) {
|
|
reply(530, "Login not available right now.\n");
|
|
pw = NULL;
|
|
return;
|
|
}
|
|
}
|
|
setusercontext(lc, pw, (uid_t)0,
|
|
LOGIN_SETLOGIN|LOGIN_SETGROUP|LOGIN_SETPRIORITY|
|
|
LOGIN_SETRESOURCES|LOGIN_SETUMASK);
|
|
#else
|
|
setlogin(pw->pw_name);
|
|
(void) initgroups(pw->pw_name, pw->pw_gid);
|
|
#endif
|
|
|
|
/* open wtmp before chroot */
|
|
ftpd_logwtmp(ttyline, pw->pw_name, remotehost);
|
|
logged_in = 1;
|
|
|
|
if (guest && stats && statfd < 0)
|
|
#ifdef VIRTUAL_HOSTING
|
|
if ((statfd = open(thishost->statfile, O_WRONLY|O_APPEND)) < 0)
|
|
#else
|
|
if ((statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND)) < 0)
|
|
#endif
|
|
stats = 0;
|
|
|
|
dochroot =
|
|
#ifdef LOGIN_CAP /* Allow login.conf configuration as well */
|
|
login_getcapbool(lc, "ftp-chroot", 0) ||
|
|
#endif
|
|
checkuser(_PATH_FTPCHROOT, pw->pw_name, 1);
|
|
if (guest) {
|
|
/*
|
|
* We MUST do a chdir() after the chroot. Otherwise
|
|
* the old current directory will be accessible as "."
|
|
* outside the new root!
|
|
*/
|
|
if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
|
|
reply(550, "Can't set guest privileges.");
|
|
goto bad;
|
|
}
|
|
} else if (dochroot) {
|
|
if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
|
|
reply(550, "Can't change root.");
|
|
goto bad;
|
|
}
|
|
} else if (chdir(pw->pw_dir) < 0) {
|
|
if (chdir("/") < 0) {
|
|
reply(530, "User %s: can't change directory to %s.",
|
|
pw->pw_name, pw->pw_dir);
|
|
goto bad;
|
|
} else
|
|
lreply(230, "No directory! Logging in with home=/");
|
|
}
|
|
if (seteuid((uid_t)pw->pw_uid) < 0) {
|
|
reply(550, "Can't set uid.");
|
|
goto bad;
|
|
}
|
|
|
|
/*
|
|
* Display a login message, if it exists.
|
|
* N.B. reply(230,) must follow the message.
|
|
*/
|
|
#ifdef VIRTUAL_HOSTING
|
|
if ((fd = fopen(thishost->loginmsg, "r")) != NULL) {
|
|
#else
|
|
if ((fd = fopen(_PATH_FTPLOGINMESG, "r")) != NULL) {
|
|
#endif
|
|
char *cp, line[LINE_MAX];
|
|
|
|
while (fgets(line, sizeof(line), fd) != NULL) {
|
|
if ((cp = strchr(line, '\n')) != NULL)
|
|
*cp = '\0';
|
|
lreply(230, "%s", line);
|
|
}
|
|
(void) fflush(stdout);
|
|
(void) fclose(fd);
|
|
}
|
|
if (guest) {
|
|
if (ident != NULL)
|
|
free(ident);
|
|
ident = strdup(passwd);
|
|
if (ident == NULL)
|
|
fatal("Ran out of memory.");
|
|
|
|
reply(230, "Guest login ok, access restrictions apply.");
|
|
#ifdef SETPROCTITLE
|
|
#ifdef VIRTUAL_HOSTING
|
|
if (thishost != firsthost)
|
|
snprintf(proctitle, sizeof(proctitle),
|
|
"%s: anonymous(%s)/%.*s", remotehost, hostname,
|
|
(int)(sizeof(proctitle) - sizeof(remotehost) -
|
|
sizeof(": anonymous/")), passwd);
|
|
else
|
|
#endif
|
|
snprintf(proctitle, sizeof(proctitle),
|
|
"%s: anonymous/%.*s", remotehost,
|
|
(int)(sizeof(proctitle) - sizeof(remotehost) -
|
|
sizeof(": anonymous/")), passwd);
|
|
setproctitle("%s", proctitle);
|
|
#endif /* SETPROCTITLE */
|
|
if (logging)
|
|
syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
|
|
remotehost, passwd);
|
|
} else {
|
|
if (dochroot)
|
|
reply(230, "User %s logged in, access restrictions apply.",
|
|
pw->pw_name);
|
|
else
|
|
reply(230, "User %s logged in.", pw->pw_name);
|
|
|
|
#ifdef SETPROCTITLE
|
|
snprintf(proctitle, sizeof(proctitle),
|
|
"%s: %s", remotehost, pw->pw_name);
|
|
setproctitle("%s", proctitle);
|
|
#endif /* SETPROCTITLE */
|
|
if (logging)
|
|
syslog(LOG_INFO, "FTP LOGIN FROM %s as %s",
|
|
remotehost, pw->pw_name);
|
|
}
|
|
#ifdef LOGIN_CAP
|
|
login_close(lc);
|
|
#endif
|
|
return;
|
|
bad:
|
|
/* Forget all about it... */
|
|
#ifdef LOGIN_CAP
|
|
login_close(lc);
|
|
#endif
|
|
end_login();
|
|
}
|
|
|
|
void
|
|
retrieve(cmd, name)
|
|
char *cmd, *name;
|
|
{
|
|
FILE *fin, *dout;
|
|
struct stat st;
|
|
int (*closefunc) __P((FILE *));
|
|
time_t start;
|
|
|
|
if (cmd == 0) {
|
|
fin = fopen(name, "r"), closefunc = fclose;
|
|
st.st_size = 0;
|
|
} else {
|
|
char line[BUFSIZ];
|
|
|
|
(void) snprintf(line, sizeof(line), cmd, name), name = line;
|
|
fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
|
|
st.st_size = -1;
|
|
st.st_blksize = BUFSIZ;
|
|
}
|
|
if (fin == NULL) {
|
|
if (errno != 0) {
|
|
perror_reply(550, name);
|
|
if (cmd == 0) {
|
|
LOGCMD("get", name);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
byte_count = -1;
|
|
if (cmd == 0 && (fstat(fileno(fin), &st) < 0 || !S_ISREG(st.st_mode))) {
|
|
reply(550, "%s: not a plain file.", name);
|
|
goto done;
|
|
}
|
|
if (restart_point) {
|
|
if (type == TYPE_A) {
|
|
off_t i, n;
|
|
int c;
|
|
|
|
n = restart_point;
|
|
i = 0;
|
|
while (i++ < n) {
|
|
if ((c=getc(fin)) == EOF) {
|
|
perror_reply(550, name);
|
|
goto done;
|
|
}
|
|
if (c == '\n')
|
|
i++;
|
|
}
|
|
} else if (lseek(fileno(fin), restart_point, L_SET) < 0) {
|
|
perror_reply(550, name);
|
|
goto done;
|
|
}
|
|
}
|
|
dout = dataconn(name, st.st_size, "w");
|
|
if (dout == NULL)
|
|
goto done;
|
|
time(&start);
|
|
send_data(fin, dout, st.st_blksize, st.st_size,
|
|
restart_point == 0 && cmd == 0 && S_ISREG(st.st_mode));
|
|
if (cmd == 0 && guest && stats)
|
|
logxfer(name, st.st_size, start);
|
|
(void) fclose(dout);
|
|
data = -1;
|
|
pdata = -1;
|
|
done:
|
|
if (cmd == 0)
|
|
LOGBYTES("get", name, byte_count);
|
|
(*closefunc)(fin);
|
|
}
|
|
|
|
void
|
|
store(name, mode, unique)
|
|
char *name, *mode;
|
|
int unique;
|
|
{
|
|
FILE *fout, *din;
|
|
struct stat st;
|
|
int (*closefunc) __P((FILE *));
|
|
|
|
if ((unique || guest) && stat(name, &st) == 0 &&
|
|
(name = gunique(name)) == NULL) {
|
|
LOGCMD(*mode == 'w' ? "put" : "append", name);
|
|
return;
|
|
}
|
|
|
|
if (restart_point)
|
|
mode = "r+";
|
|
fout = fopen(name, mode);
|
|
closefunc = fclose;
|
|
if (fout == NULL) {
|
|
perror_reply(553, name);
|
|
LOGCMD(*mode == 'w' ? "put" : "append", name);
|
|
return;
|
|
}
|
|
byte_count = -1;
|
|
if (restart_point) {
|
|
if (type == TYPE_A) {
|
|
off_t i, n;
|
|
int c;
|
|
|
|
n = restart_point;
|
|
i = 0;
|
|
while (i++ < n) {
|
|
if ((c=getc(fout)) == EOF) {
|
|
perror_reply(550, name);
|
|
goto done;
|
|
}
|
|
if (c == '\n')
|
|
i++;
|
|
}
|
|
/*
|
|
* We must do this seek to "current" position
|
|
* because we are changing from reading to
|
|
* writing.
|
|
*/
|
|
if (fseek(fout, 0L, L_INCR) < 0) {
|
|
perror_reply(550, name);
|
|
goto done;
|
|
}
|
|
} else if (lseek(fileno(fout), restart_point, L_SET) < 0) {
|
|
perror_reply(550, name);
|
|
goto done;
|
|
}
|
|
}
|
|
din = dataconn(name, (off_t)-1, "r");
|
|
if (din == NULL)
|
|
goto done;
|
|
if (receive_data(din, fout) == 0) {
|
|
if (unique)
|
|
reply(226, "Transfer complete (unique file name:%s).",
|
|
name);
|
|
else
|
|
reply(226, "Transfer complete.");
|
|
}
|
|
(void) fclose(din);
|
|
data = -1;
|
|
pdata = -1;
|
|
done:
|
|
LOGBYTES(*mode == 'w' ? "put" : "append", name, byte_count);
|
|
(*closefunc)(fout);
|
|
}
|
|
|
|
static FILE *
|
|
getdatasock(mode)
|
|
char *mode;
|
|
{
|
|
int on = 1, s, t, tries;
|
|
|
|
if (data >= 0)
|
|
return (fdopen(data, mode));
|
|
(void) seteuid((uid_t)0);
|
|
|
|
s = socket(data_dest.su_family, SOCK_STREAM, 0);
|
|
if (s < 0)
|
|
goto bad;
|
|
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
|
|
(char *) &on, sizeof(on)) < 0)
|
|
goto bad;
|
|
/* anchor socket to avoid multi-homing problems */
|
|
data_source = ctrl_addr;
|
|
data_source.su_port = htons(20); /* ftp-data port */
|
|
for (tries = 1; ; tries++) {
|
|
if (bind(s, (struct sockaddr *)&data_source,
|
|
data_source.su_len) >= 0)
|
|
break;
|
|
if (errno != EADDRINUSE || tries > 10)
|
|
goto bad;
|
|
sleep(tries);
|
|
}
|
|
(void) seteuid((uid_t)pw->pw_uid);
|
|
#ifdef IP_TOS
|
|
if (data_source.su_family == AF_INET)
|
|
{
|
|
on = IPTOS_THROUGHPUT;
|
|
if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int)) < 0)
|
|
syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
|
|
}
|
|
#endif
|
|
#ifdef TCP_NOPUSH
|
|
/*
|
|
* Turn off push flag to keep sender TCP from sending short packets
|
|
* at the boundaries of each write(). Should probably do a SO_SNDBUF
|
|
* to set the send buffer size as well, but that may not be desirable
|
|
* in heavy-load situations.
|
|
*/
|
|
on = 1;
|
|
if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, (char *)&on, sizeof on) < 0)
|
|
syslog(LOG_WARNING, "setsockopt (TCP_NOPUSH): %m");
|
|
#endif
|
|
#ifdef SO_SNDBUF
|
|
on = 65536;
|
|
if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&on, sizeof on) < 0)
|
|
syslog(LOG_WARNING, "setsockopt (SO_SNDBUF): %m");
|
|
#endif
|
|
|
|
return (fdopen(s, mode));
|
|
bad:
|
|
/* Return the real value of errno (close may change it) */
|
|
t = errno;
|
|
(void) seteuid((uid_t)pw->pw_uid);
|
|
(void) close(s);
|
|
errno = t;
|
|
return (NULL);
|
|
}
|
|
|
|
static FILE *
|
|
dataconn(name, size, mode)
|
|
char *name;
|
|
off_t size;
|
|
char *mode;
|
|
{
|
|
char sizebuf[32];
|
|
FILE *file;
|
|
int retry = 0, tos;
|
|
|
|
file_size = size;
|
|
byte_count = 0;
|
|
if (size != (off_t) -1)
|
|
(void) snprintf(sizebuf, sizeof(sizebuf), " (%qd bytes)", size);
|
|
else
|
|
*sizebuf = '\0';
|
|
if (pdata >= 0) {
|
|
union sockunion from;
|
|
int s, fromlen = ctrl_addr.su_len;
|
|
struct timeval timeout;
|
|
fd_set set;
|
|
|
|
FD_ZERO(&set);
|
|
FD_SET(pdata, &set);
|
|
|
|
timeout.tv_usec = 0;
|
|
timeout.tv_sec = 120;
|
|
|
|
if (select(pdata+1, &set, (fd_set *) 0, (fd_set *) 0, &timeout) == 0 ||
|
|
(s = accept(pdata, (struct sockaddr *) &from, &fromlen)) < 0) {
|
|
reply(425, "Can't open data connection.");
|
|
(void) close(pdata);
|
|
pdata = -1;
|
|
return (NULL);
|
|
}
|
|
(void) close(pdata);
|
|
pdata = s;
|
|
#ifdef IP_TOS
|
|
if (from.su_family == AF_INET)
|
|
{
|
|
tos = IPTOS_THROUGHPUT;
|
|
(void) setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos,
|
|
sizeof(int));
|
|
}
|
|
#endif
|
|
reply(150, "Opening %s mode data connection for '%s'%s.",
|
|
type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
|
|
return (fdopen(pdata, mode));
|
|
}
|
|
if (data >= 0) {
|
|
reply(125, "Using existing data connection for '%s'%s.",
|
|
name, sizebuf);
|
|
usedefault = 1;
|
|
return (fdopen(data, mode));
|
|
}
|
|
if (usedefault)
|
|
data_dest = his_addr;
|
|
usedefault = 1;
|
|
file = getdatasock(mode);
|
|
if (file == NULL) {
|
|
char hostbuf[BUFSIZ], portbuf[BUFSIZ];
|
|
getnameinfo((struct sockaddr *)&data_source,
|
|
data_source.su_len, hostbuf, sizeof(hostbuf) - 1,
|
|
portbuf, sizeof(portbuf),
|
|
NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID);
|
|
reply(425, "Can't create data socket (%s,%s): %s.",
|
|
hostbuf, portbuf, strerror(errno));
|
|
return (NULL);
|
|
}
|
|
data = fileno(file);
|
|
while (connect(data, (struct sockaddr *)&data_dest,
|
|
data_dest.su_len) < 0) {
|
|
if (errno == EADDRINUSE && retry < swaitmax) {
|
|
sleep((unsigned) swaitint);
|
|
retry += swaitint;
|
|
continue;
|
|
}
|
|
perror_reply(425, "Can't build data connection");
|
|
(void) fclose(file);
|
|
data = -1;
|
|
return (NULL);
|
|
}
|
|
reply(150, "Opening %s mode data connection for '%s'%s.",
|
|
type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
|
|
return (file);
|
|
}
|
|
|
|
/*
|
|
* Tranfer the contents of "instr" to "outstr" peer using the appropriate
|
|
* encapsulation of the data subject to Mode, Structure, and Type.
|
|
*
|
|
* NB: Form isn't handled.
|
|
*/
|
|
static void
|
|
send_data(instr, outstr, blksize, filesize, isreg)
|
|
FILE *instr, *outstr;
|
|
off_t blksize;
|
|
off_t filesize;
|
|
int isreg;
|
|
{
|
|
int c, cnt, filefd, netfd;
|
|
char *buf, *bp;
|
|
size_t len;
|
|
|
|
transflag++;
|
|
if (setjmp(urgcatch)) {
|
|
transflag = 0;
|
|
return;
|
|
}
|
|
switch (type) {
|
|
|
|
case TYPE_A:
|
|
while ((c = getc(instr)) != EOF) {
|
|
byte_count++;
|
|
if (c == '\n') {
|
|
if (ferror(outstr))
|
|
goto data_err;
|
|
(void) putc('\r', outstr);
|
|
}
|
|
(void) putc(c, outstr);
|
|
}
|
|
fflush(outstr);
|
|
transflag = 0;
|
|
if (ferror(instr))
|
|
goto file_err;
|
|
if (ferror(outstr))
|
|
goto data_err;
|
|
reply(226, "Transfer complete.");
|
|
return;
|
|
|
|
case TYPE_I:
|
|
case TYPE_L:
|
|
/*
|
|
* isreg is only set if we are not doing restart and we
|
|
* are sending a regular file
|
|
*/
|
|
netfd = fileno(outstr);
|
|
filefd = fileno(instr);
|
|
|
|
if (isreg && filesize < (off_t)16 * 1024 * 1024) {
|
|
buf = mmap(0, filesize, PROT_READ, MAP_SHARED, filefd,
|
|
(off_t)0);
|
|
if (buf == MAP_FAILED) {
|
|
syslog(LOG_WARNING, "mmap(%lu): %m",
|
|
(unsigned long)filesize);
|
|
goto oldway;
|
|
}
|
|
bp = buf;
|
|
len = filesize;
|
|
do {
|
|
cnt = write(netfd, bp, len);
|
|
len -= cnt;
|
|
bp += cnt;
|
|
if (cnt > 0) byte_count += cnt;
|
|
} while(cnt > 0 && len > 0);
|
|
|
|
transflag = 0;
|
|
munmap(buf, (size_t)filesize);
|
|
if (cnt < 0)
|
|
goto data_err;
|
|
reply(226, "Transfer complete.");
|
|
return;
|
|
}
|
|
|
|
oldway:
|
|
if ((buf = malloc((u_int)blksize)) == NULL) {
|
|
transflag = 0;
|
|
perror_reply(451, "Local resource failure: malloc");
|
|
return;
|
|
}
|
|
|
|
while ((cnt = read(filefd, buf, (u_int)blksize)) > 0 &&
|
|
write(netfd, buf, cnt) == cnt)
|
|
byte_count += cnt;
|
|
transflag = 0;
|
|
(void)free(buf);
|
|
if (cnt != 0) {
|
|
if (cnt < 0)
|
|
goto file_err;
|
|
goto data_err;
|
|
}
|
|
reply(226, "Transfer complete.");
|
|
return;
|
|
default:
|
|
transflag = 0;
|
|
reply(550, "Unimplemented TYPE %d in send_data", type);
|
|
return;
|
|
}
|
|
|
|
data_err:
|
|
transflag = 0;
|
|
perror_reply(426, "Data connection");
|
|
return;
|
|
|
|
file_err:
|
|
transflag = 0;
|
|
perror_reply(551, "Error on input file");
|
|
}
|
|
|
|
/*
|
|
* Transfer data from peer to "outstr" using the appropriate encapulation of
|
|
* the data subject to Mode, Structure, and Type.
|
|
*
|
|
* N.B.: Form isn't handled.
|
|
*/
|
|
static int
|
|
receive_data(instr, outstr)
|
|
FILE *instr, *outstr;
|
|
{
|
|
int c;
|
|
int cnt, bare_lfs;
|
|
char buf[BUFSIZ];
|
|
|
|
transflag++;
|
|
if (setjmp(urgcatch)) {
|
|
transflag = 0;
|
|
return (-1);
|
|
}
|
|
|
|
bare_lfs = 0;
|
|
|
|
switch (type) {
|
|
|
|
case TYPE_I:
|
|
case TYPE_L:
|
|
while ((cnt = read(fileno(instr), buf, sizeof(buf))) > 0) {
|
|
if (write(fileno(outstr), buf, cnt) != cnt)
|
|
goto file_err;
|
|
byte_count += cnt;
|
|
}
|
|
if (cnt < 0)
|
|
goto data_err;
|
|
transflag = 0;
|
|
return (0);
|
|
|
|
case TYPE_E:
|
|
reply(553, "TYPE E not implemented.");
|
|
transflag = 0;
|
|
return (-1);
|
|
|
|
case TYPE_A:
|
|
while ((c = getc(instr)) != EOF) {
|
|
byte_count++;
|
|
if (c == '\n')
|
|
bare_lfs++;
|
|
while (c == '\r') {
|
|
if (ferror(outstr))
|
|
goto data_err;
|
|
if ((c = getc(instr)) != '\n') {
|
|
(void) putc ('\r', outstr);
|
|
if (c == '\0' || c == EOF)
|
|
goto contin2;
|
|
}
|
|
}
|
|
(void) putc(c, outstr);
|
|
contin2: ;
|
|
}
|
|
fflush(outstr);
|
|
if (ferror(instr))
|
|
goto data_err;
|
|
if (ferror(outstr))
|
|
goto file_err;
|
|
transflag = 0;
|
|
if (bare_lfs) {
|
|
lreply(226,
|
|
"WARNING! %d bare linefeeds received in ASCII mode",
|
|
bare_lfs);
|
|
(void)printf(" File may not have transferred correctly.\r\n");
|
|
}
|
|
return (0);
|
|
default:
|
|
reply(550, "Unimplemented TYPE %d in receive_data", type);
|
|
transflag = 0;
|
|
return (-1);
|
|
}
|
|
|
|
data_err:
|
|
transflag = 0;
|
|
perror_reply(426, "Data Connection");
|
|
return (-1);
|
|
|
|
file_err:
|
|
transflag = 0;
|
|
perror_reply(452, "Error writing file");
|
|
return (-1);
|
|
}
|
|
|
|
void
|
|
statfilecmd(filename)
|
|
char *filename;
|
|
{
|
|
FILE *fin;
|
|
int c;
|
|
char line[LINE_MAX];
|
|
|
|
(void)snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename);
|
|
fin = ftpd_popen(line, "r");
|
|
lreply(211, "status of %s:", filename);
|
|
while ((c = getc(fin)) != EOF) {
|
|
if (c == '\n') {
|
|
if (ferror(stdout)){
|
|
perror_reply(421, "control connection");
|
|
(void) ftpd_pclose(fin);
|
|
dologout(1);
|
|
/* NOTREACHED */
|
|
}
|
|
if (ferror(fin)) {
|
|
perror_reply(551, filename);
|
|
(void) ftpd_pclose(fin);
|
|
return;
|
|
}
|
|
(void) putc('\r', stdout);
|
|
}
|
|
(void) putc(c, stdout);
|
|
}
|
|
(void) ftpd_pclose(fin);
|
|
reply(211, "End of Status");
|
|
}
|
|
|
|
void
|
|
statcmd()
|
|
{
|
|
union sockunion *su;
|
|
u_char *a, *p;
|
|
char hname[INET6_ADDRSTRLEN];
|
|
int ispassive;
|
|
|
|
lreply(211, "%s FTP server status:", hostname, version);
|
|
printf(" %s\r\n", version);
|
|
printf(" Connected to %s", remotehost);
|
|
if (!getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
|
|
hname, sizeof(hname) - 1, NULL, 0,
|
|
NI_NUMERICHOST|NI_WITHSCOPEID)) {
|
|
if (strcmp(hname, remotehost) != 0)
|
|
printf(" (%s)", hname);
|
|
}
|
|
printf("\r\n");
|
|
if (logged_in) {
|
|
if (guest)
|
|
printf(" Logged in anonymously\r\n");
|
|
else
|
|
printf(" Logged in as %s\r\n", pw->pw_name);
|
|
} else if (askpasswd)
|
|
printf(" Waiting for password\r\n");
|
|
else
|
|
printf(" Waiting for user name\r\n");
|
|
printf(" TYPE: %s", typenames[type]);
|
|
if (type == TYPE_A || type == TYPE_E)
|
|
printf(", FORM: %s", formnames[form]);
|
|
if (type == TYPE_L)
|
|
#if NBBY == 8
|
|
printf(" %d", NBBY);
|
|
#else
|
|
printf(" %d", bytesize); /* need definition! */
|
|
#endif
|
|
printf("; STRUcture: %s; transfer MODE: %s\r\n",
|
|
strunames[stru], modenames[mode]);
|
|
if (data != -1)
|
|
printf(" Data connection open\r\n");
|
|
else if (pdata != -1) {
|
|
ispassive = 1;
|
|
su = &pasv_addr;
|
|
goto printaddr;
|
|
} else if (usedefault == 0) {
|
|
ispassive = 0;
|
|
su = &data_dest;
|
|
printaddr:
|
|
#define UC(b) (((int) b) & 0xff)
|
|
if (epsvall) {
|
|
printf(" EPSV only mode (EPSV ALL)\r\n");
|
|
goto epsvonly;
|
|
}
|
|
|
|
/* PORT/PASV */
|
|
if (su->su_family == AF_INET) {
|
|
a = (u_char *) &su->su_sin.sin_addr;
|
|
p = (u_char *) &su->su_sin.sin_port;
|
|
printf(" %s (%d,%d,%d,%d,%d,%d)\r\n",
|
|
ispassive ? "PASV" : "PORT",
|
|
UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
|
|
UC(p[0]), UC(p[1]));
|
|
}
|
|
|
|
/* LPRT/LPSV */
|
|
{
|
|
int alen, af, i;
|
|
|
|
switch (su->su_family) {
|
|
case AF_INET:
|
|
a = (u_char *) &su->su_sin.sin_addr;
|
|
p = (u_char *) &su->su_sin.sin_port;
|
|
alen = sizeof(su->su_sin.sin_addr);
|
|
af = 4;
|
|
break;
|
|
case AF_INET6:
|
|
a = (u_char *) &su->su_sin6.sin6_addr;
|
|
p = (u_char *) &su->su_sin6.sin6_port;
|
|
alen = sizeof(su->su_sin6.sin6_addr);
|
|
af = 6;
|
|
break;
|
|
default:
|
|
af = 0;
|
|
break;
|
|
}
|
|
if (af) {
|
|
printf(" %s (%d,%d,", ispassive ? "LPSV" : "LPRT",
|
|
af, alen);
|
|
for (i = 0; i < alen; i++)
|
|
printf("%d,", UC(a[i]));
|
|
printf("%d,%d,%d)\r\n", 2, UC(p[0]), UC(p[1]));
|
|
}
|
|
}
|
|
|
|
epsvonly:;
|
|
/* EPRT/EPSV */
|
|
{
|
|
int af;
|
|
|
|
switch (su->su_family) {
|
|
case AF_INET:
|
|
af = 1;
|
|
break;
|
|
case AF_INET6:
|
|
af = 2;
|
|
break;
|
|
default:
|
|
af = 0;
|
|
break;
|
|
}
|
|
if (af) {
|
|
if (!getnameinfo((struct sockaddr *)su, su->su_len,
|
|
hname, sizeof(hname) - 1, NULL, 0,
|
|
NI_NUMERICHOST)) {
|
|
printf(" %s |%d|%s|%d|\r\n",
|
|
ispassive ? "EPSV" : "EPRT",
|
|
af, hname, htons(su->su_port));
|
|
}
|
|
}
|
|
}
|
|
#undef UC
|
|
} else
|
|
printf(" No data connection\r\n");
|
|
reply(211, "End of status");
|
|
}
|
|
|
|
void
|
|
fatal(s)
|
|
char *s;
|
|
{
|
|
|
|
reply(451, "Error in server: %s\n", s);
|
|
reply(221, "Closing connection due to server error.");
|
|
dologout(0);
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
void
|
|
#if __STDC__
|
|
reply(int n, const char *fmt, ...)
|
|
#else
|
|
reply(n, fmt, va_alist)
|
|
int n;
|
|
char *fmt;
|
|
va_dcl
|
|
#endif
|
|
{
|
|
va_list ap;
|
|
#if __STDC__
|
|
va_start(ap, fmt);
|
|
#else
|
|
va_start(ap);
|
|
#endif
|
|
(void)printf("%d ", n);
|
|
(void)vprintf(fmt, ap);
|
|
(void)printf("\r\n");
|
|
(void)fflush(stdout);
|
|
if (debug) {
|
|
syslog(LOG_DEBUG, "<--- %d ", n);
|
|
vsyslog(LOG_DEBUG, fmt, ap);
|
|
}
|
|
}
|
|
|
|
void
|
|
#if __STDC__
|
|
lreply(int n, const char *fmt, ...)
|
|
#else
|
|
lreply(n, fmt, va_alist)
|
|
int n;
|
|
char *fmt;
|
|
va_dcl
|
|
#endif
|
|
{
|
|
va_list ap;
|
|
#if __STDC__
|
|
va_start(ap, fmt);
|
|
#else
|
|
va_start(ap);
|
|
#endif
|
|
(void)printf("%d- ", n);
|
|
(void)vprintf(fmt, ap);
|
|
(void)printf("\r\n");
|
|
(void)fflush(stdout);
|
|
if (debug) {
|
|
syslog(LOG_DEBUG, "<--- %d- ", n);
|
|
vsyslog(LOG_DEBUG, fmt, ap);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ack(s)
|
|
char *s;
|
|
{
|
|
|
|
reply(250, "%s command successful.", s);
|
|
}
|
|
|
|
void
|
|
nack(s)
|
|
char *s;
|
|
{
|
|
|
|
reply(502, "%s command not implemented.", s);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
void
|
|
yyerror(s)
|
|
char *s;
|
|
{
|
|
char *cp;
|
|
|
|
if ((cp = strchr(cbuf,'\n')))
|
|
*cp = '\0';
|
|
reply(500, "'%s': command not understood.", cbuf);
|
|
}
|
|
|
|
void
|
|
delete(name)
|
|
char *name;
|
|
{
|
|
struct stat st;
|
|
|
|
LOGCMD("delete", name);
|
|
if (stat(name, &st) < 0) {
|
|
perror_reply(550, name);
|
|
return;
|
|
}
|
|
if ((st.st_mode&S_IFMT) == S_IFDIR) {
|
|
if (rmdir(name) < 0) {
|
|
perror_reply(550, name);
|
|
return;
|
|
}
|
|
goto done;
|
|
}
|
|
if (unlink(name) < 0) {
|
|
perror_reply(550, name);
|
|
return;
|
|
}
|
|
done:
|
|
ack("DELE");
|
|
}
|
|
|
|
void
|
|
cwd(path)
|
|
char *path;
|
|
{
|
|
|
|
if (chdir(path) < 0)
|
|
perror_reply(550, path);
|
|
else
|
|
ack("CWD");
|
|
}
|
|
|
|
void
|
|
makedir(name)
|
|
char *name;
|
|
{
|
|
|
|
LOGCMD("mkdir", name);
|
|
if (mkdir(name, 0777) < 0)
|
|
perror_reply(550, name);
|
|
else
|
|
reply(257, "MKD command successful.");
|
|
}
|
|
|
|
void
|
|
removedir(name)
|
|
char *name;
|
|
{
|
|
|
|
LOGCMD("rmdir", name);
|
|
if (rmdir(name) < 0)
|
|
perror_reply(550, name);
|
|
else
|
|
ack("RMD");
|
|
}
|
|
|
|
void
|
|
pwd()
|
|
{
|
|
char path[MAXPATHLEN + 1];
|
|
|
|
if (getwd(path) == (char *)NULL)
|
|
reply(550, "%s.", path);
|
|
else
|
|
reply(257, "\"%s\" is current directory.", path);
|
|
}
|
|
|
|
char *
|
|
renamefrom(name)
|
|
char *name;
|
|
{
|
|
struct stat st;
|
|
|
|
if (stat(name, &st) < 0) {
|
|
perror_reply(550, name);
|
|
return ((char *)0);
|
|
}
|
|
reply(350, "File exists, ready for destination name");
|
|
return (name);
|
|
}
|
|
|
|
void
|
|
renamecmd(from, to)
|
|
char *from, *to;
|
|
{
|
|
struct stat st;
|
|
|
|
LOGCMD2("rename", from, to);
|
|
|
|
if (guest && (stat(to, &st) == 0)) {
|
|
reply(550, "%s: permission denied", to);
|
|
return;
|
|
}
|
|
|
|
if (rename(from, to) < 0)
|
|
perror_reply(550, "rename");
|
|
else
|
|
ack("RNTO");
|
|
}
|
|
|
|
static void
|
|
dolog(who)
|
|
struct sockaddr *who;
|
|
{
|
|
int error;
|
|
|
|
realhostname_sa(remotehost, sizeof(remotehost) - 1, who, who->sa_len);
|
|
|
|
#ifdef SETPROCTITLE
|
|
#ifdef VIRTUAL_HOSTING
|
|
if (thishost != firsthost)
|
|
snprintf(proctitle, sizeof(proctitle), "%s: connected (to %s)",
|
|
remotehost, hostname);
|
|
else
|
|
#endif
|
|
snprintf(proctitle, sizeof(proctitle), "%s: connected",
|
|
remotehost);
|
|
setproctitle("%s", proctitle);
|
|
#endif /* SETPROCTITLE */
|
|
|
|
if (logging) {
|
|
#ifdef VIRTUAL_HOSTING
|
|
if (thishost != firsthost)
|
|
syslog(LOG_INFO, "connection from %s (to %s)",
|
|
remotehost, hostname);
|
|
else
|
|
#endif
|
|
{
|
|
char who_name[MAXHOSTNAMELEN];
|
|
|
|
error = getnameinfo(who, who->sa_len,
|
|
who_name, sizeof(who_name) - 1,
|
|
NULL, 0,
|
|
NI_NUMERICHOST|NI_WITHSCOPEID);
|
|
syslog(LOG_INFO, "connection from %s (%s)", remotehost,
|
|
error == 0 ? who_name : "");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Record logout in wtmp file
|
|
* and exit with supplied status.
|
|
*/
|
|
void
|
|
dologout(status)
|
|
int status;
|
|
{
|
|
/*
|
|
* Prevent reception of SIGURG from resulting in a resumption
|
|
* back to the main program loop.
|
|
*/
|
|
transflag = 0;
|
|
|
|
if (logged_in) {
|
|
(void) seteuid((uid_t)0);
|
|
ftpd_logwtmp(ttyline, "", "");
|
|
}
|
|
/* beware of flushing buffers after a SIGPIPE */
|
|
_exit(status);
|
|
}
|
|
|
|
static void
|
|
myoob(signo)
|
|
int signo;
|
|
{
|
|
char *cp;
|
|
|
|
/* only process if transfer occurring */
|
|
if (!transflag)
|
|
return;
|
|
cp = tmpline;
|
|
if (getline(cp, 7, stdin) == NULL) {
|
|
reply(221, "You could at least say goodbye.");
|
|
dologout(0);
|
|
}
|
|
upper(cp);
|
|
if (strcmp(cp, "ABOR\r\n") == 0) {
|
|
tmpline[0] = '\0';
|
|
reply(426, "Transfer aborted. Data connection closed.");
|
|
reply(226, "Abort successful");
|
|
longjmp(urgcatch, 1);
|
|
}
|
|
if (strcmp(cp, "STAT\r\n") == 0) {
|
|
tmpline[0] = '\0';
|
|
if (file_size != (off_t) -1)
|
|
reply(213, "Status: %qd of %qd bytes transferred",
|
|
byte_count, file_size);
|
|
else
|
|
reply(213, "Status: %qd bytes transferred", byte_count);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Note: a response of 425 is not mentioned as a possible response to
|
|
* the PASV command in RFC959. However, it has been blessed as
|
|
* a legitimate response by Jon Postel in a telephone conversation
|
|
* with Rick Adams on 25 Jan 89.
|
|
*/
|
|
void
|
|
passive()
|
|
{
|
|
int len;
|
|
char *p, *a;
|
|
|
|
if (pdata >= 0) /* close old port if one set */
|
|
close(pdata);
|
|
|
|
pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
|
|
if (pdata < 0) {
|
|
perror_reply(425, "Can't open passive connection");
|
|
return;
|
|
}
|
|
|
|
(void) seteuid((uid_t)0);
|
|
|
|
#ifdef IP_PORTRANGE
|
|
if (ctrl_addr.su_family == AF_INET) {
|
|
int on = restricted_data_ports ? IP_PORTRANGE_HIGH
|
|
: IP_PORTRANGE_DEFAULT;
|
|
|
|
if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE,
|
|
(char *)&on, sizeof(on)) < 0)
|
|
goto pasv_error;
|
|
}
|
|
#endif
|
|
|
|
pasv_addr = ctrl_addr;
|
|
pasv_addr.su_port = 0;
|
|
if (bind(pdata, (struct sockaddr *)&pasv_addr, pasv_addr.su_len) < 0)
|
|
goto pasv_error;
|
|
|
|
(void) seteuid((uid_t)pw->pw_uid);
|
|
|
|
len = sizeof(pasv_addr);
|
|
if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
|
|
goto pasv_error;
|
|
if (listen(pdata, 1) < 0)
|
|
goto pasv_error;
|
|
if (pasv_addr.su_family == AF_INET)
|
|
a = (char *) &pasv_addr.su_sin.sin_addr;
|
|
else if (pasv_addr.su_family == AF_INET6 &&
|
|
IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr))
|
|
a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
|
|
else
|
|
goto pasv_error;
|
|
|
|
p = (char *) &pasv_addr.su_port;
|
|
|
|
#define UC(b) (((int) b) & 0xff)
|
|
|
|
reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
|
|
UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
|
|
return;
|
|
|
|
pasv_error:
|
|
(void) seteuid((uid_t)pw->pw_uid);
|
|
(void) close(pdata);
|
|
pdata = -1;
|
|
perror_reply(425, "Can't open passive connection");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Long Passive defined in RFC 1639.
|
|
* 228 Entering Long Passive Mode
|
|
* (af, hal, h1, h2, h3,..., pal, p1, p2...)
|
|
*/
|
|
|
|
void
|
|
long_passive(cmd, pf)
|
|
char *cmd;
|
|
int pf;
|
|
{
|
|
int len;
|
|
char *p, *a;
|
|
|
|
if (pdata >= 0) /* close old port if one set */
|
|
close(pdata);
|
|
|
|
if (pf != PF_UNSPEC) {
|
|
if (ctrl_addr.su_family != pf) {
|
|
switch (ctrl_addr.su_family) {
|
|
case AF_INET:
|
|
pf = 1;
|
|
break;
|
|
case AF_INET6:
|
|
pf = 2;
|
|
break;
|
|
default:
|
|
pf = 0;
|
|
break;
|
|
}
|
|
/*
|
|
* XXX
|
|
* only EPRT/EPSV ready clients will understand this
|
|
*/
|
|
if (strcmp(cmd, "EPSV") == 0 && pf) {
|
|
reply(522, "Network protocol mismatch, "
|
|
"use (%d)", pf);
|
|
} else
|
|
reply(501, "Network protocol mismatch"); /*XXX*/
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
|
|
if (pdata < 0) {
|
|
perror_reply(425, "Can't open passive connection");
|
|
return;
|
|
}
|
|
|
|
(void) seteuid((uid_t)0);
|
|
|
|
pasv_addr = ctrl_addr;
|
|
pasv_addr.su_port = 0;
|
|
len = pasv_addr.su_len;
|
|
|
|
if (bind(pdata, (struct sockaddr *)&pasv_addr, len) < 0)
|
|
goto pasv_error;
|
|
|
|
(void) seteuid((uid_t)pw->pw_uid);
|
|
|
|
if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
|
|
goto pasv_error;
|
|
if (listen(pdata, 1) < 0)
|
|
goto pasv_error;
|
|
|
|
#define UC(b) (((int) b) & 0xff)
|
|
|
|
if (strcmp(cmd, "LPSV") == 0) {
|
|
p = (char *)&pasv_addr.su_port;
|
|
switch (pasv_addr.su_family) {
|
|
case AF_INET:
|
|
a = (char *) &pasv_addr.su_sin.sin_addr;
|
|
v4_reply:
|
|
reply(228,
|
|
"Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)",
|
|
4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
|
|
2, UC(p[0]), UC(p[1]));
|
|
return;
|
|
case AF_INET6:
|
|
if (IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) {
|
|
a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12];
|
|
goto v4_reply;
|
|
}
|
|
a = (char *) &pasv_addr.su_sin6.sin6_addr;
|
|
reply(228,
|
|
"Entering Long Passive Mode "
|
|
"(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)",
|
|
6, 16, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
|
|
UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]),
|
|
UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]),
|
|
UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
|
|
2, UC(p[0]), UC(p[1]));
|
|
return;
|
|
}
|
|
} else if (strcmp(cmd, "EPSV") == 0) {
|
|
switch (pasv_addr.su_family) {
|
|
case AF_INET:
|
|
case AF_INET6:
|
|
reply(229, "Entering Extended Passive Mode (|||%d|)",
|
|
ntohs(pasv_addr.su_port));
|
|
return;
|
|
}
|
|
} else {
|
|
/* more proper error code? */
|
|
}
|
|
|
|
pasv_error:
|
|
(void) seteuid((uid_t)pw->pw_uid);
|
|
(void) close(pdata);
|
|
pdata = -1;
|
|
perror_reply(425, "Can't open passive connection");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Generate unique name for file with basename "local".
|
|
* The file named "local" is already known to exist.
|
|
* Generates failure reply on error.
|
|
*/
|
|
static char *
|
|
gunique(local)
|
|
char *local;
|
|
{
|
|
static char new[MAXPATHLEN];
|
|
struct stat st;
|
|
int count;
|
|
char *cp;
|
|
|
|
cp = strrchr(local, '/');
|
|
if (cp)
|
|
*cp = '\0';
|
|
if (stat(cp ? local : ".", &st) < 0) {
|
|
perror_reply(553, cp ? local : ".");
|
|
return ((char *) 0);
|
|
}
|
|
if (cp)
|
|
*cp = '/';
|
|
/* -4 is for the .nn<null> we put on the end below */
|
|
(void) snprintf(new, sizeof(new) - 4, "%s", local);
|
|
cp = new + strlen(new);
|
|
*cp++ = '.';
|
|
for (count = 1; count < 100; count++) {
|
|
(void)sprintf(cp, "%d", count);
|
|
if (stat(new, &st) < 0)
|
|
return (new);
|
|
}
|
|
reply(452, "Unique file name cannot be created.");
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Format and send reply containing system error number.
|
|
*/
|
|
void
|
|
perror_reply(code, string)
|
|
int code;
|
|
char *string;
|
|
{
|
|
|
|
reply(code, "%s: %s.", string, strerror(errno));
|
|
}
|
|
|
|
static char *onefile[] = {
|
|
"",
|
|
0
|
|
};
|
|
|
|
void
|
|
send_file_list(whichf)
|
|
char *whichf;
|
|
{
|
|
struct stat st;
|
|
DIR *dirp = NULL;
|
|
struct dirent *dir;
|
|
FILE *dout = NULL;
|
|
char **dirlist, *dirname;
|
|
int simple = 0;
|
|
int freeglob = 0;
|
|
glob_t gl;
|
|
|
|
if (strpbrk(whichf, "~{[*?") != NULL) {
|
|
int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
|
|
|
|
memset(&gl, 0, sizeof(gl));
|
|
freeglob = 1;
|
|
if (glob(whichf, flags, 0, &gl)) {
|
|
reply(550, "not found");
|
|
goto out;
|
|
} else if (gl.gl_pathc == 0) {
|
|
errno = ENOENT;
|
|
perror_reply(550, whichf);
|
|
goto out;
|
|
}
|
|
dirlist = gl.gl_pathv;
|
|
} else {
|
|
onefile[0] = whichf;
|
|
dirlist = onefile;
|
|
simple = 1;
|
|
}
|
|
|
|
if (setjmp(urgcatch)) {
|
|
transflag = 0;
|
|
goto out;
|
|
}
|
|
while ((dirname = *dirlist++)) {
|
|
if (stat(dirname, &st) < 0) {
|
|
/*
|
|
* If user typed "ls -l", etc, and the client
|
|
* used NLST, do what the user meant.
|
|
*/
|
|
if (dirname[0] == '-' && *dirlist == NULL &&
|
|
transflag == 0) {
|
|
retrieve(_PATH_LS " %s", dirname);
|
|
goto out;
|
|
}
|
|
perror_reply(550, whichf);
|
|
if (dout != NULL) {
|
|
(void) fclose(dout);
|
|
transflag = 0;
|
|
data = -1;
|
|
pdata = -1;
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
if (S_ISREG(st.st_mode)) {
|
|
if (dout == NULL) {
|
|
dout = dataconn("file list", (off_t)-1, "w");
|
|
if (dout == NULL)
|
|
goto out;
|
|
transflag++;
|
|
}
|
|
fprintf(dout, "%s%s\n", dirname,
|
|
type == TYPE_A ? "\r" : "");
|
|
byte_count += strlen(dirname) + 1;
|
|
continue;
|
|
} else if (!S_ISDIR(st.st_mode))
|
|
continue;
|
|
|
|
if ((dirp = opendir(dirname)) == NULL)
|
|
continue;
|
|
|
|
while ((dir = readdir(dirp)) != NULL) {
|
|
char nbuf[MAXPATHLEN];
|
|
|
|
if (dir->d_name[0] == '.' && dir->d_namlen == 1)
|
|
continue;
|
|
if (dir->d_name[0] == '.' && dir->d_name[1] == '.' &&
|
|
dir->d_namlen == 2)
|
|
continue;
|
|
|
|
snprintf(nbuf, sizeof(nbuf),
|
|
"%s/%s", dirname, dir->d_name);
|
|
|
|
/*
|
|
* We have to do a stat to insure it's
|
|
* not a directory or special file.
|
|
*/
|
|
if (simple || (stat(nbuf, &st) == 0 &&
|
|
S_ISREG(st.st_mode))) {
|
|
if (dout == NULL) {
|
|
dout = dataconn("file list", (off_t)-1,
|
|
"w");
|
|
if (dout == NULL)
|
|
goto out;
|
|
transflag++;
|
|
}
|
|
if (nbuf[0] == '.' && nbuf[1] == '/')
|
|
fprintf(dout, "%s%s\n", &nbuf[2],
|
|
type == TYPE_A ? "\r" : "");
|
|
else
|
|
fprintf(dout, "%s%s\n", nbuf,
|
|
type == TYPE_A ? "\r" : "");
|
|
byte_count += strlen(nbuf) + 1;
|
|
}
|
|
}
|
|
(void) closedir(dirp);
|
|
}
|
|
|
|
if (dout == NULL)
|
|
reply(550, "No files found.");
|
|
else if (ferror(dout) != 0)
|
|
perror_reply(550, "Data connection");
|
|
else
|
|
reply(226, "Transfer complete.");
|
|
|
|
transflag = 0;
|
|
if (dout != NULL)
|
|
(void) fclose(dout);
|
|
data = -1;
|
|
pdata = -1;
|
|
out:
|
|
if (freeglob) {
|
|
freeglob = 0;
|
|
globfree(&gl);
|
|
}
|
|
}
|
|
|
|
void
|
|
reapchild(signo)
|
|
int signo;
|
|
{
|
|
while (wait3(NULL, WNOHANG, NULL) > 0);
|
|
}
|
|
|
|
#ifdef OLD_SETPROCTITLE
|
|
/*
|
|
* Clobber argv so ps will show what we're doing. (Stolen from sendmail.)
|
|
* Warning, since this is usually started from inetd.conf, it often doesn't
|
|
* have much of an environment or arglist to overwrite.
|
|
*/
|
|
void
|
|
#if __STDC__
|
|
setproctitle(const char *fmt, ...)
|
|
#else
|
|
setproctitle(fmt, va_alist)
|
|
char *fmt;
|
|
va_dcl
|
|
#endif
|
|
{
|
|
int i;
|
|
va_list ap;
|
|
char *p, *bp, ch;
|
|
char buf[LINE_MAX];
|
|
|
|
#if __STDC__
|
|
va_start(ap, fmt);
|
|
#else
|
|
va_start(ap);
|
|
#endif
|
|
(void)vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
|
|
/* make ps print our process name */
|
|
p = Argv[0];
|
|
*p++ = '-';
|
|
|
|
i = strlen(buf);
|
|
if (i > LastArgv - p - 2) {
|
|
i = LastArgv - p - 2;
|
|
buf[i] = '\0';
|
|
}
|
|
bp = buf;
|
|
while (ch = *bp++)
|
|
if (ch != '\n' && ch != '\r')
|
|
*p++ = ch;
|
|
while (p < LastArgv)
|
|
*p++ = ' ';
|
|
}
|
|
#endif /* OLD_SETPROCTITLE */
|
|
|
|
static void
|
|
logxfer(name, size, start)
|
|
char *name;
|
|
long size;
|
|
long start;
|
|
{
|
|
char buf[1024];
|
|
char path[MAXPATHLEN + 1];
|
|
time_t now;
|
|
|
|
if (statfd >= 0 && getwd(path) != NULL) {
|
|
time(&now);
|
|
snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s/%s!%ld!%ld\n",
|
|
ctime(&now)+4, ident, remotehost,
|
|
path, name, size, now - start + (now == start));
|
|
write(statfd, buf, strlen(buf));
|
|
}
|
|
}
|