X-Git-Url: https://git.subgeniuskitty.com/unix-history/.git/blobdiff_plain/930b478bb4216e63bd025bfc4ff49e4e21215dfa..94ae9fcacb9cd5e70c293a55d838de48cda0d985:/usr/src/libexec/ftpd/ftpd.c diff --git a/usr/src/libexec/ftpd/ftpd.c b/usr/src/libexec/ftpd/ftpd.c index 5fda06c30b..09dc72d955 100644 --- a/usr/src/libexec/ftpd/ftpd.c +++ b/usr/src/libexec/ftpd/ftpd.c @@ -1,6 +1,19 @@ +/* + * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * %sccs.include.redist.c% + */ + #ifndef lint -static char sccsid[] = "@(#)ftpd.c 4.26 (Berkeley) %G%"; -#endif +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 */ + +#ifndef lint +static char sccsid[] = "@(#)ftpd.c 8.2 (Berkeley) %G%"; +#endif /* not lint */ /* * FTP server. @@ -9,49 +22,62 @@ static char sccsid[] = "@(#)ftpd.c 4.26 (Berkeley) %G%"; #include #include #include -#include #include #include +#include +#include +#define FTP_NAMES #include #include +#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include + +#include "pathnames.h" +#include "extern.h" + +#if __STDC__ +#include +#else +#include +#endif -/* - * File containing login names - * NOT to be used on this machine. - * Commonly used to disallow uucp. - */ -#define FTPUSERS "/etc/ftpusers" +static char version[] = "Version 6.00"; -extern int errno; -extern char *sys_errlist[]; -extern char *crypt(); -extern char version[]; -extern char *home; /* pointer to home directory for glob */ -extern FILE *popen(), *fopen(); -extern int pclose(), fclose(); +extern off_t restart_point; +extern char cbuf[]; struct sockaddr_in ctrl_addr; struct sockaddr_in data_source; struct sockaddr_in data_dest; struct sockaddr_in his_addr; - -struct hostent *hp; +struct sockaddr_in pasv_addr; int data; -jmp_buf errcatch; +jmp_buf errcatch, urgcatch; int logged_in; struct passwd *pw; int debug; -int timeout; +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 guest; int type; @@ -59,9 +85,18 @@ int form; int stru; /* avoid C keyword */ int mode; int usedefault = 1; /* for data transfers */ -char hostname[32]; -char remotehost[32]; -struct servent *sp; +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[MAXHOSTNAMELEN]; +char remotehost[MAXHOSTNAMELEN]; /* * Timeout intervals for retrying connections @@ -74,317 +109,624 @@ struct servent *sp; int swaitmax = SWAITMAX; int swaitint = SWAITINT; -int lostconn(); -int reapchild(); -FILE *getdatasock(), *dataconn(); +#ifdef SETPROCTITLE +char **Argv = NULL; /* pointer to argument vector */ +char *LastArgv = NULL; /* end of argv */ +char proctitle[LINE_MAX]; /* initial part of title */ +#endif /* SETPROCTITLE */ + +#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); \ + } + +static void ack __P((char *)); +static void myoob __P((int)); +static int checkuser __P((char *)); +static FILE *dataconn __P((char *, off_t, char *)); +static void dolog __P((struct sockaddr_in *)); +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)); +static struct passwd * + sgetpwnam __P((char *)); +static char *sgetsave __P((char *)); + +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); +} -main(argc, argv) +int +main(argc, argv, envp) int argc; char *argv[]; + char **envp; { - int ctrl, s, options = 0; - char *cp; + int addrlen, ch, on = 1, tos; + char *cp, line[LINE_MAX]; + FILE *fd; - sp = getservbyname("ftp", "tcp"); - if (sp == 0) { - fprintf(stderr, "ftpd: ftp/tcp: unknown service\n"); + /* + * 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); + addrlen = sizeof(his_addr); + if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) { + syslog(LOG_ERR, "getpeername (%s): %m",argv[0]); + exit(1); + } + addrlen = sizeof(ctrl_addr); + if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) { + syslog(LOG_ERR, "getsockname (%s): %m",argv[0]); exit(1); } - ctrl_addr.sin_port = sp->s_port; - data_source.sin_port = htons(ntohs(sp->s_port) - 1); - signal(SIGPIPE, lostconn); +#ifdef IP_TOS + tos = IPTOS_LOWDELAY; + if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)) < 0) + syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); +#endif + data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1); debug = 0; - argc--, argv++; - while (argc > 0 && *argv[0] == '-') { - for (cp = &argv[0][1]; *cp; cp++) switch (*cp) { - - case 'v': - debug = 1; - break; - +#ifdef SETPROCTITLE + /* + * Save start and extent of argv for setproctitle. + */ + Argv = argv; + while (*envp) + envp++; + LastArgv = envp[-1] + strlen(envp[-1]); +#endif /* SETPROCTITLE */ + + while ((ch = getopt(argc, argv, "dlt:T:u:v")) != EOF) { + switch (ch) { case 'd': debug = 1; - options |= SO_DEBUG; break; case 'l': - logging = 1; + logging++; /* > 1 == extra logging */ break; case 't': - timeout = atoi(++cp); - goto nextopt; + timeout = atoi(optarg); + if (maxtimeout < timeout) + maxtimeout = timeout; + break; + + case 'T': + maxtimeout = atoi(optarg); + if (timeout > maxtimeout) + timeout = maxtimeout; + 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 'v': + debug = 1; break; default: - fprintf(stderr, "Unknown flag -%c ignored.\n", *cp); + warnx("unknown flag -%c ignored", optopt); break; } -nextopt: - argc--, argv++; } -#ifndef DEBUG - if (fork()) + (void) freopen(_PATH_DEVNULL, "w", stderr); + (void) signal(SIGPIPE, lostconn); + (void) signal(SIGCHLD, SIG_IGN); + if ((int)signal(SIGURG, myoob) < 0) + syslog(LOG_ERR, "signal: %m"); + + /* 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(&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); - for (s = 0; s < 10; s++) - if (!logging || (s != 2)) - (void) close(s); - (void) open("/", O_RDONLY); - (void) dup2(0, 1); - if (!logging) - (void) dup2(0, 2); - { int tt = open("/dev/tty", O_RDWR); - if (tt > 0) { - ioctl(tt, TIOCNOTTY, 0); - close(tt); - } } -#endif - while ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - perror("ftpd: socket"); - sleep(5); - } - if (options & SO_DEBUG) - if (setsockopt(s, SOL_SOCKET, SO_DEBUG, 0, 0) < 0) - perror("ftpd: setsockopt (SO_DEBUG)"); - if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 0, 0) < 0) - perror("ftpd: setsockopt (SO_KEEPALIVE)"); - while (bind(s, &ctrl_addr, sizeof (ctrl_addr), 0) < 0) { - perror("ftpd: bind"); - sleep(5); - } - signal(SIGCHLD, reapchild); - listen(s, 10); - for (;;) { - int hisaddrlen = sizeof (his_addr); - - ctrl = accept(s, &his_addr, &hisaddrlen, 0); - if (ctrl < 0) { - if (errno == EINTR) - continue; - perror("ftpd: accept"); - continue; + if ((fd = fopen(_PATH_FTPWELCOME, "r")) != NULL) { + while (fgets(line, sizeof(line), fd) != NULL) { + if ((cp = strchr(line, '\n')) != NULL) + *cp = '\0'; + lreply(220, "%s", line); } - if (fork() == 0) { - signal (SIGCHLD, SIG_IGN); - dolog(&his_addr); - close(s); - dup2(ctrl, 0), close(ctrl), dup2(0, 1); - /* do telnet option negotiation here */ - /* - * Set up default state - */ - logged_in = 0; - data = -1; - type = TYPE_A; - form = FORM_N; - stru = STRU_F; - mode = MODE_S; - (void) getsockname(0, &ctrl_addr, sizeof (ctrl_addr)); - gethostname(hostname, sizeof (hostname)); - reply(220, "%s FTP server (%s) ready.", - hostname, version); - for (;;) { - setjmp(errcatch); - yyparse(); - } + (void) fflush(stdout); + (void) fclose(fd); + /* reply(220,) must follow */ + } + (void) gethostname(hostname, sizeof(hostname)); + 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); +} + +static char ttyline[20]; + +/* + * 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; + } + end_login(); + } + + guest = 0; + if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) { + if (checkuser("ftp") || checkuser("anonymous")) + reply(530, "User %s access denied.", name); + else if ((pw = sgetpwnam("ftp")) != NULL) { + guest = 1; + askpasswd = 1; + reply(331, + "Guest login ok, type your name as password."); + } else + reply(530, "User %s unknown.", name); + if (!askpasswd && logging) + syslog(LOG_NOTICE, + "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost); + 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(name)) { + 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; } - close(ctrl); } + if (logging) + strncpy(curname, name, sizeof(curname)-1); + reply(331, "Password required for %s.", name); + askpasswd = 1; + /* + * Delay before reading passwd after first failed + * attempt to slow down passwd-guessing programs. + */ + if (login_attempts) + sleep((unsigned) login_attempts); } -reapchild() +/* + * Check if a user is in the file _PATH_FTPUSERS + */ +static int +checkuser(name) + char *name; { - union wait status; + FILE *fd; + int found = 0; + char *p, line[BUFSIZ]; - while (wait3(&status, WNOHANG, 0) > 0) - ; + if ((fd = fopen(_PATH_FTPUSERS, "r")) != NULL) { + while (fgets(line, sizeof(line), fd) != NULL) + if ((p = strchr(line, '\n')) != NULL) { + *p = '\0'; + if (line[0] == '#') + continue; + if (strcmp(p, name) == 0) { + found = 1; + break; + } + } + (void) fclose(fd); + } + return (found); } -lostconn() +/* + * Terminate login as previous user, if any, resetting state; + * used when USER command is given or login fails. + */ +static void +end_login() { - if (debug) - fprintf(stderr, "Lost connection.\n"); - dologout(-1); + (void) seteuid((uid_t)0); + if (logged_in) + logwtmp(ttyline, "", ""); + pw = NULL; + logged_in = 0; + guest = 0; } +void pass(passwd) char *passwd; { - char *xpasswd, *savestr(); - static struct passwd save; + char *salt, *xpasswd; + FILE *fd; - if (logged_in || pw == NULL) { + if (logged_in || askpasswd == 0) { reply(503, "Login with USER first."); return; } + askpasswd = 0; if (!guest) { /* "ftp" is only account allowed no password */ - xpasswd = crypt(passwd, pw->pw_passwd); - if (strcmp(xpasswd, pw->pw_passwd) != 0) { + if (pw == NULL) + salt = "xx"; + else + salt = pw->pw_passwd; + xpasswd = crypt(passwd, salt); + /* The strcmp does not catch null passwords! */ + if (pw == NULL || *pw->pw_passwd == '\0' || + strcmp(xpasswd, pw->pw_passwd)) { 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; } } - setegid(pw->pw_gid); - initgroups(pw->pw_name, pw->pw_gid); - if (chdir(pw->pw_dir)) { - reply(550, "User %s: can't change directory to $s.", - pw->pw_name, pw->pw_dir); - goto bad; + login_attempts = 0; /* this time successful */ + if (setegid((gid_t)pw->pw_gid) < 0) { + reply(550, "Can't set gid."); + return; + } + (void) initgroups(pw->pw_name, pw->pw_gid); + + /* open wtmp before chroot */ + (void)sprintf(ttyline, "ftp%d", getpid()); + logwtmp(ttyline, pw->pw_name, remotehost); + logged_in = 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 (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 (guest && chroot(pw->pw_dir) < 0) { - reply(550, "Can't set guest privileges."); + if (seteuid((uid_t)pw->pw_uid) < 0) { + reply(550, "Can't set uid."); goto bad; } - if (!guest) - reply(230, "User %s logged in.", pw->pw_name); - else - reply(230, "Guest login ok, access restrictions apply."); - logged_in = 1; - dologin(pw); - seteuid(pw->pw_uid); /* - * Save everything so globbing doesn't - * clobber the fields. + * Display a login message, if it exists. + * N.B. reply(230,) must follow the message. */ - save = *pw; - save.pw_name = savestr(pw->pw_name); - save.pw_passwd = savestr(pw->pw_passwd); - save.pw_comment = savestr(pw->pw_comment); - save.pw_gecos = savestr(pw->pw_gecos, &save.pw_gecos); - save.pw_dir = savestr(pw->pw_dir); - save.pw_shell = savestr(pw->pw_shell); - pw = &save; - home = pw->pw_dir; /* home dir for globbing */ + if ((fd = fopen(_PATH_FTPLOGINMESG, "r")) != NULL) { + 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) { + reply(230, "Guest login ok, access restrictions apply."); +#ifdef SETPROCTITLE + snprintf(proctitle, sizeof(proctitle), + "%s: anonymous/%.*s", remotehost, + sizeof(proctitle) - sizeof(remotehost) - + sizeof(": anonymous/"), passwd); + setproctitle(proctitle); +#endif /* SETPROCTITLE */ + if (logging) + syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s", + remotehost, passwd); + } else { + reply(230, "User %s logged in.", pw->pw_name); +#ifdef SETPROCTITLE + snprintf(proctitle, sizeof(proctitle), + "%s: %s", remotehost, pw->pw_name); + setproctitle(proctitle); +#endif /* SETPROCTITLE */ + if (logging) + syslog(LOG_INFO, "FTP LOGIN FROM %s as %s", + remotehost, pw->pw_name); + } + (void) umask(defumask); return; bad: - seteuid(0); - pw = NULL; -} - -char * -savestr(s) - char *s; -{ - char *malloc(); - char *new = malloc(strlen(s) + 1); - - if (new != NULL) - strcpy(new, s); - return (new); + /* Forget all about it... */ + end_login(); } +void retrieve(cmd, name) char *cmd, *name; { FILE *fin, *dout; struct stat st; - int (*closefunc)(); + int (*closefunc) __P((FILE *)); if (cmd == 0) { -#ifdef notdef - /* no remote command execution -- it's a security hole */ - if (*name == '|') - fin = popen(name + 1, "r"), closefunc = pclose; - else -#endif - fin = fopen(name, "r"), closefunc = fclose; + fin = fopen(name, "r"), closefunc = fclose; + st.st_size = 0; } else { char line[BUFSIZ]; - sprintf(line, cmd, name), name = line; - fin = popen(line, "r"), closefunc = pclose; + (void) sprintf(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) - reply(550, "%s: %s.", name, sys_errlist[errno]); + if (errno != 0) { + perror_reply(550, name); + if (cmd == 0) { + LOGCMD("get", name); + } + } return; } - st.st_size = 0; - if (cmd == 0 && - (stat(name, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) { + 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; } dout = dataconn(name, st.st_size, "w"); if (dout == NULL) goto done; - if (send_data(fin, dout) || ferror(dout)) - reply(550, "%s: %s.", name, sys_errlist[errno]); - else - reply(226, "Transfer complete."); - fclose(dout), data = -1; + send_data(fin, dout, st.st_blksize); + (void) fclose(dout); + data = -1; + pdata = -1; done: + if (cmd == 0) + LOGBYTES("get", name, byte_count); (*closefunc)(fin); } -store(name, mode) +void +store(name, mode, unique) char *name, *mode; + int unique; { FILE *fout, *din; - int (*closefunc)(), dochown = 0; - -#ifdef notdef - /* no remote command execution -- it's a security hole */ - if (name[0] == '|') - fout = popen(&name[1], "w"), closefunc = pclose; - else -#endif - { - struct stat st; + struct stat st; + int (*closefunc) __P((FILE *)); - if (stat(name, &st) < 0) - dochown++; - fout = fopen(name, mode), closefunc = fclose; + if (unique && stat(name, &st) == 0 && + (name = gunique(name)) == NULL) { + LOGCMD(*mode == 'w' ? "put" : "append", name); + return; } + + fout = fopen(name, mode); + closefunc = fclose; if (fout == NULL) { - reply(550, "%s: %s.", name, sys_errlist[errno]); + perror_reply(553, name); + LOGCMD(*mode == 'w' ? "put" : "append", name); return; } din = dataconn(name, (off_t)-1, "r"); if (din == NULL) goto done; - if (receive_data(din, fout) || ferror(fout)) - reply(550, "%s: %s.", name, sys_errlist[errno]); - else - reply(226, "Transfer complete."); - fclose(din), data = -1; + 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: - if (dochown) - (void) chown(name, pw->pw_uid, -1); + LOGBYTES(*mode == 'w' ? "put" : "append", name, byte_count); (*closefunc)(fout); } -FILE * +static FILE * getdatasock(mode) char *mode; { - int s; + int on = 1, s, t, tries; if (data >= 0) return (fdopen(data, mode)); + (void) seteuid((uid_t)0); s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) - return (NULL); - seteuid(0); - if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 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.sin_family = AF_INET; data_source.sin_addr = ctrl_addr.sin_addr; - if (bind(s, &data_source, sizeof (data_source), 0) < 0) - goto bad; - seteuid(pw->pw_uid); + for (tries = 1; ; tries++) { + if (bind(s, (struct sockaddr *)&data_source, + sizeof(data_source)) >= 0) + break; + if (errno != EADDRINUSE || tries > 10) + goto bad; + sleep(tries); + } + (void) seteuid((uid_t)pw->pw_uid); +#ifdef IP_TOS + on = IPTOS_THROUGHPUT; + if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int)) < 0) + syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); +#endif return (fdopen(s, mode)); bad: - seteuid(pw->pw_uid); - close(s); + /* 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); } -FILE * +static FILE * dataconn(name, size, mode) char *name; off_t size; @@ -392,14 +734,38 @@ dataconn(name, size, mode) { char sizebuf[32]; FILE *file; - int retry = 0; + int retry = 0, tos; - if (size >= 0) - sprintf (sizebuf, " (%ld bytes)", size); + file_size = size; + byte_count = 0; + if (size != (off_t) -1) + (void) sprintf(sizebuf, " (%qd bytes)", size); else (void) strcpy(sizebuf, ""); + if (pdata >= 0) { + struct sockaddr_in from; + int s, fromlen = sizeof(from); + + s = accept(pdata, (struct sockaddr *)&from, &fromlen); + if (s < 0) { + reply(425, "Can't open data connection."); + (void) close(pdata); + pdata = -1; + return (NULL); + } + (void) close(pdata); + pdata = s; +#ifdef IP_TOS + tos = IPTOS_LOWDELAY; + (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.", + reply(125, "Using existing data connection for '%s'%s.", name, sizebuf); usedefault = 1; return (fdopen(data, mode)); @@ -411,260 +777,433 @@ dataconn(name, size, mode) if (file == NULL) { reply(425, "Can't create data socket (%s,%d): %s.", inet_ntoa(data_source.sin_addr), - ntohs(data_source.sin_port), - sys_errlist[errno]); + ntohs(data_source.sin_port), strerror(errno)); return (NULL); } - reply(150, "Opening data connection for %s (%s,%d)%s.", - name, inet_ntoa(data_dest.sin_addr.s_addr), - ntohs(data_dest.sin_port), sizebuf); data = fileno(file); - while (connect(data, &data_dest, sizeof (data_dest), 0) < 0) { + while (connect(data, (struct sockaddr *)&data_dest, + sizeof(data_dest)) < 0) { if (errno == EADDRINUSE && retry < swaitmax) { - sleep(swaitint); + sleep((unsigned) swaitint); retry += swaitint; continue; } - reply(425, "Can't build data connection: %s.", - sys_errlist[errno]); + 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 - * encapulation of the date subject - * to Mode, Structure, and Type. + * 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. */ -send_data(instr, outstr) +static void +send_data(instr, outstr, blksize) FILE *instr, *outstr; + off_t blksize; { - register int c; - int netfd, filefd, cnt; - char buf[BUFSIZ]; + int c, cnt, filefd, netfd; + char *buf; + 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)) - return (1); - putc('\r', outstr); + if (ferror(outstr)) + goto data_err; + (void) putc('\r', outstr); } - putc(c, outstr); - if (c == '\r') - putc ('\0', outstr); + (void) putc(c, outstr); } - if (ferror (instr) || ferror (outstr)) - return (1); - return (0); - + 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: + if ((buf = malloc((u_int)blksize)) == NULL) { + transflag = 0; + perror_reply(451, "Local resource failure: malloc"); + return; + } netfd = fileno(outstr); filefd = fileno(instr); - - while ((cnt = read(filefd, buf, sizeof (buf))) > 0) - if (write(netfd, buf, cnt) < 0) - return (1); - return (cnt < 0); + 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; } - reply(504,"Unimplemented TYPE %d in send_data", type); - return (1); + +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. + * 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; { - register int c; - int cnt; + int c; + int cnt, bare_lfs = 0; char buf[BUFSIZ]; - + transflag++; + if (setjmp(urgcatch)) { + transflag = 0; + return (-1); + } switch (type) { case TYPE_I: case TYPE_L: - while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0) - if (write(fileno(outstr), buf, cnt) < 0) - return (1); - return (cnt < 0); + 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(504, "TYPE E not implemented."); - return (1); + reply(553, "TYPE E not implemented."); + transflag = 0; + return (-1); case TYPE_A: while ((c = getc(instr)) != EOF) { - if (c == '\r') { - if (ferror (outstr)) - return (1); - if ((c = getc(instr)) != '\n') - putc ('\r', outstr); - if (c == '\0') - continue; + 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; + } } - putc (c, outstr); + (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(230, "WARNING! %d bare linefeeds received in ASCII mode", bare_lfs); + printf(" File may not have transferred correctly.\r\n"); } - if (ferror (instr) || ferror (outstr)) - return (1); return (0); + default: + reply(550, "Unimplemented TYPE %d in receive_data", type); + transflag = 0; + return (-1); } - fatal("Unknown type in receive_data."); - /*NOTREACHED*/ + +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), "/bin/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() +{ + struct sockaddr_in *sin; + u_char *a, *p; + + lreply(211, "%s FTP server status:", hostname, version); + printf(" %s\r\n", version); + printf(" Connected to %s", remotehost); + if (!isdigit(remotehost[0])) + printf(" (%s)", inet_ntoa(his_addr.sin_addr)); + 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) { + printf(" in Passive mode"); + sin = &pasv_addr; + goto printaddr; + } else if (usedefault == 0) { + printf(" PORT"); + sin = &data_dest; +printaddr: + a = (u_char *) &sin->sin_addr; + p = (u_char *) &sin->sin_port; +#define UC(b) (((int) b) & 0xff) + printf(" (%d,%d,%d,%d,%d,%d)\r\n", UC(a[0]), + UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1])); +#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 */ } -reply(n, s, args) +void +#if __STDC__ +reply(int n, const char *fmt, ...) +#else +reply(n, fmt, va_alist) int n; - char *s; + char *fmt; + va_dcl +#endif { - - printf("%d ", n); - _doprnt(s, &args, stdout); - printf("\r\n"); - fflush(stdout); + 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) { - fprintf(stderr, "<--- %d ", n); - _doprnt(s, &args, stderr); - fprintf(stderr, "\n"); - fflush(stderr); + syslog(LOG_DEBUG, "<--- %d ", n); + vsyslog(LOG_DEBUG, fmt, ap); } } -lreply(n, s, args) +void +#if __STDC__ +lreply(int n, const char *fmt, ...) +#else +lreply(n, fmt, va_alist) int n; - char *s; + char *fmt; + va_dcl +#endif { - printf("%d-", n); - _doprnt(s, &args, stdout); - printf("\r\n"); - fflush(stdout); + 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) { - fprintf(stderr, "<--- %d-", n); - _doprnt(s, &args, stderr); - fprintf(stderr, "\n"); + syslog(LOG_DEBUG, "<--- %d- ", n); + vsyslog(LOG_DEBUG, fmt, ap); } } -replystr(s) - char *s; -{ - printf("%s\r\n", s); - fflush(stdout); - if (debug) - fprintf(stderr, "<--- %s\n", s); -} - +static void ack(s) char *s; { - reply(200, "%s command okay.", s); + + reply(250, "%s command successful.", s); } +void nack(s) char *s; { + reply(502, "%s command not implemented.", s); } -yyerror() +/* ARGSUSED */ +void +yyerror(s) + char *s; { - reply(500, "Command not understood."); + 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) { - reply(550, "%s: %s.", name, sys_errlist[errno]); + perror_reply(550, name); return; } if ((st.st_mode&S_IFMT) == S_IFDIR) { if (rmdir(name) < 0) { - reply(550, "%s: %s.", name, sys_errlist[errno]); + perror_reply(550, name); return; } goto done; } if (unlink(name) < 0) { - reply(550, "%s: %s.", name, sys_errlist[errno]); + perror_reply(550, name); return; } done: ack("DELE"); } +void cwd(path) char *path; { - if (chdir(path) < 0) { - reply(550, "%s: %s.", path, sys_errlist[errno]); - return; - } - ack("CWD"); + if (chdir(path) < 0) + perror_reply(550, path); + else + ack("CWD"); } +void makedir(name) char *name; { - struct stat st; - int dochown = stat(name, &st) < 0; - - if (mkdir(name, 0777) < 0) { - reply(550, "%s: %s.", name, sys_errlist[errno]); - return; - } - if (dochown) - (void) chown(name, pw->pw_uid, -1); - ack("MKDIR"); + + LOGCMD("mkdir", name); + if (mkdir(name, 0777) < 0) + perror_reply(550, name); + else + reply(257, "MKD command successful."); } +void removedir(name) char *name; { - if (rmdir(name) < 0) { - reply(550, "%s: %s.", name, sys_errlist[errno]); - return; - } - ack("RMDIR"); + LOGCMD("rmdir", name); + if (rmdir(name) < 0) + perror_reply(550, name); + else + ack("RMD"); } +void pwd() { char path[MAXPATHLEN + 1]; - if (getwd(path) == NULL) { - reply(451, "%s.", path); - return; - } - reply(251, "\"%s\" is current directory.", path); + if (getwd(path) == (char *)NULL) + reply(550, "%s.", path); + else + reply(257, "\"%s\" is current directory.", path); } char * @@ -674,223 +1213,361 @@ renamefrom(name) struct stat st; if (stat(name, &st) < 0) { - reply(550, "%s: %s.", name, sys_errlist[errno]); + perror_reply(550, name); return ((char *)0); } reply(350, "File exists, ready for destination name"); return (name); } +void renamecmd(from, to) char *from, *to; { - if (rename(from, to) < 0) { - reply(550, "rename: %s.", sys_errlist[errno]); - return; - } - ack("RNTO"); + LOGCMD2("rename", from, to); + if (rename(from, to) < 0) + perror_reply(550, "rename"); + else + ack("RNTO"); } +static void dolog(sin) struct sockaddr_in *sin; { - struct hostent *hp = gethostbyaddr(&sin->sin_addr, - sizeof (struct in_addr), AF_INET); - time_t t; + struct hostent *hp = gethostbyaddr((char *)&sin->sin_addr, + sizeof(struct in_addr), AF_INET); - if (hp) { - strncpy(remotehost, hp->h_name, sizeof (remotehost)); - endhostent(); - } else - strncpy(remotehost, inet_ntoa(sin->sin_addr), - sizeof (remotehost)); - if (!logging) - return; - t = time(0); - fprintf(stderr,"FTPD: connection from %s at %s", remotehost, ctime(&t)); - fflush(stderr); + if (hp) + (void) strncpy(remotehost, hp->h_name, sizeof(remotehost)); + else + (void) strncpy(remotehost, inet_ntoa(sin->sin_addr), + sizeof(remotehost)); +#ifdef SETPROCTITLE + snprintf(proctitle, sizeof(proctitle), "%s: connected", remotehost); + setproctitle(proctitle); +#endif /* SETPROCTITLE */ + + if (logging) + syslog(LOG_INFO, "connection from %s", remotehost); } -#include - -#define SCPYN(a, b) strncpy(a, b, sizeof (a)) -struct utmp utmp; - /* - * Record login in wtmp file. + * Record logout in wtmp file + * and exit with supplied status. */ -dologin(pw) - struct passwd *pw; +void +dologout(status) + int status; { - int wtmp; - char line[32]; - wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND); - if (wtmp >= 0) { - /* hack, but must be unique and no tty line */ - sprintf(line, "ftp%d", getpid()); - SCPYN(utmp.ut_line, line); - SCPYN(utmp.ut_name, pw->pw_name); - SCPYN(utmp.ut_host, remotehost); - utmp.ut_time = time(0); - (void) write(wtmp, (char *)&utmp, sizeof (utmp)); - (void) close(wtmp); + if (logged_in) { + (void) seteuid((uid_t)0); + 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) { + 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); } } /* - * Record logout in wtmp file - * and exit with supplied status. + * 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. */ -dologout(status) - int status; +void +passive() { - int wtmp; + int len; + char *p, *a; - if (!logged_in) + pdata = socket(AF_INET, SOCK_STREAM, 0); + if (pdata < 0) { + perror_reply(425, "Can't open passive connection"); return; - seteuid(0); - wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND); - if (wtmp >= 0) { - SCPYN(utmp.ut_name, ""); - SCPYN(utmp.ut_host, ""); - utmp.ut_time = time(0); - (void) write(wtmp, (char *)&utmp, sizeof (utmp)); - (void) close(wtmp); } - /* beware of flushing buffers after a SIGPIPE */ - _exit(status); + pasv_addr = ctrl_addr; + pasv_addr.sin_port = 0; + (void) seteuid((uid_t)0); + if (bind(pdata, (struct sockaddr *)&pasv_addr, sizeof(pasv_addr)) < 0) { + (void) seteuid((uid_t)pw->pw_uid); + 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; + a = (char *) &pasv_addr.sin_addr; + p = (char *) &pasv_addr.sin_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) close(pdata); + pdata = -1; + perror_reply(425, "Can't open passive connection"); + return; } /* - * Special version of popen which avoids - * call to shell. This insures noone may - * create a pipe to a hidden program as a side - * effect of a list or dir command. + * Generate unique name for file with basename "local". + * The file named "local" is already known to exist. + * Generates failure reply on error. */ -#define tst(a,b) (*mode == 'r'? (b) : (a)) -#define RDR 0 -#define WTR 1 -static int popen_pid[5]; - static char * -nextarg(cpp) - char *cpp; +gunique(local) + char *local; { - register char *cp = cpp; + static char new[MAXPATHLEN]; + struct stat st; + int count; + char *cp; - if (cp == 0) - return (cp); - while (*cp && *cp != ' ' && *cp != '\t') - cp++; - if (*cp == ' ' || *cp == '\t') { - *cp++ = '\0'; - while (*cp == ' ' || *cp == '\t') - cp++; + cp = strrchr(local, '/'); + if (cp) + *cp = '\0'; + if (stat(cp ? local : ".", &st) < 0) { + perror_reply(553, cp ? local : "."); + return ((char *) 0); } - if (cp == cpp) - return ((char *)0); - return (cp); + if (cp) + *cp = '/'; + (void) strcpy(new, 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); } -FILE * -popen(cmd, mode) - char *cmd, *mode; +/* + * Format and send reply containing system error number. + */ +void +perror_reply(code, string) + int code; + char *string; { - int p[2], ac, gac; - register myside, hisside, pid; - char *av[20], *gav[512]; - register char *cp; - if (pipe(p) < 0) - return (NULL); - cp = cmd, ac = 0; - /* break up string into pieces */ - do { - av[ac++] = cp; - cp = nextarg(cp); - } while (cp && *cp && ac < 20); - av[ac] = (char *)0; - gav[0] = av[0]; - /* glob each piece */ - for (gac = ac = 1; av[ac] != NULL; ac++) { - char **pop; - extern char **glob(); - - pop = glob(av[ac]); - if (pop) { - av[ac] = (char *)pop; /* save to free later */ - while (*pop && gac < 512) - gav[gac++] = *pop++; - } - } - gav[gac] = (char *)0; - myside = tst(p[WTR], p[RDR]); - hisside = tst(p[RDR], p[WTR]); - if ((pid = fork()) == 0) { - /* myside and hisside reverse roles in child */ - close(myside); - dup2(hisside, tst(0, 1)); - close(hisside); - execv(gav[0], gav); - _exit(1); - } - for (ac = 1; av[ac] != NULL; ac++) - blkfree((char **)av[ac]); - if (pid == -1) - return (NULL); - popen_pid[myside] = pid; - close(hisside); - return (fdopen(myside, mode)); + reply(code, "%s: %s.", string, strerror(errno)); } -pclose(ptr) - FILE *ptr; +static char *onefile[] = { + "", + 0 +}; + +void +send_file_list(whichf) + char *whichf; { - register f, r, (*hstat)(), (*istat)(), (*qstat)(); - int status; + 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) { + + memset(&gl, 0, sizeof(gl)); + freeglob = 1; + if (glob(whichf, GLOB_BRACE|GLOB_QUOTE|GLOB_TILDE, 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("/bin/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; + + sprintf(nbuf, "%s/%s", dirname, dir->d_name); - f = fileno(ptr); - fclose(ptr); - istat = signal(SIGINT, SIG_IGN); - qstat = signal(SIGQUIT, SIG_IGN); - hstat = signal(SIGHUP, SIG_IGN); - while ((r = wait(&status)) != popen_pid[f] && r != -1) - ; - if (r == -1) - status = -1; - signal(SIGINT, istat); - signal(SIGQUIT, qstat); - signal(SIGHUP, hstat); - return (status); + /* + * 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); + } } +#ifdef SETPROCTITLE /* - * Check user requesting login priviledges. - * Disallow anyone mentioned in the file FTPUSERS - * to allow people such as uucp to be avoided. + * 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. */ -checkuser(name) - register char *name; +void +#if __STDC__ +setproctitle(const char *fmt, ...) +#else +setproctitle(fmt, va_alist) + char *fmt; + va_dcl +#endif { - char line[BUFSIZ], *index(); - FILE *fd; - int found = 0; + 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); - fd = fopen(FTPUSERS, "r"); - if (fd == NULL) - return (1); - while (fgets(line, sizeof (line), fd) != NULL) { - register char *cp = index(line, '\n'); + /* make ps print our process name */ + p = Argv[0]; + *p++ = '-'; - if (cp) - *cp = '\0'; - if (strcmp(line, name) == 0) { - found++; - break; - } + i = strlen(buf); + if (i > LastArgv - p - 2) { + i = LastArgv - p - 2; + buf[i] = '\0'; } - fclose(fd); - return (!found); + bp = buf; + while (ch = *bp++) + if (ch != '\n' && ch != '\r') + *p++ = ch; + while (p < LastArgv) + *p++ = ' '; } +#endif /* SETPROCTITLE */