diff options
Diffstat (limited to 'crypto/heimdal/appl/ftp/ftpd/ftpd.c')
-rw-r--r-- | crypto/heimdal/appl/ftp/ftpd/ftpd.c | 2249 |
1 files changed, 2249 insertions, 0 deletions
diff --git a/crypto/heimdal/appl/ftp/ftpd/ftpd.c b/crypto/heimdal/appl/ftp/ftpd/ftpd.c new file mode 100644 index 0000000..8c5ddf3 --- /dev/null +++ b/crypto/heimdal/appl/ftp/ftpd/ftpd.c @@ -0,0 +1,2249 @@ +/* + * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define FTP_NAMES +#include "ftpd_locl.h" +#ifdef KRB5 +#include <krb5.h> +#endif +#include "getarg.h" + +RCSID("$Id: ftpd.c,v 1.137 2000/01/05 13:46:04 joda Exp $"); + +static char version[] = "Version 6.00"; + +extern off_t restart_point; +extern char cbuf[]; + +struct sockaddr_storage ctrl_addr_ss; +struct sockaddr *ctrl_addr = (struct sockaddr *)&ctrl_addr_ss; + +struct sockaddr_storage data_source_ss; +struct sockaddr *data_source = (struct sockaddr *)&data_source_ss; + +struct sockaddr_storage data_dest_ss; +struct sockaddr *data_dest = (struct sockaddr *)&data_dest_ss; + +struct sockaddr_storage his_addr_ss; +struct sockaddr *his_addr = (struct sockaddr *)&his_addr_ss; + +struct sockaddr_storage pasv_addr_ss; +struct sockaddr *pasv_addr = (struct sockaddr *)&pasv_addr_ss; + +int data; +jmp_buf errcatch, urgcatch; +int oobflag; +int logged_in; +struct passwd *pw; +int debug = 0; +int ftpd_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 dochroot; +int type; +int form; +int stru; /* avoid C keyword */ +int mode; +int usedefault = 1; /* for data transfers */ +int pdata = -1; /* for passive mode */ +int 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 */ +int guest_umask = 0777; /* Paranoia for anonymous users */ +char tmpline[10240]; +char hostname[MaxHostNameLen]; +char remotehost[MaxHostNameLen]; +static char ttyline[20]; + +#define AUTH_PLAIN (1 << 0) /* allow sending passwords */ +#define AUTH_OTP (1 << 1) /* passwords are one-time */ +#define AUTH_FTP (1 << 2) /* allow anonymous login */ + +static int auth_level = 0; /* Only allow kerberos login by default */ + +/* + * Timeout intervals for retrying connections + * to hosts that don't accept PORT cmds. This + * is a kludge, but given the problems with TCP... + */ +#define SWAITMAX 90 /* wait at most 90 seconds */ +#define SWAITINT 5 /* interval between retries */ + +int swaitmax = SWAITMAX; +int swaitint = SWAITINT; + +#ifdef HAVE_SETPROCTITLE +char proctitle[BUFSIZ]; /* initial part of title */ +#endif /* HAVE_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 = %ld bytes", \ + cmd, (*(file) == '/') ? "" : curdir(), file, (long)cnt); \ + } + +static void ack (char *); +static void myoob (int); +static int checkuser (char *, char *); +static int checkaccess (char *); +static FILE *dataconn (const char *, off_t, const char *); +static void dolog (struct sockaddr *sa, int len); +static void end_login (void); +static FILE *getdatasock (const char *); +static char *gunique (char *); +static RETSIGTYPE lostconn (int); +static int receive_data (FILE *, FILE *); +static void send_data (FILE *, FILE *); +static struct passwd * sgetpwnam (char *); + +static char * +curdir(void) +{ + static char path[MaxPathLen+1]; /* path + '/' + '\0' */ + + if (getcwd(path, sizeof(path)-1) == NULL) + return (""); + if (path[1] != '\0') /* special case for root dir. */ + strlcat(path, "/", sizeof(path)); + /* For guest account, skip / since it's chrooted */ + return (guest ? path+1 : path); +} + +#ifndef LINE_MAX +#define LINE_MAX 1024 +#endif + +static int +parse_auth_level(char *str) +{ + char *p; + int ret = 0; + char *foo = NULL; + + for(p = strtok_r(str, ",", &foo); + p; + p = strtok_r(NULL, ",", &foo)) { + if(strcmp(p, "user") == 0) + ; +#ifdef OTP + else if(strcmp(p, "otp") == 0) + ret |= AUTH_PLAIN|AUTH_OTP; +#endif + else if(strcmp(p, "ftp") == 0 || + strcmp(p, "safe") == 0) + ret |= AUTH_FTP; + else if(strcmp(p, "plain") == 0) + ret |= AUTH_PLAIN; + else if(strcmp(p, "none") == 0) + ret |= AUTH_PLAIN|AUTH_FTP; + else + warnx("bad value for -a: `%s'", p); + } + return ret; +} + +/* + * Print usage and die. + */ + +static int debug_flag; +static int interactive_flag; +static char *guest_umask_string; +static char *port_string; +static char *umask_string; +static char *auth_string; + +int use_builtin_ls = -1; + +static int help_flag; +static int version_flag; + +struct getargs args[] = { + { NULL, 'a', arg_string, &auth_string, "required authentication" }, + { NULL, 'i', arg_flag, &interactive_flag, "don't assume stdin is a socket" }, + { NULL, 'p', arg_string, &port_string, "what port to listen to" }, + { NULL, 'g', arg_string, &guest_umask_string, "umask for guest logins" }, + { NULL, 'l', arg_counter, &logging, "log more stuff", "" }, + { NULL, 't', arg_integer, &ftpd_timeout, "initial timeout" }, + { NULL, 'T', arg_integer, &maxtimeout, "max timeout" }, + { NULL, 'u', arg_string, &umask_string, "umask for user logins" }, + { NULL, 'd', arg_flag, &debug_flag, "enable debugging" }, + { NULL, 'v', arg_flag, &debug_flag, "enable debugging" }, + { "builtin-ls", 'B', arg_flag, &use_builtin_ls, "use built-in ls to list files" }, + { "version", 0, arg_flag, &version_flag }, + { "help", 'h', arg_flag, &help_flag } +}; + +static int num_args = sizeof(args) / sizeof(args[0]); + +static void +usage (int code) +{ + arg_printusage(args, num_args, NULL, ""); + exit (code); +} + +/* output contents of a file */ +static int +show_file(const char *file, int code) +{ + FILE *f; + char buf[128]; + + f = fopen(file, "r"); + if(f == NULL) + return -1; + while(fgets(buf, sizeof(buf), f)){ + buf[strcspn(buf, "\r\n")] = '\0'; + lreply(code, "%s", buf); + } + fclose(f); + return 0; +} + +int +main(int argc, char **argv) +{ + int his_addr_len, ctrl_addr_len, on = 1, tos; + char *cp, line[LINE_MAX]; + FILE *fd; + int port; + struct servent *sp; + + int optind = 0; + + set_progname (argv[0]); + +#ifdef KRB4 + /* detach from any tickets and tokens */ + { + char tkfile[1024]; + snprintf(tkfile, sizeof(tkfile), + "/tmp/ftp_%u", (unsigned)getpid()); + krb_set_tkt_string(tkfile); + if(k_hasafs()) + k_setpag(); + } +#endif + if(getarg(args, num_args, argc, argv, &optind)) + usage(1); + + if(help_flag) + usage(0); + + if(version_flag) { + print_version(NULL); + exit(0); + } + + if(auth_string) + auth_level = parse_auth_level(auth_string); + { + char *p; + long val = 0; + + if(guest_umask_string) { + val = strtol(guest_umask_string, &p, 8); + if (*p != '\0' || val < 0) + warnx("bad value for -g"); + else + guest_umask = val; + } + if(umask_string) { + val = strtol(umask_string, &p, 8); + if (*p != '\0' || val < 0) + warnx("bad value for -u"); + else + defumask = val; + } + } + if(port_string) { + sp = getservbyname(port_string, "tcp"); + if(sp) + port = sp->s_port; + else + if(isdigit(port_string[0])) + port = htons(atoi(port_string)); + else + warnx("bad value for -p"); + } else { + sp = getservbyname("ftp", "tcp"); + if(sp) + port = sp->s_port; + else + port = htons(21); + } + + if (maxtimeout < ftpd_timeout) + maxtimeout = ftpd_timeout; + +#if 0 + if (ftpd_timeout > maxtimeout) + ftpd_timeout = maxtimeout; +#endif + + if(interactive_flag) + mini_inetd (port); + + /* + * 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); + his_addr_len = sizeof(his_addr_ss); + if (getpeername(STDIN_FILENO, his_addr, &his_addr_len) < 0) { + syslog(LOG_ERR, "getpeername (%s): %m",argv[0]); + exit(1); + } + ctrl_addr_len = sizeof(ctrl_addr_ss); + if (getsockname(STDIN_FILENO, ctrl_addr, &ctrl_addr_len) < 0) { + syslog(LOG_ERR, "getsockname (%s): %m",argv[0]); + exit(1); + } +#if defined(IP_TOS) && defined(HAVE_SETSOCKOPT) + tos = IPTOS_LOWDELAY; + if (setsockopt(STDIN_FILENO, IPPROTO_IP, IP_TOS, + (void *)&tos, sizeof(int)) < 0) + syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); +#endif + data_source->sa_family = ctrl_addr->sa_family; + socket_set_port (data_source, + htons(ntohs(socket_get_port(ctrl_addr)) - 1)); + + /* set this here so it can be put in wtmp */ + snprintf(ttyline, sizeof(ttyline), "ftp%u", (unsigned)getpid()); + + + /* freopen(_PATH_DEVNULL, "w", stderr); */ + signal(SIGPIPE, lostconn); + signal(SIGCHLD, SIG_IGN); +#ifdef SIGURG + if (signal(SIGURG, myoob) == SIG_ERR) + syslog(LOG_ERR, "signal: %m"); +#endif + + /* Try to handle urgent data inline */ +#if defined(SO_OOBINLINE) && defined(HAVE_SETSOCKOPT) + if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (void *)&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, his_addr_len); + /* + * 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(show_file(_PATH_NOLOGIN, 530) == 0) { + reply(530, "System not available."); + exit(0); + } + show_file(_PATH_FTPWELCOME, 220); + /* reply(220,) must follow */ + gethostname(hostname, sizeof(hostname)); + + reply(220, "%s FTP server (%s" +#ifdef KRB5 + "+%s" +#endif +#ifdef KRB4 + "+%s" +#endif + ") ready.", hostname, version +#ifdef KRB5 + ,heimdal_version +#endif +#ifdef KRB4 + ,krb4_version +#endif + ); + + setjmp(errcatch); + for (;;) + yyparse(); + /* NOTREACHED */ +} + +static RETSIGTYPE +lostconn(int signo) +{ + + if (debug) + syslog(LOG_DEBUG, "lost connection"); + dologout(-1); +} + +/* + * Helper function for sgetpwnam(). + */ +static char * +sgetsave(char *s) +{ + char *new = strdup(s); + + if (new == NULL) { + perror_reply(421, "Local resource failure: malloc"); + dologout(1); + /* NOTREACHED */ + } + 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(char *name) +{ + static struct passwd save; + struct passwd *p; + + if ((p = k_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 */ +#ifdef OTP +OtpContext otp_ctx; +#endif + +/* + * 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(char *name) +{ + char *cp, *shell; + + if(auth_level == 0 && !sec_complete){ + reply(530, "No login allowed without authorization."); + return; + } + + if (logged_in) { + if (guest) { + reply(530, "Can't change user from guest login."); + return; + } else if (dochroot) { + reply(530, "Can't change user from chroot user."); + return; + } + end_login(); + } + + guest = 0; + if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) { + if ((auth_level & AUTH_FTP) == 0 || + checkaccess("ftp") || + checkaccess("anonymous")) + reply(530, "User %s access denied.", name); + else if ((pw = sgetpwnam("ftp")) != NULL) { + guest = 1; + defumask = guest_umask; /* paranoia for incoming */ + askpasswd = 1; + reply(331, "Guest login ok, type your name as password."); + } else + reply(530, "User %s unknown.", name); + if (!askpasswd && logging) { + char data_addr[256]; + + if (inet_ntop (his_addr->sa_family, + socket_get_address(his_addr), + data_addr, sizeof(data_addr)) == NULL) + strlcpy (data_addr, "unknown address", + sizeof(data_addr)); + + syslog(LOG_NOTICE, + "ANONYMOUS FTP LOGIN REFUSED FROM %s(%s)", + remotehost, data_addr); + } + return; + } + if((auth_level & AUTH_PLAIN) == 0 && !sec_complete){ + reply(530, "Only authorized and anonymous login allowed."); + return; + } + if ((pw = sgetpwnam(name))) { + if ((shell = pw->pw_shell) == NULL || *shell == 0) + shell = _PATH_BSHELL; + while ((cp = getusershell()) != NULL) + if (strcmp(cp, shell) == 0) + break; + endusershell(); + + if (cp == NULL || checkaccess(name)) { + reply(530, "User %s access denied.", name); + if (logging) { + char data_addr[256]; + + if (inet_ntop (his_addr->sa_family, + socket_get_address(his_addr), + data_addr, + sizeof(data_addr)) == NULL) + strlcpy (data_addr, + "unknown address", + sizeof(data_addr)); + + syslog(LOG_NOTICE, + "FTP LOGIN REFUSED FROM %s(%s), %s", + remotehost, + data_addr, + name); + } + pw = (struct passwd *) NULL; + return; + } + } + if (logging) + strlcpy(curname, name, sizeof(curname)); + if(sec_complete) { + if(sec_userok(name) == 0) + do_login(232, name); + else + reply(530, "User %s access denied.", name); + } else { + char ss[256]; + +#ifdef OTP + if (otp_challenge(&otp_ctx, name, ss, sizeof(ss)) == 0) { + reply(331, "Password %s for %s required.", + ss, name); + askpasswd = 1; + } else +#endif + if ((auth_level & AUTH_OTP) == 0) { + reply(331, "Password required for %s.", name); + askpasswd = 1; + } else { + char *s; + +#ifdef OTP + if ((s = otp_error (&otp_ctx)) != NULL) + lreply(530, "OTP: %s", s); +#endif + reply(530, + "Only authorized, anonymous" +#ifdef OTP + " and OTP " +#endif + "login allowed."); + } + + } + /* + * Delay before reading passwd after first failed + * attempt to slow down passwd-guessing programs. + */ + if (login_attempts) + sleep(login_attempts); +} + +/* + * Check if a user is in the file "fname" + */ +static int +checkuser(char *fname, char *name) +{ + FILE *fd; + int found = 0; + char *p, line[BUFSIZ]; + + if ((fd = fopen(fname, "r")) != NULL) { + while (fgets(line, sizeof(line), fd) != NULL) + if ((p = strchr(line, '\n')) != NULL) { + *p = '\0'; + if (line[0] == '#') + continue; + if (strcmp(line, name) == 0) { + found = 1; + break; + } + } + fclose(fd); + } + return (found); +} + + +/* + * Determine whether a user has access, based on information in + * _PATH_FTPUSERS. The users are listed one per line, with `allow' + * or `deny' after the username. If anything other than `allow', or + * just nothing, is given after the username, `deny' is assumed. + * + * If the user is not found in the file, but the pseudo-user `*' is, + * the permission is taken from that line. + * + * This preserves the old semantics where if a user was listed in the + * file he was denied, otherwise he was allowed. + * + * Return 1 if the user is denied, or 0 if he is allowed. */ + +static int +match(const char *pattern, const char *string) +{ + return fnmatch(pattern, string, FNM_NOESCAPE); +} + +static int +checkaccess(char *name) +{ +#define ALLOWED 0 +#define NOT_ALLOWED 1 + FILE *fd; + int allowed = ALLOWED; + char *user, *perm, line[BUFSIZ]; + char *foo; + + fd = fopen(_PATH_FTPUSERS, "r"); + + if(fd == NULL) + return allowed; + + while (fgets(line, sizeof(line), fd) != NULL) { + foo = NULL; + user = strtok_r(line, " \t\n", &foo); + if (user == NULL || user[0] == '#') + continue; + perm = strtok_r(NULL, " \t\n", &foo); + if (match(user, name) == 0){ + if(perm && strcmp(perm, "allow") == 0) + allowed = ALLOWED; + else + allowed = NOT_ALLOWED; + break; + } + } + fclose(fd); + return allowed; +} +#undef ALLOWED +#undef NOT_ALLOWED + + +int do_login(int code, char *passwd) +{ + FILE *fd; + login_attempts = 0; /* this time successful */ + if (setegid((gid_t)pw->pw_gid) < 0) { + reply(550, "Can't set gid."); + return -1; + } + initgroups(pw->pw_name, pw->pw_gid); + + /* open wtmp before chroot */ + ftpd_logwtmp(ttyline, pw->pw_name, remotehost); + logged_in = 1; + + dochroot = checkuser(_PATH_FTPCHROOT, pw->pw_name); + 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."); + return -1; + } + } else if (dochroot) { + if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) { + reply(550, "Can't change root."); + return -1; + } + } 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); + return -1; + } else + lreply(code, "No directory! Logging in with home=/"); + } + if (seteuid((uid_t)pw->pw_uid) < 0) { + reply(550, "Can't set uid."); + return -1; + } + + if(use_builtin_ls == -1) { + struct stat st; + /* if /bin/ls exist and is a regular file, use it, otherwise + use built-in ls */ + if(stat("/bin/ls", &st) == 0 && + S_ISREG(st.st_mode)) + use_builtin_ls = 0; + else + use_builtin_ls = 1; + } + + /* + * Display a login message, if it exists. + * N.B. reply(code,) must follow the message. + */ + show_file(_PATH_FTPLOGINMESG, code); + if(show_file(_PATH_ISSUE_NET, code) != 0) + show_file(_PATH_ISSUE, code); + if (guest) { + reply(code, "Guest login ok, access restrictions apply."); +#ifdef HAVE_SETPROCTITLE + snprintf (proctitle, sizeof(proctitle), + "%s: anonymous/%s", + remotehost, + passwd); + setproctitle(proctitle); +#endif /* HAVE_SETPROCTITLE */ + if (logging) { + char data_addr[256]; + + if (inet_ntop (his_addr->sa_family, + socket_get_address(his_addr), + data_addr, sizeof(data_addr)) == NULL) + strlcpy (data_addr, "unknown address", + sizeof(data_addr)); + + syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s(%s), %s", + remotehost, + data_addr, + passwd); + } + } else { + reply(code, "User %s logged in.", pw->pw_name); +#ifdef HAVE_SETPROCTITLE + snprintf(proctitle, sizeof(proctitle), "%s: %s", remotehost, pw->pw_name); + setproctitle(proctitle); +#endif /* HAVE_SETPROCTITLE */ + if (logging) { + char data_addr[256]; + + if (inet_ntop (his_addr->sa_family, + socket_get_address(his_addr), + data_addr, sizeof(data_addr)) == NULL) + strlcpy (data_addr, "unknown address", + sizeof(data_addr)); + + syslog(LOG_INFO, "FTP LOGIN FROM %s(%s) as %s", + remotehost, + data_addr, + pw->pw_name); + } + } + umask(defumask); + return 0; +} + +/* + * Terminate login as previous user, if any, resetting state; + * used when USER command is given or login fails. + */ +static void +end_login(void) +{ + + seteuid((uid_t)0); + if (logged_in) + ftpd_logwtmp(ttyline, "", ""); + pw = NULL; + logged_in = 0; + guest = 0; + dochroot = 0; +} + +void +pass(char *passwd) +{ + int rval; + + /* some clients insists on sending a password */ + if (logged_in && askpasswd == 0){ + reply(230, "Dumpucko!"); + return; + } + + if (logged_in || askpasswd == 0) { + reply(503, "Login with USER first."); + return; + } + askpasswd = 0; + rval = 1; + if (!guest) { /* "ftp" is only account allowed no password */ + if (pw == NULL) + rval = 1; /* failure below */ +#ifdef OTP + else if (otp_verify_user (&otp_ctx, passwd) == 0) { + rval = 0; + } +#endif + else if((auth_level & AUTH_OTP) == 0) { +#ifdef KRB4 + char realm[REALM_SZ]; + if((rval = krb_get_lrealm(realm, 1)) == KSUCCESS) + rval = krb_verify_user(pw->pw_name, + "", realm, + passwd, + KRB_VERIFY_SECURE, NULL); + if (rval == KSUCCESS ) { + chown (tkt_string(), pw->pw_uid, pw->pw_gid); + if(k_hasafs()) + krb_afslog(0, 0); + } else +#endif + rval = unix_verify_user(pw->pw_name, passwd); + } else { + char *s; + +#ifdef OTP + if ((s = otp_error(&otp_ctx)) != NULL) + lreply(530, "OTP: %s", s); +#endif + } + memset (passwd, 0, strlen(passwd)); + + /* + * If rval == 1, the user failed the authentication + * check above. If rval == 0, either Kerberos or + * local authentication succeeded. + */ + if (rval) { + char data_addr[256]; + + if (inet_ntop (his_addr->sa_family, + socket_get_address(his_addr), + data_addr, sizeof(data_addr)) == NULL) + strlcpy (data_addr, "unknown address", + sizeof(data_addr)); + + reply(530, "Login incorrect."); + if (logging) + syslog(LOG_NOTICE, + "FTP LOGIN FAILED FROM %s(%s), %s", + remotehost, + data_addr, + curname); + pw = NULL; + if (login_attempts++ >= 5) { + syslog(LOG_NOTICE, + "repeated login failures from %s(%s)", + remotehost, + data_addr); + exit(0); + } + return; + } + } + if(!do_login(230, passwd)) + return; + + /* Forget all about it... */ + end_login(); +} + +void +retrieve(const char *cmd, char *name) +{ + FILE *fin = NULL, *dout; + struct stat st; + int (*closefunc) (FILE *); + char line[BUFSIZ]; + + + if (cmd == 0) { + fin = fopen(name, "r"); + closefunc = fclose; + st.st_size = 0; + if(fin == NULL){ + int save_errno = errno; + struct cmds { + const char *ext; + const char *cmd; + const char *rev_cmd; + } cmds[] = { + {".tar", "/bin/gtar cPf - %s", NULL}, + {".tar.gz", "/bin/gtar zcPf - %s", NULL}, + {".tar.Z", "/bin/gtar ZcPf - %s", NULL}, + {".gz", "/bin/gzip -c -- %s", "/bin/gzip -c -d -- %s"}, + {".Z", "/bin/compress -c -- %s", "/bin/uncompress -c -- %s"}, + {NULL, NULL} + }; + struct cmds *p; + for(p = cmds; p->ext; p++){ + char *tail = name + strlen(name) - strlen(p->ext); + char c = *tail; + + if(strcmp(tail, p->ext) == 0 && + (*tail = 0) == 0 && + access(name, R_OK) == 0){ + snprintf (line, sizeof(line), p->cmd, name); + *tail = c; + break; + } + *tail = c; + if (p->rev_cmd != NULL) { + char *ext; + + asprintf(&ext, "%s%s", name, p->ext); + if (ext != NULL) { + if (access(ext, R_OK) == 0) { + snprintf (line, sizeof(line), + p->rev_cmd, ext); + free(ext); + break; + } + free(ext); + } + } + + } + if(p->ext){ + fin = ftpd_popen(line, "r", 0, 0); + closefunc = ftpd_pclose; + st.st_size = -1; + cmd = line; + } else + errno = save_errno; + } + } else { + snprintf(line, sizeof(line), cmd, name); + name = line; + fin = ftpd_popen(line, "r", 1, 0); + closefunc = ftpd_pclose; + st.st_size = -1; + } + if (fin == NULL) { + if (errno != 0) { + perror_reply(550, name); + if (cmd == 0) { + LOGCMD("get", name); + } + } + return; + } + byte_count = -1; + if (cmd == 0){ + if(fstat(fileno(fin), &st) < 0 || !S_ISREG(st.st_mode)) { + reply(550, "%s: not a plain file.", name); + goto done; + } + } + if (restart_point) { + if (type == TYPE_A) { + off_t i, n; + int c; + + n = restart_point; + i = 0; + while (i++ < n) { + if ((c=getc(fin)) == EOF) { + perror_reply(550, name); + goto done; + } + if (c == '\n') + i++; + } + } else if (lseek(fileno(fin), restart_point, SEEK_SET) < 0) { + perror_reply(550, name); + goto done; + } + } + dout = dataconn(name, st.st_size, "w"); + if (dout == NULL) + goto done; + set_buffer_size(fileno(dout), 0); + send_data(fin, dout); + fclose(dout); + data = -1; + pdata = -1; +done: + if (cmd == 0) + LOGBYTES("get", name, byte_count); + (*closefunc)(fin); +} + +/* filename sanity check */ + +int +filename_check(char *filename) +{ + static const char good_chars[] = "+-=_,."; + char *p; + + p = strrchr(filename, '/'); + if(p) + filename = p + 1; + + p = filename; + + if(isalnum(*p)){ + p++; + while(*p && (isalnum(*p) || strchr(good_chars, *p))) + p++; + if(*p == '\0') + return 0; + } + lreply(553, "\"%s\" is an illegal filename.", filename); + lreply(553, "The filename must start with an alphanumeric " + "character and must only"); + reply(553, "consist of alphanumeric characters or any of the following: %s", + good_chars); + return 1; +} + +void +do_store(char *name, char *mode, int unique) +{ + FILE *fout, *din; + struct stat st; + int (*closefunc) (FILE *); + + if(guest && filename_check(name)) + return; + if (unique && stat(name, &st) == 0 && + (name = gunique(name)) == NULL) { + LOGCMD(*mode == 'w' ? "put" : "append", name); + return; + } + + if (restart_point) + mode = "r+"; + fout = fopen(name, mode); + closefunc = fclose; + if (fout == NULL) { + perror_reply(553, name); + LOGCMD(*mode == 'w' ? "put" : "append", name); + return; + } + byte_count = -1; + if (restart_point) { + if (type == TYPE_A) { + off_t i, n; + int c; + + n = restart_point; + i = 0; + while (i++ < n) { + if ((c=getc(fout)) == EOF) { + perror_reply(550, name); + goto done; + } + if (c == '\n') + i++; + } + /* + * We must do this seek to "current" position + * because we are changing from reading to + * writing. + */ + if (fseek(fout, 0L, SEEK_CUR) < 0) { + perror_reply(550, name); + goto done; + } + } else if (lseek(fileno(fout), restart_point, SEEK_SET) < 0) { + perror_reply(550, name); + goto done; + } + } + din = dataconn(name, (off_t)-1, "r"); + if (din == NULL) + goto done; + set_buffer_size(fileno(din), 1); + if (receive_data(din, fout) == 0) { + if (unique) + reply(226, "Transfer complete (unique file name:%s).", + name); + else + reply(226, "Transfer complete."); + } + fclose(din); + data = -1; + pdata = -1; +done: + LOGBYTES(*mode == 'w' ? "put" : "append", name, byte_count); + (*closefunc)(fout); +} + +static FILE * +getdatasock(const char *mode) +{ + int s, t, tries; + + if (data >= 0) + return (fdopen(data, mode)); + seteuid(0); + s = socket(ctrl_addr->sa_family, SOCK_STREAM, 0); + if (s < 0) + goto bad; + socket_set_reuseaddr (s, 1); + /* anchor socket to avoid multi-homing problems */ + socket_set_address_and_port (data_source, + socket_get_address (ctrl_addr), + socket_get_port (data_source)); + + for (tries = 1; ; tries++) { + if (bind(s, data_source, + socket_sockaddr_size (data_source)) >= 0) + break; + if (errno != EADDRINUSE || tries > 10) + goto bad; + sleep(tries); + } + seteuid(pw->pw_uid); +#ifdef IPTOS_THROUGHPUT + socket_set_tos (s, IPTOS_THROUGHPUT); +#endif + return (fdopen(s, mode)); +bad: + /* Return the real value of errno (close may change it) */ + t = errno; + seteuid((uid_t)pw->pw_uid); + close(s); + errno = t; + return (NULL); +} + +static FILE * +dataconn(const char *name, off_t size, const char *mode) +{ + char sizebuf[32]; + FILE *file; + int retry = 0; + + file_size = size; + byte_count = 0; + if (size >= 0) + snprintf(sizebuf, sizeof(sizebuf), " (%ld bytes)", (long)size); + else + *sizebuf = '\0'; + if (pdata >= 0) { + struct sockaddr_storage from_ss; + struct sockaddr *from = (struct sockaddr *)&from_ss; + int s; + int fromlen = sizeof(from_ss); + + s = accept(pdata, from, &fromlen); + if (s < 0) { + reply(425, "Can't open data connection."); + close(pdata); + pdata = -1; + return (NULL); + } + close(pdata); + pdata = s; +#if defined(IP_TOS) && defined(HAVE_SETSOCKOPT) + { + int tos = IPTOS_THROUGHPUT; + + setsockopt(s, IPPROTO_IP, IP_TOS, (void *)&tos, + sizeof(tos)); + } +#endif + reply(150, "Opening %s mode data connection for '%s'%s.", + type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); + return (fdopen(pdata, mode)); + } + if (data >= 0) { + reply(125, "Using existing data connection for '%s'%s.", + name, sizebuf); + usedefault = 1; + return (fdopen(data, mode)); + } + if (usedefault) + data_dest = his_addr; + usedefault = 1; + file = getdatasock(mode); + if (file == NULL) { + char data_addr[256]; + + if (inet_ntop (data_source->sa_family, + socket_get_address(data_source), + data_addr, sizeof(data_addr)) == NULL) + strlcpy (data_addr, "unknown address", + sizeof(data_addr)); + + reply(425, "Can't create data socket (%s,%d): %s.", + data_addr, + socket_get_port (data_source), + strerror(errno)); + return (NULL); + } + data = fileno(file); + while (connect(data, data_dest, + socket_sockaddr_size(data_dest)) < 0) { + if (errno == EADDRINUSE && retry < swaitmax) { + sleep(swaitint); + retry += swaitint; + continue; + } + perror_reply(425, "Can't build data connection"); + fclose(file); + data = -1; + return (NULL); + } + reply(150, "Opening %s mode data connection for '%s'%s.", + type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); + return (file); +} + +/* + * Tranfer the contents of "instr" to "outstr" peer using the appropriate + * encapsulation of the data subject * to Mode, Structure, and Type. + * + * NB: Form isn't handled. + */ +static void +send_data(FILE *instr, FILE *outstr) +{ + int c, cnt, filefd, netfd; + static char *buf; + static size_t bufsize; + + transflag++; + if (setjmp(urgcatch)) { + transflag = 0; + return; + } + switch (type) { + + case TYPE_A: + while ((c = getc(instr)) != EOF) { + byte_count++; + if(c == '\n') + sec_putc('\r', outstr); + sec_putc(c, outstr); + } + sec_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 defined(HAVE_MMAP) && !defined(NO_MMAP) +#ifndef MAP_FAILED +#define MAP_FAILED (-1) +#endif + { + struct stat st; + char *chunk; + int in = fileno(instr); + if(fstat(in, &st) == 0 && S_ISREG(st.st_mode) + && st.st_size > 0) { + /* + * mmap zero bytes has potential of loosing, don't do it. + */ + chunk = mmap(0, st.st_size, PROT_READ, + MAP_SHARED, in, 0); + if((void *)chunk != (void *)MAP_FAILED) { + cnt = st.st_size - restart_point; + sec_write(fileno(outstr), chunk + restart_point, cnt); + if (munmap(chunk, st.st_size) < 0) + warn ("munmap"); + sec_fflush(outstr); + byte_count = cnt; + transflag = 0; + } + } + } +#endif + if(transflag) { + struct stat st; + + netfd = fileno(outstr); + filefd = fileno(instr); + buf = alloc_buffer (buf, &bufsize, + fstat(filefd, &st) >= 0 ? &st : NULL); + if (buf == NULL) { + transflag = 0; + perror_reply(451, "Local resource failure: malloc"); + return; + } + while ((cnt = read(filefd, buf, bufsize)) > 0 && + sec_write(netfd, buf, cnt) == cnt) + byte_count += cnt; + sec_fflush(outstr); /* to end an encrypted stream */ + transflag = 0; + if (cnt != 0) { + if (cnt < 0) + goto file_err; + goto data_err; + } + } + reply(226, "Transfer complete."); + return; + default: + transflag = 0; + reply(550, "Unimplemented TYPE %d in send_data", type); + return; + } + +data_err: + transflag = 0; + perror_reply(426, "Data connection"); + return; + +file_err: + transflag = 0; + perror_reply(551, "Error on input file"); +} + +/* + * Transfer data from peer to "outstr" using the appropriate encapulation of + * the data subject to Mode, Structure, and Type. + * + * N.B.: Form isn't handled. + */ +static int +receive_data(FILE *instr, FILE *outstr) +{ + int cnt, bare_lfs = 0; + static char *buf; + static size_t bufsize; + struct stat st; + + transflag++; + if (setjmp(urgcatch)) { + transflag = 0; + return (-1); + } + + buf = alloc_buffer (buf, &bufsize, + fstat(fileno(outstr), &st) >= 0 ? &st : NULL); + if (buf == NULL) { + transflag = 0; + perror_reply(451, "Local resource failure: malloc"); + return -1; + } + + switch (type) { + + case TYPE_I: + case TYPE_L: + while ((cnt = sec_read(fileno(instr), buf, bufsize)) > 0) { + if (write(fileno(outstr), buf, cnt) != cnt) + goto file_err; + byte_count += cnt; + } + if (cnt < 0) + goto data_err; + transflag = 0; + return (0); + + case TYPE_E: + reply(553, "TYPE E not implemented."); + transflag = 0; + return (-1); + + case TYPE_A: + { + char *p, *q; + int cr_flag = 0; + while ((cnt = sec_read(fileno(instr), + buf + cr_flag, + bufsize - cr_flag)) > 0){ + byte_count += cnt; + cnt += cr_flag; + cr_flag = 0; + for(p = buf, q = buf; p < buf + cnt;) { + if(*p == '\n') + bare_lfs++; + if(*p == '\r') { + if(p == buf + cnt - 1){ + cr_flag = 1; + p++; + continue; + }else if(p[1] == '\n'){ + *q++ = '\n'; + p += 2; + continue; + } + } + *q++ = *p++; + } + fwrite(buf, q - buf, 1, outstr); + if(cr_flag) + buf[0] = '\r'; + } + if(cr_flag) + putc('\r', outstr); + fflush(outstr); + if (ferror(instr)) + goto data_err; + if (ferror(outstr)) + goto file_err; + transflag = 0; + if (bare_lfs) { + lreply(226, "WARNING! %d bare linefeeds received in ASCII mode\r\n" + " File may not have transferred correctly.\r\n", + bare_lfs); + } + return (0); + } + default: + reply(550, "Unimplemented TYPE %d in receive_data", type); + transflag = 0; + return (-1); + } + +data_err: + transflag = 0; + perror_reply(426, "Data Connection"); + return (-1); + +file_err: + transflag = 0; + perror_reply(452, "Error writing file"); + return (-1); +} + +void +statfilecmd(char *filename) +{ + FILE *fin; + int c; + char line[LINE_MAX]; + + snprintf(line, sizeof(line), "/bin/ls -la -- %s", filename); + fin = ftpd_popen(line, "r", 1, 0); + lreply(211, "status of %s:", filename); + while ((c = getc(fin)) != EOF) { + if (c == '\n') { + if (ferror(stdout)){ + perror_reply(421, "control connection"); + ftpd_pclose(fin); + dologout(1); + /* NOTREACHED */ + } + if (ferror(fin)) { + perror_reply(551, filename); + ftpd_pclose(fin); + return; + } + putc('\r', stdout); + } + putc(c, stdout); + } + ftpd_pclose(fin); + reply(211, "End of Status"); +} + +void +statcmd(void) +{ +#if 0 + struct sockaddr_in *sin; + u_char *a, *p; + + lreply(211, "%s FTP server (%s) 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"); +#endif + reply(211, "End of status"); +} + +void +fatal(char *s) +{ + + reply(451, "Error in server: %s\n", s); + reply(221, "Closing connection due to server error."); + dologout(0); + /* NOTREACHED */ +} + +static void +int_reply(int, char *, const char *, va_list) +#ifdef __GNUC__ +__attribute__ ((format (printf, 3, 0))) +#endif +; + +static void +int_reply(int n, char *c, const char *fmt, va_list ap) +{ + char buf[10240]; + char *p; + p=buf; + if(n){ + snprintf(p, sizeof(buf), "%d%s", n, c); + p+=strlen(p); + } + vsnprintf(p, sizeof(buf) - strlen(p), fmt, ap); + p+=strlen(p); + snprintf(p, sizeof(buf) - strlen(p), "\r\n"); + p+=strlen(p); + sec_fprintf(stdout, "%s", buf); + fflush(stdout); + if (debug) + syslog(LOG_DEBUG, "<--- %s- ", buf); +} + +void +reply(int n, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int_reply(n, " ", fmt, ap); + delete_ftp_command(); + va_end(ap); +} + +void +lreply(int n, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int_reply(n, "-", fmt, ap); + va_end(ap); +} + +void +nreply(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int_reply(0, NULL, fmt, ap); + va_end(ap); +} + +static void +ack(char *s) +{ + + reply(250, "%s command successful.", s); +} + +void +nack(char *s) +{ + + reply(502, "%s command not implemented.", s); +} + +/* ARGSUSED */ +void +yyerror(char *s) +{ + char *cp; + + if ((cp = strchr(cbuf,'\n'))) + *cp = '\0'; + reply(500, "'%s': command not understood.", cbuf); +} + +void +do_delete(char *name) +{ + struct stat st; + + LOGCMD("delete", name); + if (stat(name, &st) < 0) { + perror_reply(550, name); + return; + } + if ((st.st_mode&S_IFMT) == S_IFDIR) { + if (rmdir(name) < 0) { + perror_reply(550, name); + return; + } + goto done; + } + if (unlink(name) < 0) { + perror_reply(550, name); + return; + } +done: + ack("DELE"); +} + +void +cwd(char *path) +{ + + if (chdir(path) < 0) + perror_reply(550, path); + else + ack("CWD"); +} + +void +makedir(char *name) +{ + + LOGCMD("mkdir", name); + if(guest && filename_check(name)) + return; + if (mkdir(name, 0777) < 0) + perror_reply(550, name); + else{ + if(guest) + chmod(name, 0700); /* guest has umask 777 */ + reply(257, "MKD command successful."); + } +} + +void +removedir(char *name) +{ + + LOGCMD("rmdir", name); + if (rmdir(name) < 0) + perror_reply(550, name); + else + ack("RMD"); +} + +void +pwd(void) +{ + char path[MaxPathLen]; + char *ret; + + /* SunOS has a broken getcwd that does popen(pwd) (!!!), this + * failes miserably when running chroot + */ + ret = getcwd(path, sizeof(path)); + if (ret == NULL) + reply(550, "%s.", strerror(errno)); + else + reply(257, "\"%s\" is current directory.", path); +} + +char * +renamefrom(char *name) +{ + struct stat st; + + if (stat(name, &st) < 0) { + perror_reply(550, name); + return NULL; + } + reply(350, "File exists, ready for destination name"); + return (name); +} + +void +renamecmd(char *from, char *to) +{ + + LOGCMD2("rename", from, to); + if(guest && filename_check(to)) + return; + if (rename(from, to) < 0) + perror_reply(550, "rename"); + else + ack("RNTO"); +} + +static void +dolog(struct sockaddr *sa, int len) +{ + getnameinfo_verified (sa, len, remotehost, sizeof(remotehost), + NULL, 0, 0); +#ifdef HAVE_SETPROCTITLE + snprintf(proctitle, sizeof(proctitle), "%s: connected", remotehost); + setproctitle(proctitle); +#endif /* HAVE_SETPROCTITLE */ + + if (logging) { + char data_addr[256]; + + if (inet_ntop (his_addr->sa_family, + socket_get_address(his_addr), + data_addr, sizeof(data_addr)) == NULL) + strlcpy (data_addr, "unknown address", + sizeof(data_addr)); + + + syslog(LOG_INFO, "connection from %s(%s)", + remotehost, + data_addr); + } +} + +/* + * Record logout in wtmp file + * and exit with supplied status. + */ +void +dologout(int status) +{ + transflag = 0; + if (logged_in) { + seteuid((uid_t)0); + ftpd_logwtmp(ttyline, "", ""); +#ifdef KRB4 + cond_kdestroy(); +#endif + } + /* beware of flushing buffers after a SIGPIPE */ +#ifdef XXX + exit(status); +#else + _exit(status); +#endif +} + +void abor(void) +{ +} + +static void +myoob(int signo) +{ +#if 0 + char *cp; +#endif + + /* only process if transfer occurring */ + if (!transflag) + return; + + /* This is all XXX */ + oobflag = 1; + /* if the command resulted in a new command, + parse that as well */ + do{ + yyparse(); + } while(ftp_command); + oobflag = 0; + +#if 0 + cp = tmpline; + if (ftpd_getline(cp, 7) == 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: %ld of %ld bytes transferred", + (long)byte_count, + (long)file_size); + else + reply(213, "Status: %ld bytes transferred" + (long)byte_count); + } +#endif +} + +/* + * 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 +pasv(void) +{ + int len; + char *p, *a; + struct sockaddr_in *sin; + + if (ctrl_addr->sa_family != AF_INET) { + reply(425, + "You cannot do PASV with something that's not IPv4"); + return; + } + + pdata = socket(ctrl_addr->sa_family, SOCK_STREAM, 0); + if (pdata < 0) { + perror_reply(425, "Can't open passive connection"); + return; + } + pasv_addr->sa_family = ctrl_addr->sa_family; + socket_set_address_and_port (pasv_addr, + socket_get_address (ctrl_addr), + 0); + seteuid(0); + if (bind(pdata, pasv_addr, socket_sockaddr_size (pasv_addr)) < 0) { + seteuid(pw->pw_uid); + goto pasv_error; + } + seteuid(pw->pw_uid); + len = sizeof(pasv_addr_ss); + if (getsockname(pdata, pasv_addr, &len) < 0) + goto pasv_error; + if (listen(pdata, 1) < 0) + goto pasv_error; + sin = (struct sockaddr_in *)pasv_addr; + a = (char *) &sin->sin_addr; + p = (char *) &sin->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: + close(pdata); + pdata = -1; + perror_reply(425, "Can't open passive connection"); + return; +} + +void +epsv(char *proto) +{ + int len; + + pdata = socket(ctrl_addr->sa_family, SOCK_STREAM, 0); + if (pdata < 0) { + perror_reply(425, "Can't open passive connection"); + return; + } + pasv_addr->sa_family = ctrl_addr->sa_family; + socket_set_address_and_port (pasv_addr, + socket_get_address (ctrl_addr), + 0); + seteuid(0); + if (bind(pdata, pasv_addr, socket_sockaddr_size (pasv_addr)) < 0) { + seteuid(pw->pw_uid); + goto pasv_error; + } + seteuid(pw->pw_uid); + len = sizeof(pasv_addr_ss); + if (getsockname(pdata, pasv_addr, &len) < 0) + goto pasv_error; + if (listen(pdata, 1) < 0) + goto pasv_error; + + reply(229, "Entering Extended Passive Mode (|||%d|)", + ntohs(socket_get_port (pasv_addr))); + return; + +pasv_error: + close(pdata); + pdata = -1; + perror_reply(425, "Can't open passive connection"); + return; +} + +void +eprt(char *str) +{ + char *end; + char sep; + int af; + int ret; + int port; + + usedefault = 0; + if (pdata >= 0) { + close(pdata); + pdata = -1; + } + + sep = *str++; + if (sep == '\0') { + reply(500, "Bad syntax in EPRT"); + return; + } + af = strtol (str, &end, 0); + if (af == 0 || *end != sep) { + reply(500, "Bad syntax in EPRT"); + return; + } + str = end + 1; + switch (af) { +#ifdef HAVE_IPV6 + case 2 : + data_dest->sa_family = AF_INET6; + break; +#endif + case 1 : + data_dest->sa_family = AF_INET; + break; + default : + reply(522, "Network protocol %d not supported, use (1" +#ifdef HAVE_IPV6 + ",2" +#endif + ")", af); + return; + } + end = strchr (str, sep); + if (end == NULL) { + reply(500, "Bad syntax in EPRT"); + return; + } + *end = '\0'; + ret = inet_pton (data_dest->sa_family, str, + socket_get_address (data_dest)); + + if (ret != 1) { + reply(500, "Bad address syntax in EPRT"); + return; + } + str = end + 1; + port = strtol (str, &end, 0); + if (port == 0 || *end != sep) { + reply(500, "Bad port syntax in EPRT"); + return; + } + socket_set_port (data_dest, htons(port)); + reply(200, "EPRT command successful."); +} + +/* + * 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(char *local) +{ + static char new[MaxPathLen]; + struct stat st; + int count; + char *cp; + + cp = strrchr(local, '/'); + if (cp) + *cp = '\0'; + if (stat(cp ? local : ".", &st) < 0) { + perror_reply(553, cp ? local : "."); + return NULL; + } + if (cp) + *cp = '/'; + for (count = 1; count < 100; count++) { + snprintf (new, sizeof(new), "%s.%d", local, 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(int code, const char *string) +{ + reply(code, "%s: %s.", string, strerror(errno)); +} + +static char *onefile[] = { + "", + 0 +}; + +void +list_file(char *file) +{ + if(use_builtin_ls) { + FILE *dout; + dout = dataconn(file, -1, "w"); + if (dout == NULL) + return; + set_buffer_size(fileno(dout), 0); + builtin_ls(dout, file); + reply(226, "Transfer complete."); + fclose(dout); + data = -1; + pdata = -1; + } else { +#ifdef HAVE_LS_A + const char *cmd = "/bin/ls -lA -- %s"; +#else + const char *cmd = "/bin/ls -la -- %s"; +#endif + retrieve(cmd, file); + } +} + +void +send_file_list(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; + char buf[MaxPathLen]; + + if (strpbrk(whichf, "~{[*?") != NULL) { + int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE; + + memset(&gl, 0, sizeof(gl)); + freeglob = 1; + if (glob(whichf, flags, 0, &gl)) { + reply(550, "not found"); + goto out; + } else if (gl.gl_pathc == 0) { + errno = ENOENT; + perror_reply(550, whichf); + goto out; + } + dirlist = gl.gl_pathv; + } else { + onefile[0] = whichf; + dirlist = onefile; + simple = 1; + } + + if (setjmp(urgcatch)) { + transflag = 0; + goto out; + } + while ((dirname = *dirlist++)) { + if (stat(dirname, &st) < 0) { + /* + * If user typed "ls -l", etc, and the client + * used NLST, do what the user meant. + */ + if (dirname[0] == '-' && *dirlist == NULL && + transflag == 0) { + retrieve("/bin/ls -- %s", dirname); + goto out; + } + perror_reply(550, whichf); + if (dout != NULL) { + 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++; + } + snprintf(buf, sizeof(buf), "%s%s\n", dirname, + type == TYPE_A ? "\r" : ""); + sec_write(fileno(dout), buf, strlen(buf)); + 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 (!strcmp(dir->d_name, ".")) + continue; + if (!strcmp(dir->d_name, "..")) + continue; + + snprintf(nbuf, sizeof(nbuf), "%s/%s", dirname, dir->d_name); + + /* + * We have to do a stat to insure it's + * not a directory or special file. + */ + if (simple || (stat(nbuf, &st) == 0 && + S_ISREG(st.st_mode))) { + if (dout == NULL) { + dout = dataconn("file list", (off_t)-1, "w"); + if (dout == NULL) + goto out; + transflag++; + } + if(strncmp(nbuf, "./", 2) == 0) + snprintf(buf, sizeof(buf), "%s%s\n", nbuf +2, + type == TYPE_A ? "\r" : ""); + else + snprintf(buf, sizeof(buf), "%s%s\n", nbuf, + type == TYPE_A ? "\r" : ""); + sec_write(fileno(dout), buf, strlen(buf)); + byte_count += strlen(nbuf) + 1; + } + } + 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){ + sec_write(fileno(dout), buf, 0); /* XXX flush */ + + fclose(dout); + } + data = -1; + pdata = -1; +out: + if (freeglob) { + freeglob = 0; + globfree(&gl); + } +} + + +int +find(char *pattern) +{ + char line[1024]; + FILE *f; + + snprintf(line, sizeof(line), + "/bin/locate -d %s -- %s", + ftp_rooted("/etc/locatedb"), + pattern); + f = ftpd_popen(line, "r", 1, 1); + if(f == NULL){ + perror_reply(550, "/bin/locate"); + return 1; + } + lreply(200, "Output from find."); + while(fgets(line, sizeof(line), f)){ + if(line[strlen(line)-1] == '\n') + line[strlen(line)-1] = 0; + nreply("%s", line); + } + reply(200, "Done"); + ftpd_pclose(f); + return 0; +} + |