/* * 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 #endif #include "getarg.h" RCSID("$Id$"); 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; 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 restricted_data_ports = 1; 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 allow_insecure_oob = 1; static int transflag; static int urgflag; 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]; int paranoid = 1; #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 handleoobcmd(void); static int checkuser (char *, char *); static int checkaccess (char *); static FILE *dataconn (const char *, off_t, const char *); static void dolog (struct sockaddr *, int); static void end_login (void); static FILE *getdatasock (const char *, int); 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 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; static const char *good_chars = "+-=_,."; 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, 'U', arg_negative_flag, &restricted_data_ports, "don't use high data ports" }, { NULL, 'd', arg_flag, &debug, "enable debugging" }, { NULL, 'v', arg_flag, &debug, "enable debugging" }, { "builtin-ls", 'B', arg_flag, &use_builtin_ls, "use built-in ls to list files" }, { "good-chars", 0, arg_string, &good_chars, "allowed anonymous upload filename chars" }, { "insecure-oob", 'I', arg_negative_flag, &allow_insecure_oob, "don't allow insecure OOB ABOR/STAT" }, #ifdef KRB5 { "gss-bindings", 0, arg_flag, &ftp_do_gss_bindings, "Require GSS-API bindings", NULL}, #endif { "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) { socklen_t his_addr_len, ctrl_addr_len; int on = 1; int port; struct servent *sp; int optind = 0; setprogname (argv[0]); 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; } } sp = getservbyname("ftp", "tcp"); if(sp) port = sp->s_port; else port = htons(21); if(port_string) { sp = getservbyname(port_string, "tcp"); if(sp) port = sp->s_port; else if(isdigit((unsigned char)port_string[0])) port = htons(atoi(port_string)); else warnx("bad value for -p"); } if (maxtimeout < ftpd_timeout) maxtimeout = ftpd_timeout; #if 0 if (ftpd_timeout > maxtimeout) ftpd_timeout = maxtimeout; #endif if(interactive_flag) mini_inetd(port, NULL); /* * 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) if (ctrl_addr->sa_family == AF_INET) socket_set_tos(STDIN_FILENO, IP_TOS); #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 ") ready.", hostname, version #ifdef KRB5 ,heimdal_version #endif ); 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); sec_session(name); } else reply(530, "User %s access denied.", name); } else { #ifdef OTP char ss[256]; 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 { #ifdef OTP char *s; 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) { 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); #if defined(KRB5) if(k_hasafs()) k_setpag(); #endif /* 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("%s", 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("%s", 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) { if (seteuid((uid_t)0) < 0) fatal("Failed to seteuid"); if (logged_in) ftpd_logwtmp(ttyline, "", ""); pw = NULL; logged_in = 0; guest = 0; dochroot = 0; } #ifdef KRB5 static int krb5_verify(struct passwd *pwd, char *passwd) { krb5_context context; krb5_ccache id; krb5_principal princ; krb5_error_code ret; ret = krb5_init_context(&context); if(ret) return ret; ret = krb5_parse_name(context, pwd->pw_name, &princ); if(ret){ krb5_free_context(context); return ret; } ret = krb5_cc_new_unique(context, "MEMORY", NULL, &id); if(ret){ krb5_free_principal(context, princ); krb5_free_context(context); return ret; } ret = krb5_verify_user(context, princ, id, passwd, 1, NULL); krb5_free_principal(context, princ); if (k_hasafs()) { krb5_afslog_uid_home(context, id,NULL, NULL,pwd->pw_uid, pwd->pw_dir); } krb5_cc_destroy(context, id); krb5_free_context (context); if(ret) return ret; return 0; } #endif /* KRB5 */ void pass(char *passwd) { int rval; /* some clients insists on sending a password */ if (logged_in && askpasswd == 0){ reply(230, "Password not necessary"); 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 KRB5 rval = krb5_verify(pw, passwd); #endif if (rval) rval = unix_verify_user(pw->pw_name, passwd); } else { #ifdef OTP char *s; 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; int ret; ret = asprintf(&ext, "%s%s", name, p->ext); if (ret != -1) { 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) { char *p; p = strrchr(filename, '/'); if(p) filename = p + 1; p = filename; if(isalnum((unsigned char)*p)){ p++; while(*p && (isalnum((unsigned char)*p) || strchr(good_chars, (unsigned char)*p))) p++; if(*p == '\0') return 0; } lreply(553, "\"%s\" is not an acceptable 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) { char *uname; if (stat(name, &st) == 0) { if ((uname = gunique(name)) == NULL) return; name = uname; } LOGCMD(*mode == 'w' ? "put" : "append", name); } 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((*closefunc)(fout) < 0) perror_reply(552, name); else { if (unique) reply(226, "Transfer complete (unique file name:%s).", name); else reply(226, "Transfer complete."); } } else (*closefunc)(fout); fclose(din); data = -1; pdata = -1; done: LOGBYTES(*mode == 'w' ? "put" : "append", name, byte_count); } static FILE * getdatasock(const char *mode, int domain) { int s, t, tries; if (data >= 0) return (fdopen(data, mode)); if (seteuid(0) < 0) fatal("Failed to seteuid"); s = socket(domain, 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); } if (seteuid(pw->pw_uid) < 0) fatal("Failed to seteuid"); #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; if (seteuid((uid_t)pw->pw_uid) < 0) fatal("Failed to seteuid"); close(s); errno = t; return (NULL); } static int accept_with_timeout(int socket, struct sockaddr *address, socklen_t *address_len, struct timeval *timeout) { int ret; fd_set rfd; FD_ZERO(&rfd); FD_SET(socket, &rfd); ret = select(socket + 1, &rfd, NULL, NULL, timeout); if(ret < 0) return ret; if(ret == 0) { errno = ETIMEDOUT; return -1; } return accept(socket, address, address_len); } static FILE * dataconn(const char *name, off_t size, const char *mode) { char sizebuf[32]; FILE *file; int domain, 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; struct timeval timeout; int s; socklen_t fromlen = sizeof(from_ss); timeout.tv_sec = 15; timeout.tv_usec = 0; s = accept_with_timeout(pdata, from, &fromlen, &timeout); if (s < 0) { reply(425, "Can't open data connection."); close(pdata); pdata = -1; return (NULL); } close(pdata); pdata = s; #if defined(IPTOS_THROUGHPUT) if (from->sa_family == AF_INET) socket_set_tos(s, IPTOS_THROUGHPUT); #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; /* * Default to using the same socket type as the ctrl address, * unless we know the type of the data address. */ domain = data_dest->sa_family; if (domain == PF_UNSPEC) domain = ctrl_addr->sa_family; file = getdatasock(mode, domain); 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 = 1; switch (type) { case TYPE_A: while ((c = getc(instr)) != EOF) { if (urgflag && handleoobcmd()) return; byte_count++; if(c == '\n') sec_putc('\r', outstr); sec_putc(c, outstr); } sec_fflush(outstr); transflag = 0; urgflag = 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 0 /* XXX handle urg flag */ #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; urgflag = 0; } } } #endif #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; urgflag = 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; if (urgflag && handleoobcmd()) return; } sec_fflush(outstr); /* to end an encrypted stream */ transflag = 0; urgflag = 0; if (cnt != 0) { if (cnt < 0) goto file_err; goto data_err; } } reply(226, "Transfer complete."); return; default: transflag = 0; urgflag = 0; reply(550, "Unimplemented TYPE %d in send_data", type); return; } data_err: transflag = 0; urgflag = 0; perror_reply(426, "Data connection"); return; file_err: transflag = 0; urgflag = 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 = 1; buf = alloc_buffer (buf, &bufsize, fstat(fileno(outstr), &st) >= 0 ? &st : NULL); if (buf == NULL) { transflag = 0; urgflag = 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 (urgflag && handleoobcmd()) return (-1); } if (cnt < 0) goto data_err; transflag = 0; urgflag = 0; return (0); case TYPE_E: reply(553, "TYPE E not implemented."); transflag = 0; urgflag = 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){ if (urgflag && handleoobcmd()) return (-1); 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; urgflag = 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; urgflag = 0; return (-1); } data_err: transflag = 0; urgflag = 0; perror_reply(426, "Data Connection"); return (-1); file_err: transflag = 0; urgflag = 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((unsigned char)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); } void do_delete(char *name) { struct stat st; LOGCMD("delete", name); if (stat(name, &st) < 0) { perror_reply(550, name); return; } if (S_ISDIR(st.st_mode)) { 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(const 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("%s", 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; urgflag = 0; if (logged_in) { #if KRB5 cond_kdestroy(); #endif seteuid((uid_t)0); /* No need to check, we call exit() below */ ftpd_logwtmp(ttyline, "", ""); } /* beware of flushing buffers after a SIGPIPE */ #ifdef XXX exit(status); #else _exit(status); #endif } void abor(void) { if (!transflag) return; reply(426, "Transfer aborted. Data connection closed."); reply(226, "Abort successful"); transflag = 0; } static void myoob(int signo) { urgflag = 1; } static char * mec_space(char *p) { while(isspace(*(unsigned char *)p)) p++; return p; } static int handleoobcmd(void) { char *cp; /* only process if transfer occurring */ if (!transflag) return 0; urgflag = 0; cp = tmpline; if (ftpd_getline(cp, sizeof(tmpline)) == NULL) { reply(221, "You could at least say goodbye."); dologout(0); } if (strncasecmp("MIC", cp, 3) == 0) { mec(mec_space(cp + 3), prot_safe); } else if (strncasecmp("CONF", cp, 4) == 0) { mec(mec_space(cp + 4), prot_confidential); } else if (strncasecmp("ENC", cp, 3) == 0) { mec(mec_space(cp + 3), prot_private); } else if (!allow_insecure_oob) { reply(533, "Command protection level denied " "for paranoid reasons."); goto out; } if (secure_command()) cp = ftp_command; if (strcasecmp(cp, "ABOR\r\n") == 0) { abor(); } else if (strcasecmp(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); } out: return (transflag == 0); } /* * 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) { socklen_t 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; } if(pdata != -1) close(pdata); 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); socket_set_portrange(pdata, restricted_data_ports, pasv_addr->sa_family); if (seteuid(0) < 0) fatal("Failed to seteuid"); if (bind(pdata, pasv_addr, socket_sockaddr_size (pasv_addr)) < 0) { if (seteuid(pw->pw_uid) < 0) fatal("Failed to seteuid"); goto pasv_error; } if (seteuid(pw->pw_uid) < 0) fatal("Failed to seteuid"); 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) { socklen_t 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); socket_set_portrange(pdata, restricted_data_ports, pasv_addr->sa_family); if (seteuid(0) < 0) fatal("Failed to seteuid"); if (bind(pdata, pasv_addr, socket_sockaddr_size (pasv_addr)) < 0) { if (seteuid(pw->pw_uid)) fatal("Failed to seteuid"); goto pasv_error; } if (seteuid(pw->pw_uid) < 0) fatal("Failed to seteuid"); 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; } if (port < IPPORT_RESERVED) { reply(500, "Bad port in invalid range in EPRT"); return; } socket_set_port (data_dest, htons(port)); if (paranoid && (data_dest->sa_family != his_addr->sa_family || memcmp(socket_get_address(data_dest), socket_get_address(his_addr), socket_sockaddr_size(data_dest)) != 0)) { reply(500, "Bad address in EPRT"); } 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); if(builtin_ls(dout, file) == 0) reply(226, "Transfer complete."); else reply(451, "Requested action aborted. Local error in processing."); 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| #ifdef GLOB_MAXPATH GLOB_MAXPATH #else GLOB_LIMIT #endif ; 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; } while ((dirname = *dirlist++)) { if (urgflag && handleoobcmd()) goto out; 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) { list_file(dirname); goto out; } perror_reply(550, whichf); 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 = 1; } 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 (urgflag && handleoobcmd()) goto out; 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 = 1; } 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."); out: transflag = 0; if (dout != NULL){ sec_write(fileno(dout), buf, 0); /* XXX flush */ fclose(dout); } data = -1; pdata = -1; if (freeglob) 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; }