/*
- * Copyright (c) 1985 Regents of the University of California.
- * All rights reserved. The Berkeley software License Agreement
- * specifies the terms and conditions for redistribution.
+ * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * %sccs.include.redist.c%
*/
#ifndef lint
-char copyright[] =
-"@(#) Copyright (c) 1985 Regents of the University of California.\n\
- All rights reserved.\n";
-#endif not 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 */
#ifndef lint
-static char sccsid[] = "@(#)ftpd.c 5.9 (Berkeley) %G%";
-#endif not lint
+static char sccsid[] = "@(#)ftpd.c 8.2 (Berkeley) %G%";
+#endif /* not lint */
/*
* FTP server.
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
-#include <sys/file.h>
#include <sys/wait.h>
#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#define FTP_NAMES
#include <arpa/ftp.h>
#include <arpa/inet.h>
#include <arpa/telnet.h>
-#include <stdio.h>
-#include <signal.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 <setjmp.h>
-#include <netdb.h>
-#include <errno.h>
-#include <strings.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
-/*
- * File containing login names
- * NOT to be used on this machine.
- * Commonly used to disallow uucp.
- */
-#define FTPUSERS "/etc/ftpusers"
-
-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(), *freopen();
-extern int pclose(), fclose();
-extern char *getline();
+#include "pathnames.h"
+#include "extern.h"
+
+#if __STDC__
+#include <stdarg.h>
+#else
+#include <varargs.h>
+#endif
+
+static char version[] = "Version 6.00";
+
+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 sockaddr_in pasv_addr;
int data;
jmp_buf errcatch, urgcatch;
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 guest;
-int wtmp;
int type;
int form;
int stru; /* avoid C keyword */
int mode;
int usedefault = 1; /* for data transfers */
-int pdata; /* for passive mode */
-int unique;
-int transflag;
+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[32];
-char remotehost[32];
+char hostname[MAXHOSTNAMELEN];
+char remotehost[MAXHOSTNAMELEN];
/*
* Timeout intervals for retrying connections
int swaitmax = SWAITMAX;
int swaitint = SWAITINT;
-int lostconn();
-int myoob();
-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 addrlen, on = 1;
- long pgid;
- char *cp;
+ int addrlen, ch, on = 1, tos;
+ char *cp, line[LINE_MAX];
+ FILE *fd;
- addrlen = sizeof (his_addr);
- if (getpeername(0, &his_addr, &addrlen) < 0) {
+ /*
+ * 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, (char *) &ctrl_addr, &addrlen) < 0) {
+ addrlen = sizeof(ctrl_addr);
+ if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
exit(1);
}
+#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;
- openlog("ftpd", LOG_PID, LOG_DAEMON);
- 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;
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, "ftpd: Unknown flag -%c ignored.\n",
- *cp);
+ warnx("unknown flag -%c ignored", optopt);
break;
}
-nextopt:
- argc--, argv++;
}
- (void) freopen("/dev/null", "w", stderr);
+ (void) freopen(_PATH_DEVNULL, "w", stderr);
(void) signal(SIGPIPE, lostconn);
(void) signal(SIGCHLD, SIG_IGN);
- if (signal(SIGURG, myoob) < 0) {
+ if ((int)signal(SIGURG, myoob) < 0)
syslog(LOG_ERR, "signal: %m");
- }
- /* handle urgent data inline */
+
+ /* Try to handle urgent data inline */
#ifdef SO_OOBINLINE
- if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0) {
+ if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0)
syslog(LOG_ERR, "setsockopt: %m");
- }
-#endif SO_OOBINLINE
- pgid = getpid();
- if (ioctl(fileno(stdin), SIOCSPGRP, (char *) &pgid) < 0) {
- syslog(LOG_ERR, "ioctl: %m");
- }
+#endif
+
+#ifdef F_SETOWN
+ if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
+ syslog(LOG_ERR, "fcntl F_SETOWN: %m");
+#endif
dolog(&his_addr);
- /* 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;
tmpline[0] = '\0';
- (void) gethostname(hostname, sizeof (hostname));
- reply(220, "%s FTP server (%s) ready.",
- hostname, version);
- for (;;) {
- (void) setjmp(errcatch);
- (void) yyparse();
+
+ /* 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);
}
+ 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);
+ }
+ (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 */
}
-lostconn()
+static void
+lostconn(signo)
+ int signo;
{
if (debug)
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;
+ }
+ }
+ 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);
+}
+
+/*
+ * Check if a user is in the file _PATH_FTPUSERS
+ */
+static int
+checkuser(name)
+ char *name;
+{
+ FILE *fd;
+ int found = 0;
+ char *p, line[BUFSIZ];
+
+ 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);
+}
+
+/*
+ * 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)
+ 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 (pw == NULL)
+ salt = "xx";
+ else
+ salt = pw->pw_passwd;
+ xpasswd = crypt(passwd, salt);
/* The strcmp does not catch null passwords! */
- if (*pw->pw_passwd == '\0' || strcmp(xpasswd, pw->pw_passwd)) {
+ 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(530, "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;
- /* grab wtmp before chroot */
- wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND);
- if (guest && chroot(pw->pw_dir) < 0) {
- reply(550, "Can't set guest privileges.");
- if (wtmp >= 0) {
- (void) close(wtmp);
- wtmp = -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 (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_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((unsigned) strlen(s) + 1);
-
- if (new != NULL)
- (void) 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)(), tmp;
+ 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];
(void) sprintf(line, cmd, name), name = line;
- fin = popen(line, "r"), closefunc = pclose;
+ 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 ((tmp = send_data(fin, dout)) > 0 || ferror(dout) > 0) {
- reply(550, "%s: %s.", name, sys_errlist[errno]);
- }
- else if (tmp == 0) {
- reply(226, "Transfer complete.");
- }
+ 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, tmp;
- char *gunique(), *local;
-
-#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 *));
- local = name;
- if (stat(name, &st) < 0) {
- dochown++;
- }
- else if (unique) {
- if ((local = gunique(name)) == NULL) {
- return;
- }
- dochown++;
- }
- fout = fopen(local, 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(553, "%s: %s.", local, sys_errlist[errno]);
+ perror_reply(553, name);
+ LOGCMD(*mode == 'w' ? "put" : "append", name);
return;
}
- din = dataconn(local, (off_t)-1, "r");
+ din = dataconn(name, (off_t)-1, "r");
if (din == NULL)
goto done;
- if ((tmp = receive_data(din, fout)) > 0 || ferror(fout) > 0) {
- reply(552, "%s: %s.", local, sys_errlist[errno]);
- }
- else if (tmp == 0 && !unique) {
- reply(226, "Transfer complete.");
- }
- else if (tmp == 0 && unique) {
- reply(226, "Transfer complete (unique file name:%s).", local);
+ 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(local, pw->pw_uid, -1);
+ LOGBYTES(*mode == 'w' ? "put" : "append", name, byte_count);
(*closefunc)(fout);
}
-FILE *
+static FILE *
getdatasock(mode)
char *mode;
{
- int s, on = 1;
+ 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, (char *) &on, sizeof (on)) < 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)
- 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);
+ /* 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;
{
char sizebuf[32];
FILE *file;
- int retry = 0;
+ int retry = 0, tos;
- if (size >= 0)
- (void) 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) {
+ if (pdata >= 0) {
struct sockaddr_in from;
int s, fromlen = sizeof(from);
- s = accept(pdata, &from, &fromlen);
+ s = accept(pdata, (struct sockaddr *)&from, &fromlen);
if (s < 0) {
reply(425, "Can't open data connection.");
(void) close(pdata);
pdata = -1;
- return(NULL);
+ return (NULL);
}
(void) close(pdata);
pdata = s;
- reply(150, "Openning data connection for %s (%s,%d)%s.",
- name, inet_ntoa(from.sin_addr),
- ntohs(from.sin_port), sizebuf);
- return(fdopen(pdata, mode));
+#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));
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);
}
data = fileno(file);
- while (connect(data, &data_dest, sizeof (data_dest)) < 0) {
+ while (connect(data, (struct sockaddr *)&data_dest,
+ sizeof(data_dest)) < 0) {
if (errno == EADDRINUSE && retry < swaitmax) {
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 data connection for %s (%s,%d)%s.",
- name, inet_ntoa(data_dest.sin_addr),
- ntohs(data_dest.sin_port), sizebuf);
+ 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(-1);
+ return;
}
switch (type) {
case TYPE_A:
while ((c = getc(instr)) != EOF) {
+ byte_count++;
if (c == '\n') {
- if (ferror (outstr)) {
- transflag = 0;
- return (1);
- }
+ if (ferror(outstr))
+ goto data_err;
(void) putc('\r', outstr);
}
(void) putc(c, outstr);
- /* if (c == '\r') */
- /* putc ('\0', outstr); */
}
+ fflush(outstr);
transflag = 0;
- if (ferror (instr) || ferror (outstr)) {
- return (1);
- }
- return (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) {
- transflag = 0;
- return (1);
- }
+ 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;
- return (cnt < 0);
+ reply(550, "Unimplemented TYPE %d in send_data", type);
+ return;
}
- reply(550, "Unimplemented TYPE %d in send_data", type);
+
+data_err:
transflag = 0;
- return (-1);
+ 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);
+ 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) {
- transflag = 0;
- return (1);
- }
+ 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 (cnt < 0);
+ return (0);
case TYPE_E:
reply(553, "TYPE E not implemented.");
case TYPE_A:
while ((c = getc(instr)) != EOF) {
+ byte_count++;
+ if (c == '\n')
+ bare_lfs++;
while (c == '\r') {
- if (ferror (outstr)) {
- transflag = 0;
- return (1);
- }
- if ((c = getc(instr)) != '\n')
+ if (ferror(outstr))
+ goto data_err;
+ if ((c = getc(instr)) != '\n') {
(void) putc ('\r', outstr);
- /* if (c == '\0') */
- /* continue; */
+ if (c == '\0' || c == EOF)
+ goto contin2;
+ }
}
- (void) 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 (ferror (instr) || ferror (outstr))
- return (1);
+ if (bare_lfs) {
+ lreply(230, "WARNING! %d bare linefeeds received in ASCII mode", bare_lfs);
+ 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;
- fatal("Unknown type in receive_data.");
- /*NOTREACHED*/
+ 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 */
}
-/*VARARGS2*/
-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");
- (void) 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) {
syslog(LOG_DEBUG, "<--- %d ", n);
- syslog(LOG_DEBUG, s, &args);
+ vsyslog(LOG_DEBUG, fmt, ap);
}
}
-/*VARARGS2*/
-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");
- (void) 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) {
syslog(LOG_DEBUG, "<--- %d- ", n);
- syslog(LOG_DEBUG, s, &args);
+ 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;
- cp = index(cbuf,'\n');
- *cp = '\0';
- reply(500, "'%s': command not understood.",cbuf);
+ 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);
- reply(257, "MKD command successful.");
+
+ 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("RMD");
+ LOGCMD("rmdir", name);
+ if (rmdir(name) < 0)
+ perror_reply(550, name);
+ else
+ ack("RMD");
}
+void
pwd()
{
char path[MAXPATHLEN + 1];
- if (getwd(path) == NULL) {
+ if (getwd(path) == (char *)NULL)
reply(550, "%s.", path);
- return;
- }
- reply(257, "\"%s\" is current directory.", path);
+ else
+ reply(257, "\"%s\" is current directory.", path);
}
char *
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;
- extern char *ctime();
-
- if (hp) {
- (void) strncpy(remotehost, hp->h_name, sizeof (remotehost));
- endhostent();
- } else
- (void) strncpy(remotehost, inet_ntoa(sin->sin_addr),
- sizeof (remotehost));
- if (!logging)
- return;
- t = time((time_t *) 0);
- syslog(LOG_INFO,"FTPD: connection from %s at %s", remotehost, ctime(&t));
-}
-
-#include <utmp.h>
-
-#define SCPYN(a, b) (void) strncpy(a, b, sizeof (a))
-struct utmp utmp;
+ struct hostent *hp = gethostbyaddr((char *)&sin->sin_addr,
+ sizeof(struct in_addr), AF_INET);
-/*
- * Record login in wtmp file.
- */
-dologin(pw)
- struct passwd *pw;
-{
- char line[32];
-
- if (wtmp >= 0) {
- /* hack, but must be unique and no tty line */
- (void) 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 = (long) time((time_t *) 0);
- (void) write(wtmp, (char *)&utmp, sizeof (utmp));
- if (!guest) { /* anon must hang on */
- (void) close(wtmp);
- wtmp = -1;
- }
- }
+ 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);
}
/*
* Record logout in wtmp file
* and exit with supplied status.
*/
+void
dologout(status)
int status;
{
if (logged_in) {
- (void) seteuid(0);
- if (wtmp < 0)
- wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND);
- if (wtmp >= 0) {
- SCPYN(utmp.ut_name, "");
- SCPYN(utmp.ut_host, "");
- utmp.ut_time = (long) time((time_t *) 0);
- (void) write(wtmp, (char *)&utmp, sizeof (utmp));
- (void) close(wtmp);
- }
+ (void) seteuid((uid_t)0);
+ logwtmp(ttyline, "", "");
}
/* beware of flushing buffers after a SIGPIPE */
_exit(status);
}
-/*
- * 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.
- */
-#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;
-{
- register char *cp = cpp;
-
- if (cp == 0)
- return (cp);
- while (*cp && *cp != ' ' && *cp != '\t')
- cp++;
- if (*cp == ' ' || *cp == '\t') {
- *cp++ = '\0';
- while (*cp == ' ' || *cp == '\t')
- cp++;
- }
- if (cp == cpp)
- return ((char *)0);
- return (cp);
-}
-
-FILE *
-popen(cmd, mode)
- char *cmd, *mode;
-{
- 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(), **copyblk();
-
- pop = glob(av[ac]);
- if (pop == (char **)NULL) { /* globbing failed */
- char *vv[2];
-
- vv[0] = av[ac];
- vv[1] = 0;
- pop = copyblk(vv);
- }
- 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 */
- (void) close(myside);
- (void) dup2(hisside, tst(0, 1));
- (void) 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;
- (void) close(hisside);
- return (fdopen(myside, mode));
-}
-
-pclose(ptr)
- FILE *ptr;
-{
- register f, r, (*hstat)(), (*istat)(), (*qstat)();
- int status;
-
- f = fileno(ptr);
- (void) 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;
- (void) signal(SIGINT, istat);
- (void) signal(SIGQUIT, qstat);
- (void) signal(SIGHUP, hstat);
- return (status);
-}
-
-/*
- * Check user requesting login priviledges.
- * Disallow anyone who does not have a standard
- * shell returned by getusershell() (/etc/shells).
- * Disallow anyone mentioned in the file FTPUSERS
- * to allow people such as uucp to be avoided.
- */
-checkuser(name)
- register char *name;
-{
- register char *cp;
- char line[BUFSIZ], *index(), *getusershell();
- FILE *fd;
- struct passwd *pw;
- int found = 0;
-
- pw = getpwnam(name);
- if (pw == NULL)
- return (0);
- if (pw ->pw_shell == NULL || pw->pw_shell[0] == NULL)
- pw->pw_shell = "/bin/sh";
- while ((cp = getusershell()) != NULL)
- if (strcmp(cp, pw->pw_shell) == 0)
- break;
- endusershell();
- if (cp == NULL)
- return (0);
- fd = fopen(FTPUSERS, "r");
- if (fd == NULL)
- return (1);
- while (fgets(line, sizeof (line), fd) != NULL) {
- cp = index(line, '\n');
- if (cp)
- *cp = '\0';
- if (strcmp(line, name) == 0) {
- found++;
- break;
- }
- }
- (void) fclose(fd);
- return (!found);
-}
-
-myoob()
+static void
+myoob(signo)
+ int signo;
{
char *cp;
/* only process if transfer occurring */
- if (!transflag) {
+ if (!transflag)
return;
- }
cp = tmpline;
if (getline(cp, 7, stdin) == NULL) {
- reply(221, "You could at least say goodby.");
+ reply(221, "You could at least say goodbye.");
dologout(0);
}
upper(cp);
- if (strcmp(cp, "ABOR\r\n"))
- return;
- tmpline[0] = '\0';
- reply(426,"Transfer aborted. Data connection closed.");
- reply(226,"Abort successful");
- longjmp(urgcatch, 1);
+ 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);
+ }
}
/*
- * Note: The 530 reply codes could be 4xx codes, except nothing is
- * given in the state tables except 421 which implies an exit. (RFC959)
+ * 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;
- struct sockaddr_in tmp;
- register char *p, *a;
+ char *p, *a;
pdata = socket(AF_INET, SOCK_STREAM, 0);
if (pdata < 0) {
- reply(530, "Can't open passive connection");
+ perror_reply(425, "Can't open passive connection");
return;
}
- tmp = ctrl_addr;
- tmp.sin_port = 0;
- seteuid(0);
- if (bind(pdata, (struct sockaddr *) &tmp, sizeof(tmp)) < 0) {
- seteuid(pw->pw_uid);
- (void) close(pdata);
- pdata = -1;
- reply(530, "Can't open passive connection");
- return;
+ 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;
}
- seteuid(pw->pw_uid);
- len = sizeof(tmp);
- if (getsockname(pdata, (char *) &tmp, &len) < 0) {
- (void) close(pdata);
- pdata = -1;
- reply(530, "Can't open passive connection");
- return;
- }
- if (listen(pdata, 1) < 0) {
- (void) close(pdata);
- pdata = -1;
- reply(530, "Can't open passive connection");
- return;
- }
- a = (char *) &tmp.sin_addr;
- p = (char *) &tmp.sin_port;
+ (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;
}
-char *
+/*
+ * 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];
- char *cp = rindex(local, '/');
- int d, count=0;
- char ext = '1';
+ struct stat st;
+ int count;
+ char *cp;
- if (cp) {
+ cp = strrchr(local, '/');
+ if (cp)
*cp = '\0';
+ if (stat(cp ? local : ".", &st) < 0) {
+ perror_reply(553, cp ? local : ".");
+ return ((char *) 0);
}
- d = access(cp ? local : ".", 2);
- if (cp) {
+ if (cp)
*cp = '/';
- }
- if (d < 0) {
- syslog(LOG_ERR, "%s: %m", local);
- return((char *) 0);
- }
(void) strcpy(new, local);
cp = new + strlen(new);
*cp++ = '.';
- while (!d) {
- if (++count == 100) {
- reply(452, "Unique file name not cannot be created.");
- return((char *) 0);
- }
- *cp++ = ext;
- *cp = '\0';
- if (ext == '9') {
- ext = '0';
- }
- else {
- ext++;
- }
- if ((d = access(new, 0)) < 0) {
- break;
- }
- if (ext != '0') {
- 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) {
+
+ 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;
}
- else if (*(cp - 2) == '.') {
- *(cp - 1) = '1';
+ 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;
}
- else {
- *(cp - 2) = *(cp - 2) + 1;
- cp--;
+
+ 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);
+
+ /*
+ * 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
+/*
+ * 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';
}
- return(new);
+ bp = buf;
+ while (ch = *bp++)
+ if (ch != '\n' && ch != '\r')
+ *p++ = ch;
+ while (p < LastArgv)
+ *p++ = ' ';
}
+#endif /* SETPROCTITLE */