/
niceSOFT
/
inetutils
Обзор
Документация
Войти
/
niceSOFT
/
inetutils
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
ftpd/ftpd.c
2 262 строки
53 KB
Collin Funk
ftpd: assume char is 8 bits
13 апр 2026, 01:37
13 апр 2026, 01:37
e5761ca
Код
Авторство
О чём код?
/* Copyright (C) 1994-2026 Free Software Foundation, Inc. This file is part of GNU Inetutils. GNU Inetutils is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNU Inetutils is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see `http://www.gnu.org/licenses/'. */ /* - Ftp Server * 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. 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. */ /* * FTP server. */ #include <config.h> #include <alloca.h> #include <sys/param.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/wait.h> #include <netinet/in.h> #ifdef HAVE_NETINET_IN_SYSTM_H # include <netinet/in_systm.h> #endif #ifdef HAVE_NETINET_IP_H # include <netinet/ip.h> #endif #define FTP_NAMES 1 #include <arpa/ftp.h> #include <arpa/inet.h> #include <arpa/telnet.h> #include <ctype.h> #include <dirent.h> #include <errno.h> #include <fcntl.h> #include <limits.h> #include <netdb.h> #include <setjmp.h> #include <signal.h> #include <grp.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <syslog.h> #include <sys/time.h> #include <time.h> #include <unistd.h> #ifdef HAVE_MMAP # include <sys/mman.h> #endif /* Include glob.h last, because it may define "const" which breaks system headers on some platforms. */ #include <glob.h> #include <argp.h> #include <error.h> #include <xgetcwd.h> #include <progname.h> #include <libinetutils.h> #include "extern.h" #include "attribute.h" #ifndef LINE_MAX # define LINE_MAX 2048 #endif #ifndef LOG_FTP # define LOG_FTP LOG_DAEMON /* Use generic facility. */ #endif #ifndef MAP_FAILED # define MAP_FAILED (void*)-1 #endif /* Exported to ftpcmd.h. */ struct sockaddr_storage data_dest; /* Data port. */ socklen_t data_dest_len; struct sockaddr_storage his_addr; /* Peer address. */ socklen_t his_addrlen; int logging; /* Enable log to syslog. */ int no_version; /* Don't print version to client. */ int type = TYPE_A; /* Default TYPE_A. */ int form = FORM_N; /* Default FORM_N. */ int debug; /* Enable debug mode if 1. */ int rfc2577 = 1; /* Follow suggestions in RFC 2577. */ int timeout = 900; /* Timeout after 15 minutes of inactivity. */ int maxtimeout = 7200; /* Don't allow idle time to be set beyond 2 hours. */ int pdata = -1; /* For passive mode. */ char *hostname; /* Who we are. */ int usedefault = 1; /* For data transfers. */ char tmpline[7]; /* Temp buffer use in OOB. */ char addrstr[NI_MAXHOST]; /* Host name or address string. */ char portstr[8]; /* Numeric port as string. */ /* Requester credentials. */ struct credentials cred; jmp_buf errcatch; static struct sockaddr_storage ctrl_addr; /* Control address. */ static socklen_t ctrl_addrlen; static struct sockaddr_storage data_source; /* Port address. */ static socklen_t data_source_len; static struct sockaddr_storage pasv_addr; /* Pasv address. */ static socklen_t pasv_addrlen; static int data = -1; /* Port data connection socket. */ static jmp_buf urgcatch; static int stru = STRU_F; /* Avoid C keyword. */ static int stru_mode = MODE_S; /* Default STRU mode stru_mode = MODE_S. */ static int anon_only; /* Allow only anonymous login. */ static int daemon_mode; /* Start in daemon mode. */ static off_t file_size; static off_t byte_count; static sig_atomic_t transflag; /* Flag where in a middle of transfer. */ static const char *pid_file = PATH_FTPDPID; #if !defined CMASK || CMASK == 0 # undef CMASK # define CMASK 027 #endif static int defumask = CMASK; /* Default umask value. */ 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. */ static char ttyline[20]; /* Line to log in utmp. */ #define NUM_SIMUL_OFF_TO_STRS 4 /* Returns a string with the decimal representation of the off_t OFF, taking into account that off_t might be longer than a long. The return value is a pointer to a static buffer, but a return value will only be reused every NUM_SIMUL_OFF_TO_STRS calls, to allow multiple off_t's to be conveniently printed with a single printf statement. */ static char * off_to_str (off_t off) { static char bufs[NUM_SIMUL_OFF_TO_STRS][80]; static char (*next_buf)[80] = bufs; if (next_buf >= (bufs + NUM_SIMUL_OFF_TO_STRS)) next_buf = bufs; if (sizeof (off) > sizeof (long)) sprintf (*next_buf, "%lld", (long long int) off); else if (sizeof (off) == sizeof (long)) sprintf (*next_buf, "%ld", (long) off); else sprintf (*next_buf, "%d", (int) off); return *next_buf++; } /* * 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 */ static int swaitmax = SWAITMAX; static int swaitint = SWAITINT; #ifdef HAVE_SETPROCTITLE char proctitle[LINE_MAX]; /* initial part of title */ #endif /* SETPROCTITLE */ #define LOGCMD(cmd, file) \ if (logging > 1) \ syslog(LOG_INFO,"%s %s%s", cmd, \ *(file) == '/' ? "" : curdir(), file); #define LOGCMD2(cmd, file1, file2) \ if (logging > 1) \ syslog(LOG_INFO,"%s %s%s %s%s", cmd, \ *(file1) == '/' ? "" : curdir(), file1, \ *(file2) == '/' ? "" : curdir(), file2); #define LOGBYTES(cmd, file, cnt) \ if (logging > 1) { \ if (cnt == (off_t)-1) \ syslog(LOG_INFO,"%s %s%s", cmd, \ *(file) == '/' ? "" : curdir(), file); \ else \ syslog(LOG_INFO, "%s %s%s = %s bytes", \ cmd, (*(file) == '/') ? "" : curdir(), file, \ off_to_str (cnt)); \ } extern int yyparse (void); static void ack (const char *); #ifdef HAVE_LIBWRAP static int check_host (struct sockaddr *sa, socklen_t len); #endif static void complete_login (struct credentials *); static char *curdir (void); static FILE *dataconn (const char *, off_t, const char *); static void dolog (struct sockaddr *, socklen_t, struct credentials *); static void end_login (struct credentials *); static FILE *getdatasock (const char *); static char *gunique (const char *); static void lostconn (int); static void myoob (int); static int receive_data (FILE *, FILE *, off_t); static void send_data (FILE *, FILE *, off_t); static void sigquit (int); const char doc[] = #ifdef WITH_PAM "File Transfer Protocol daemon, offering PAM service 'ftp'."; #else "File Transfer Protocol daemon."; #endif enum { OPT_NONRFC2577 = CHAR_MAX + 1, }; static struct argp_option options[] = { #define GRID 0 {"anonymous-only", 'A', NULL, 0, "server configured for anonymous service only", GRID + 1}, {"daemon", 'D', NULL, 0, "start the ftpd standalone", GRID + 1}, {"debug", 'd', NULL, 0, "debug mode", GRID + 1}, {"ipv4", '4', NULL, 0, "restrict daemon to IPv4", GRID + 1}, {"ipv6", '6', NULL, 0, "restrict daemon to IPv6", GRID + 1}, {"logging", 'l', NULL, 0, "increase verbosity of syslog messages", GRID + 1}, {"pidfile", 'p', "PIDFILE", OPTION_ARG_OPTIONAL, "change default location of pidfile", GRID + 1}, {"no-version", 'q', NULL, 0, "do not display version in banner", GRID + 1}, {"timeout", 't', "TIMEOUT", 0, "set default idle timeout", GRID + 1}, {"max-timeout", 'T', "MAX_TIMEOUT", 0, "set maximum value of timeout allowed", GRID + 1}, {"non-rfc2577", OPT_NONRFC2577, NULL, 0, "neglect RFC 2577 by giving info on missing users", GRID + 1}, {"umask", 'u', "VAL", 0, "set default umask", GRID + 1}, {"auth", 'a', "AUTH", 0, "use AUTH for authentication", GRID + 1}, {NULL, 0, NULL, 0, "AUTH can be one of the following:", GRID + 2}, {" default", 0, NULL, OPTION_DOC | OPTION_NO_TRANS, "passwd authentication", GRID + 3}, #ifdef WITH_PAM {" pam", 0, NULL, OPTION_DOC | OPTION_NO_TRANS, "using PAM service 'ftp'", GRID + 3}, #endif #ifdef WITH_KERBEROS {" kerberos", 0, NULL, OPTION_DOC | OPTION_NO_TRANS, "", GRID + 3}, #endif #ifdef WITH_KERBEROS5 {" kerberos5", 0, NULL, OPTION_DOC | OPTION_NO_TRANS, "", GRID + 3}, #endif #ifdef WITH_OPIE {" opie", 0, NULL, OPTION_DOC | OPTION_NO_TRANS, "", GRID + 3}, #endif {NULL, 0, NULL, 0, NULL, 0} }; static error_t parse_opt (int key, char *arg, struct argp_state *state) { switch (key) { case '4': /* Active in daemon mode only. */ usefamily = AF_INET; break; case '6': /* Active in daemon mode only. */ usefamily = AF_INET6; break; case 'A': /* Anonymous ftp only. */ anon_only = 1; break; case 'a': if (strcasecmp (arg, "default") == 0) cred.auth_type = AUTH_TYPE_PASSWD; #ifdef WITH_PAM else if (strcasecmp (arg, "pam") == 0) cred.auth_type = AUTH_TYPE_PAM; #endif #ifdef WITH_KERBEROS else if (strcasecmp (arg, "kerberos") == 0) cred.auth_type = AUTH_TYPE_KERBEROS; #endif #ifdef WITH_KERBEROS5 else if (strcasecmp (arg, "kerberos5") == 0) cred.auth_type = AUTH_TYPE_KERBEROS5; #endif #ifdef WITH_OPIE else if (strcasecmp (arg, "opie") == 0) cred.auth_type = AUTH_TYPE_OPIE; #endif break; case 'D': /* Run ftpd as daemon. */ daemon_mode = 1; break; case 'd': /* Enable debug mode. */ debug = 1; break; case 'l': /* Increase logging level. */ logging++; /* > 1 == Extra logging. */ break; case 'p': /* Override pid file */ pid_file = arg; break; case 'q': /* Don't include version number in banner. */ no_version = 1; break; case 't': /* Set default timeout value. */ timeout = atoi (arg); if (maxtimeout < timeout) maxtimeout = timeout; break; case 'T': /* Maximum timeout allowed. */ maxtimeout = atoi (arg); if (timeout > maxtimeout) timeout = maxtimeout; break; case 'u': /* Set umask. */ { long val = 0; val = strtol (arg, &arg, 8); if (*arg != '\0' || val < 0) argp_error (state, "bad value for -u"); else defumask = val; break; } case OPT_NONRFC2577: rfc2577 = 0; break; default: return ARGP_ERR_UNKNOWN; } return 0; } static struct argp argp = { options, parse_opt, NULL, doc, NULL, NULL, NULL }; int main (int argc, char *argv[], char **envp) { int index; set_program_name (argv[0]); #ifdef HAVE_TZSET tzset (); /* In case no timezone database in ~ftp. */ #endif #ifdef HAVE_INITSETPROCTITLE /* Save start and extent of argv for setproctitle. */ initsetproctitle (argc, argv, envp); #else /* !HAVE_INITSETPROCTITLE */ (void) envp; /* Silence warnings. */ #endif /* Parse the command line */ iu_argp_init ("ftpd", default_program_authors); argp_parse (&argp, argc, argv, 0, &index, NULL); /* Bail out, wrong usage */ if (argc - index != 0) error (EXIT_FAILURE, 0, "surplus arguments; try `%s --help' for more info", program_name); /* 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); freopen (PATH_DEVNULL, "w", stderr); /* If not running via inetd, we detach and dup(fd, 0), dup(fd, 1) the fd = accept(). tcpd is check if compile with the support */ if (daemon_mode) { #ifndef HAVE_FORK /* Shift out the daemon option in subforks */ int i; for (i = 0; i < argc; ++i) if (strcmp (argv[i], "-D") == 0) { int j; for (j = i; j < argc; ++j) argv[j] = argv[j + 1]; argv[--argc] = NULL; } #endif his_addrlen = sizeof (his_addr); if (server_mode (pid_file, (struct sockaddr *) &his_addr, &his_addrlen, argv) < 0) exit (EXIT_FAILURE); } else { his_addrlen = sizeof (his_addr); if (getpeername (STDIN_FILENO, (struct sockaddr *) &his_addr, &his_addrlen) < 0) { syslog (LOG_ERR, "getpeername (%s): %m", program_name); exit (EXIT_FAILURE); } } signal (SIGHUP, sigquit); signal (SIGINT, sigquit); signal (SIGQUIT, sigquit); signal (SIGTERM, sigquit); signal (SIGPIPE, lostconn); signal (SIGCHLD, SIG_IGN); if (signal (SIGURG, myoob) == SIG_ERR) syslog (LOG_ERR, "signal: %m"); /* Get info on the ctrl connection. */ { ctrl_addrlen = sizeof (ctrl_addr); if (getsockname (STDIN_FILENO, (struct sockaddr *) &ctrl_addr, &ctrl_addrlen) < 0) { syslog (LOG_ERR, "getsockname (%s): %m", program_name); exit (EXIT_FAILURE); } } #if defined IP_TOS && defined IPTOS_LOWDELAY && defined IPPROTO_IP /* To minimize delays for interactive traffic. */ if (ctrl_addr.ss_family == AF_INET) { int tos = IPTOS_LOWDELAY; if (setsockopt (STDIN_FILENO, IPPROTO_IP, IP_TOS, (char *) &tos, sizeof (int)) < 0) syslog (LOG_WARNING, "setsockopt (IP_TOS): %m"); } #endif #ifdef SO_OOBINLINE /* Try to handle urgent data inline. */ { int on = 1; if (setsockopt (STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, (char *) &on, sizeof (on)) < 0) syslog (LOG_ERR, "setsockopt: %m"); } #endif #ifdef SO_KEEPALIVE /* Set keepalives on the socket to detect dropped connections. */ { int keepalive = 1; if (setsockopt (STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, (char *) &keepalive, sizeof (keepalive)) < 0) syslog (LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m"); } #endif #ifdef F_SETOWN if (fcntl (STDIN_FILENO, F_SETOWN, getpid ()) == -1) syslog (LOG_ERR, "fcntl F_SETOWN: %m"); #endif dolog ((struct sockaddr *) &his_addr, his_addrlen, &cred); /* Deal with login disable. */ if (display_file (PATH_NOLOGIN, 530) == 0) { reply (530, "System not available."); exit (EXIT_SUCCESS); } hostname = localhost (); if (!hostname) perror_reply (550, "Local resource failure: malloc"); /* Display a Welcome message if it exists. N.B. a reply(220,) must follow as continuation. */ display_file (PATH_FTPWELCOME, 220); /* Tell them we're ready to roll. */ if (!no_version) reply (220, "%s FTP server (%s %s) ready.", hostname, PACKAGE_NAME, PACKAGE_VERSION); else reply (220, "%s FTP server ready.", hostname); /* Set the jump, if we have an error parsing, come here and start fresh. */ setjmp (errcatch); /* Roll. */ for (;;) yyparse (); } static char * curdir (void) { static char *path = 0; free (path); path = xgetcwd (); if (!path) return (char *) ""; if (path[1] != '\0') /* special case for root dir. */ { char *tmp = realloc (path, strlen (path) + 2); /* '/' + '\0' */ if (!tmp) { free (path); return (char *) ""; } strcat (tmp, "/"); path = tmp; } /* For guest account, skip / since it's chrooted */ return (cred.guest ? path + 1 : path); } static void sigquit (int signo) { syslog (LOG_ERR, "got signal %s", strsignal (signo)); dologout (-1); } static void lostconn (int signo MAYBE_UNUSED) { if (debug) syslog (LOG_DEBUG, "lost connection"); dologout (-1); } /* Helper function. */ char * sgetsave (const char *s) { char *string; size_t len; if (s == NULL) s = ""; len = strlen (s) + 1; string = malloc (len); if (string == NULL) { perror_reply (421, "Local resource failure: malloc"); dologout (1); } /* (void) strcpy (string, s); */ memcpy (string, s, len); return string; } static void complete_login (struct credentials *pcred) { char *cwd; if (setegid ((gid_t) pcred->gid) < 0) { reply (550, "Can't set gid."); goto bad; } #ifdef HAVE_INITGROUPS initgroups (pcred->name, pcred->gid); #endif /* open wtmp before chroot */ snprintf (ttyline, sizeof (ttyline), "ftp%d", (int) getpid ()); logwtmp_keep_open (ttyline, pcred->name, pcred->remotehost); if (pcred->guest) { /* We MUST do a chdir () after the chroot. Otherwise the old current directory will be accessible as "." outside the new root! */ if (chroot (pcred->rootdir) < 0 || chdir (pcred->homedir) < 0) { reply (550, "Can't set guest privileges."); goto bad; } } else if (pcred->dochroot) { if (chroot (pcred->rootdir) < 0 || chdir (pcred->homedir) < 0) { reply (550, "Can't change root."); goto bad; } } if (seteuid ((uid_t) pcred->uid) < 0) { reply (550, "Can't set uid."); goto bad; } if (!pcred->guest && !pcred->dochroot) /* Remaining case. */ { if (chdir (pcred->rootdir) < 0) { if (chdir ("/") < 0) { reply (530, "User %s: can't change directory to %s.", pcred->name, pcred->homedir); goto bad; } lreply (230, "No directory! Logging in with home=/"); } } cwd = xgetcwd (); if (cwd) { setenv ("HOME", cwd, 1); free (cwd); } /* Display a login message, if it exists. N.B. a reply(230,) must follow after this message. */ display_file (PATH_FTPLOGINMESG, 230); if (pcred->guest) { reply (230, "Guest login ok, access restrictions apply."); #ifdef HAVE_SETPROCTITLE snprintf (proctitle, sizeof (proctitle), "%s: anonymous", pcred->remotehost); setproctitle ("%s", proctitle); #endif /* HAVE_SETPROCTITLE */ if (logging) syslog (LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s", pcred->remotehost); } else { reply (230, "User %s logged in.", pcred->name); #ifdef HAVE_SETPROCTITLE snprintf (proctitle, sizeof (proctitle), "%s: %s", pcred->remotehost, pcred->name); setproctitle ("%s", proctitle); #endif /* HAVE_SETPROCTITLE */ if (logging) syslog (LOG_INFO, "FTP LOGIN FROM %s as %s", pcred->remotehost, pcred->name); } umask (defumask); return; bad: /* Forget all about it... */ end_login (pcred); /* Resets pcred->logged_in. */ } /* 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. */ void user (const char *name) { int ret; if (cred.logged_in) { if (cred.guest || cred.dochroot) { reply (530, "Can't change user from guest login."); return; } end_login (&cred); } /* Non zero means failed. */ ret = auth_user (name, &cred); if (!rfc2577 && ret != 0) { /* If they gave us a reason. */ if (cred.message) { reply (530, "%s", cred.message); free (cred.message); cred.message = NULL; } else reply (530, "User %s access denied.", name); if (logging) syslog (LOG_NOTICE, "FTP LOGIN REFUSED FROM %s, %s", cred.remotehost, name); return; } else if (rfc2577 && ret != 0) cred.delayed_reject = 1; else cred.delayed_reject = 0; /* Only messages for anonymous guests are accepted. */ if (rfc2577 && !cred.guest && cred.message) { free (cred.message); cred.message = NULL; } /* If the server is set to serve anonymous service only the request have to come from a guest or a chrooted. */ if (anon_only && !cred.guest && !cred.dochroot) { reply (530, "Sorry, only anonymous ftp allowed"); return; } if (logging) { strncpy (curname, name, sizeof (curname) - 1); curname[sizeof (curname) - 1] = '\0'; /* Make sure null terminated. */ } if (cred.message) { /* Stacked PAM modules for authentication may have * produced a multiline message at this point. * The FTP protocol does not cope well with this, * so we transfer only the very last line, which * should reflect the active authentication mechanism. */ char *msg = strrchr (cred.message, '\n'); if (msg) msg++; /* Step over separator. */ else msg = cred.message; reply (331, "%s", msg); free (cred.message); cred.message = NULL; } else reply (331, "Password required for %s.", name); askpasswd = 1; /* Delay before reading passwd after first failed attempt to slow down passwd-guessing programs. */ if (login_attempts) sleep ((unsigned) login_attempts); } /* Terminate login as previous user, if any, resetting state; used when USER command is given or login fails. */ static void end_login (struct credentials *pcred) { char *remotehost = pcred->remotehost; int atype = pcred->auth_type; if (seteuid ((uid_t) 0) == -1) _exit (EXIT_FAILURE); if (pcred->logged_in) { logwtmp_keep_open (ttyline, "", ""); #ifdef WITH_PAM pam_end_login (pcred); #endif } free (pcred->name); if (pcred->passwd) { memset (pcred->passwd, 0, strlen (pcred->passwd)); free (pcred->passwd); } free (pcred->homedir); free (pcred->rootdir); free (pcred->shell); if (pcred->pass) /* Properly erase old password. */ { memset (pcred->pass, 0, strlen (pcred->pass)); free (pcred->pass); } free (pcred->message); memset (pcred, 0, sizeof (*pcred)); pcred->remotehost = remotehost; pcred->auth_type = atype; pcred->logged_in = 0; pcred->delayed_reject = 0; } void pass (const char *passwd) { if (cred.logged_in || askpasswd == 0) { reply (503, "Login with USER first."); return; } askpasswd = 0; if (!cred.guest) /* "ftp" is the only account allowed no password. */ { /* Try to authenticate the user. Failed if != 0. */ if (auth_pass (passwd, &cred) != 0 || cred.delayed_reject) { /* Any particular reason? */ if (rfc2577) { if (cred.message) { free (cred.message); cred.message = NULL; } reply (530, "Login incorrect."); } else if (cred.message) { reply (530, "%s", cred.message); free (cred.message); cred.message = NULL; } else if (cred.expired & AUTH_EXPIRED_ACCT) reply (530, "Account is expired."); else if (cred.expired & AUTH_EXPIRED_PASS) reply (530, "Password has expired."); else reply (530, "Login incorrect."); if (logging) syslog (LOG_NOTICE, "FTP LOGIN FAILED FROM %s, %s", cred.remotehost, curname); if (login_attempts++ >= 5) { syslog (LOG_NOTICE, "repeated login failures from %s", cred.remotehost); reply (421, "Service not available, closing control connection."); exit (EXIT_SUCCESS); } return; } if (cred.message) { /* At least PAM might have committed additional messages. * Reply code 230 is used, since at this point the client * has been accepted. */ lreply_multiline (230, cred.message); free (cred.message); cred.message = NULL; } } cred.logged_in = 1; /* Everything seems to be all right. */ complete_login (&cred); if (cred.logged_in) login_attempts = 0; /* This time successful. */ else ++login_attempts; } void retrieve (const char *cmd, const char *name) { FILE *fin, *dout; struct stat st; int (*closefunc) (FILE *); size_t buffer_size = BUFSIZ; /* Dynamic buffer. */ if (cmd == 0) { fin = fopen (name, "r"), closefunc = fclose; st.st_size = 0; } else { char line[BUFSIZ]; snprintf (line, sizeof line, cmd, name); name = line; fin = ftpd_popen (line, "r"), 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 && (fstat (fileno (fin), &st) < 0 || !S_ISREG (st.st_mode))) { reply (550, "%s: not a plain file.", name); goto done; } else if (cmd == 0) buffer_size = st.st_blksize; /* Depends on file system. */ if (restart_point) { if (type == TYPE_A) { off_t i, n; int c; n = restart_point; i = 0; while (i++ < n) { c = getc (fin); if (c == EOF) { /* Error code 554 was introduced in RFC 1123. */ reply (554, "Action not taken: invalid REST value %jd for %s.", restart_point, name); goto done; } if (c == '\n') i++; } } else if (lseek (fileno (fin), restart_point, SEEK_SET) < 0) { if (errno == EINVAL) reply (554, "Action not taken: invalid REST value %jd for %s.", restart_point, name); else perror_reply (550, name); goto done; } } dout = dataconn (name, st.st_size, "w"); if (dout == NULL) goto done; send_data (fin, dout, buffer_size); fclose (dout); data = -1; pdata = -1; done: if (cmd == 0) LOGBYTES ("get", name, byte_count); (*closefunc) (fin); } void store (const char *name, const char *mode, int unique) { FILE *fout, *din; struct stat st; int (*closefunc) (FILE *); if (unique && stat (name, &st) == 0) { const char *name_unique = gunique (name); if (name_unique) name = name_unique; else { LOGCMD (*mode == 'w' ? "put" : "append", name); return; } } if (restart_point) mode = "r+"; fout = fopen (name, mode); closefunc = fclose; if (fout == NULL || fstat (fileno (fout), &st) < 0) { 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) { c = getc (fout); if (c == EOF) { /* Error code 554 was introduced in RFC 1123. */ reply (554, "Action not taken: invalid REST value %jd for %s.", restart_point, name); goto done; } if (c == '\n') i++; } /* We must do this seek to "current" position because we are changing from reading to writing. */ if (fseeko (fout, 0L, SEEK_CUR) < 0) { perror_reply (550, name); goto done; } } else if (lseek (fileno (fout), restart_point, SEEK_SET) < 0) { if (errno == EINVAL) reply (554, "Action not taken: invalid REST value %jd for %s.", restart_point, name); else perror_reply (550, name); goto done; } } din = dataconn (name, (off_t) - 1, "r"); if (din == NULL) goto done; if (receive_data (din, fout, st.st_blksize) == 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); if (seteuid ((uid_t) 0) == -1) _exit (EXIT_FAILURE); s = socket (ctrl_addr.ss_family, SOCK_STREAM, 0); if (s < 0) goto bad; /* Enables local reuse address. */ { int on = 1; if (setsockopt (s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof (on)) < 0) goto bad; } /* Anchor socket to avoid multi-homing problems. */ memcpy (&data_source, &ctrl_addr, sizeof (data_source)); data_source_len = ctrl_addrlen; /* Erase port number, suggesting bind() to allocate a new port. */ switch (data_source.ss_family) { case AF_INET6: ((struct sockaddr_in6 *) &data_source)->sin6_port = 0; break; case AF_INET: ((struct sockaddr_in *) &data_source)->sin_port = 0; break; default: break; /* Do nothing; should not happen! */ } for (tries = 1;; tries++) { if (bind (s, (struct sockaddr *) &data_source, data_source_len) >= 0) break; if (errno != EADDRINUSE || tries > 10) goto bad; sleep (tries); } if (seteuid ((uid_t) cred.uid) != 0) _exit (EXIT_FAILURE); #if defined IP_TOS && defined IPTOS_THROUGHPUT && defined IPPROTO_IP if (ctrl_addr.ss_family == AF_INET) { int on = IPTOS_THROUGHPUT; if (setsockopt (s, IPPROTO_IP, IP_TOS, (char *) &on, sizeof (int)) < 0) syslog (LOG_WARNING, "setsockopt (IP_TOS): %m"); } #endif return (fdopen (s, mode)); bad: /* Return the real value of errno (close may change it) */ t = errno; if (seteuid ((uid_t) cred.uid) != 0) _exit (EXIT_FAILURE); 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 != (off_t) - 1) snprintf (sizebuf, sizeof (sizebuf), " (%s bytes)", off_to_str (size)); else *sizebuf = '\0'; if (pdata >= 0) { struct sockaddr_storage from; int s; socklen_t fromlen = sizeof (from); signal (SIGALRM, toolong); alarm ((unsigned) timeout); s = accept (pdata, (struct sockaddr *) &from, &fromlen); alarm (0); 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 IPTOS_THROUGHPUT && defined IPPROTO_IP /* Optimize throughput. */ if (from.ss_family == AF_INET) { int tos = IPTOS_THROUGHPUT; setsockopt (s, IPPROTO_IP, IP_TOS, (char *) &tos, sizeof (int)); } #endif #ifdef SO_KEEPALIVE /* Set keepalives on the socket to detect dropped conns. */ { int keepalive = 1; setsockopt (s, SOL_SOCKET, SO_KEEPALIVE, (char *) &keepalive, sizeof (int)); } #endif reply (150, "Opening %s mode data connection for '%s'%s.", type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); return fdopen (pdata, mode); } if (data >= 0) { reply (125, "Using existing data connection for '%s'%s.", name, sizebuf); usedefault = 1; return fdopen (data, mode); } if (usedefault) { memcpy (&data_dest, &his_addr, sizeof (data_dest)); data_dest_len = his_addrlen; } usedefault = 1; file = getdatasock (mode); if (file == NULL) { int oerrno = errno; (void) getnameinfo ((struct sockaddr *) &data_source, data_source_len, addrstr, sizeof (addrstr), portstr, sizeof (portstr), NI_NUMERICSERV); reply (425, "Can't create data socket (%s,%s): %s.", addrstr, portstr, strerror (oerrno)); return NULL; } data = fileno (file); while (connect (data, (struct sockaddr *) &data_dest, data_dest_len) < 0) { if (errno == EADDRINUSE && retry < swaitmax) { sleep ((unsigned) 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; } #define IU_MMAP_SIZE 0x800000 /* 8 MByte */ /* Transfer 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, off_t blksize) { int c, cnt, filefd, netfd; char *buf = MAP_FAILED, *bp; off_t curpos; off_t len, filesize; transflag++; if (setjmp (urgcatch)) { transflag = 0; return; } netfd = fileno (outstr); filefd = fileno (instr); #ifdef HAVE_MMAP /* Last argument in mmap() must be page aligned, * at least for Solaris and Linux, so use mmap() * only with null offset retrievals. */ if (file_size > 0 && file_size < IU_MMAP_SIZE && restart_point == 0) { curpos = lseek (filefd, 0, SEEK_CUR); if (debug) syslog (LOG_DEBUG, "Position is %jd. Attempting mmap call.", curpos); if (curpos >= 0) { filesize = file_size - curpos; buf = mmap (0, filesize, PROT_READ, MAP_SHARED, filefd, curpos); } } #endif switch (type) { case TYPE_A: #ifdef HAVE_MMAP if (file_size > 0 && curpos >= 0 && buf != MAP_FAILED) { if (debug) syslog (LOG_DEBUG, "Reading file as ascii in mmap mode."); len = 0; while (len < filesize) { byte_count++; if (buf[len] == '\n') { if (ferror (outstr)) break; putc ('\r', outstr); } putc (buf[len], outstr); len++; } fflush (outstr); transflag = 0; munmap (buf, filesize); if (ferror (outstr)) goto data_err; reply (226, "Transfer complete."); return; } #endif if (debug) syslog (LOG_DEBUG, "Reading file as ascii in byte mode."); while ((c = getc (instr)) != EOF) { byte_count++; if (c == '\n') { if (ferror (outstr)) goto data_err; putc ('\r', outstr); } putc (c, outstr); } fflush (outstr); transflag = 0; if (ferror (instr)) goto file_err; if (ferror (outstr)) goto data_err; reply (226, "Transfer complete."); return; case TYPE_I: case TYPE_L: #ifdef HAVE_MMAP if (file_size > 0 && curpos >= 0 && buf != MAP_FAILED) { if (debug) syslog (LOG_DEBUG, "Reading file as image in mmap mode."); bp = buf; len = filesize; do { cnt = write (netfd, bp, len); len -= cnt; bp += cnt; if (cnt > 0) byte_count += cnt; } while (cnt > 0 && len > 0); transflag = 0; munmap (buf, (size_t) filesize); if (cnt < 0) goto data_err; reply (226, "Transfer complete."); return; } #endif if (debug) { syslog (LOG_DEBUG, "Reading file as image in block mode."); curpos = lseek (filefd, 0, SEEK_CUR); if (curpos < 0) syslog (LOG_DEBUG, "Input file: %m"); else syslog (LOG_DEBUG, "Starting at position %jd.", curpos); } buf = malloc ((unsigned int) blksize); if (buf == NULL) { transflag = 0; perror_reply (451, "Local resource failure: malloc"); return; } while ((cnt = read (filefd, buf, (unsigned int) blksize)) > 0 && write (netfd, buf, cnt) == cnt) byte_count += cnt; transflag = 0; free (buf); if (cnt != 0) { if (cnt < 0) goto file_err; goto data_err; } reply (226, "Transfer complete."); return; default: transflag = 0; reply (550, "Unimplemented TYPE %d in send_data", type); return; } data_err: transflag = 0; perror_reply (426, "Data connection"); return; file_err: transflag = 0; perror_reply (551, "Error on input file"); } /* Transfer data from peer to "outstr" using the appropriate encapulation of the data subject to Mode, Structure, and Type. N.B.: Form isn't handled. */ static int receive_data (FILE *instr, FILE *outstr, off_t blksize) { int c; int cnt, bare_lfs = 0; char *buf; transflag++; if (setjmp (urgcatch)) { transflag = 0; return -1; } switch (type) { case TYPE_I: case TYPE_L: buf = malloc ((unsigned int) blksize); if (buf == NULL) { transflag = 0; perror_reply (451, "Local resource failure: malloc"); return -1; } while ((cnt = read (fileno (instr), buf, blksize)) > 0) { if (write (fileno (outstr), buf, cnt) != cnt) { free (buf); goto file_err; } byte_count += cnt; } free (buf); if (cnt < 0) goto data_err; transflag = 0; return 0; case TYPE_E: reply (553, "TYPE E not implemented."); transflag = 0; return -1; case TYPE_A: while ((c = getc (instr)) != EOF) { byte_count++; if (c == '\n') bare_lfs++; while (c == '\r') { if (ferror (outstr)) goto data_err; c = getc (instr); if (c != '\n') { putc ('\r', outstr); if (c == '\0' || c == EOF) goto contin2; } } putc (c, outstr); contin2:; } fflush (outstr); if (ferror (instr)) goto data_err; if (ferror (outstr)) goto file_err; transflag = 0; if (bare_lfs) { lreply (226, "WARNING! %d bare linefeeds received in ASCII mode", bare_lfs); printf (" File may not have transferred correctly.\r\n"); } return (0); default: reply (550, "Unimplemented TYPE %d in receive_data", type); transflag = 0; return -1; } data_err: transflag = 0; perror_reply (426, "Data Connection"); return -1; file_err: transflag = 0; perror_reply (452, "Error writing file"); return -1; } void statfilecmd (const char *filename) { FILE *fin; int c; char line[LINE_MAX]; snprintf (line, sizeof (line), "/bin/ls -lgA %s", filename); fin = ftpd_popen (line, "r"); lreply (211, "status of %s:", filename); while ((c = getc (fin)) != EOF) { if (c == '\n') { if (ferror (stdout)) { perror_reply (421, "control connection"); ftpd_pclose (fin); dologout (1); } 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) { struct sockaddr_storage *sin; unsigned char *a, *p; lreply (211, "%s FTP server status:", hostname); if (!no_version) printf (" ftpd (%s) %s\r\n", PACKAGE_NAME, PACKAGE_VERSION); printf (" Connected to %s", cred.remotehost); (void) getnameinfo ((struct sockaddr *) &his_addr, his_addrlen, addrstr, sizeof (addrstr), NULL, 0, NI_NUMERICHOST); if (!isdigit (cred.remotehost[0])) printf (" (%s)", addrstr); printf ("\r\n"); printf (" Session timeout is %d seconds\r\n", timeout); if (cred.logged_in) { if (cred.guest) printf (" Logged in anonymously\r\n"); else printf (" Logged in as %s\r\n", cred.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) printf (" %d", CHAR_BIT); printf ("; STRUcture: %s; transfer MODE: %s\r\n", strunames[stru], modenames[stru_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 = (unsigned char *) &((struct sockaddr_in *) sin)->sin_addr; p = (unsigned char *) &((struct sockaddr_in *) sin)->sin_port; #define UC(b) (((int) b) & 0xff) printf (" (%d,%d,%d,%d,%d,%d)\r\n", UC (a[0]), UC (a[1]), UC (a[2]), UC (a[3]), UC (p[0]), UC (p[1])); #undef UC } else printf (" No data connection\r\n"); reply (211, "End of status"); } void fatal (const char *s) { reply (451, "Error in server: %s\n", s); reply (221, "Closing connection due to server error."); dologout (0); } void reply (int n, const char *fmt, ...) { va_list ap; va_start (ap, fmt); printf ("%d ", n); vprintf (fmt, ap); va_end (ap); printf ("\r\n"); fflush (stdout); if (debug) { syslog (LOG_DEBUG, "<--- %d ", n); va_start (ap, fmt); vsyslog (LOG_DEBUG, fmt, ap); va_end (ap); } } void lreply (int n, const char *fmt, ...) { va_list ap; va_start (ap, fmt); printf ("%d- ", n); vprintf (fmt, ap); va_end (ap); printf ("\r\n"); fflush (stdout); if (debug) { syslog (LOG_DEBUG, "<--- %d- ", n); va_start (ap, fmt); vsyslog (LOG_DEBUG, fmt, ap); va_end (ap); } } /* Send a possibly multiline reply as individual * lines of message with identical status code. * No format string input! */ void lreply_multiline (int n, const char *text) { char *line; line = strdup (text); if (line == NULL) return; else { int stop = 0; char *p1 = line, *p2; do { p2 = strchrnul (p1, '\n'); stop = (*p2 == '\0'); /* End of input string? */ *p2 = '\0'; printf ("%d- ", n); printf ("%s\r\n", p1); if (debug) { syslog (LOG_DEBUG, "<--- %d- ", n); syslog (LOG_DEBUG, "%s", p1); } p1 = ++p2; /* P1 is used within bounds. */ } while (!stop); free (line); } } static void ack (const char *s) { reply (250, "%s command successful.", s); } void nack (const char *s) { reply (502, "%s command not implemented.", s); } void delete (const 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 (const char *name) { LOGCMD ("mkdir", name); if (mkdir (name, 0777) < 0) perror_reply (550, name); else if (name[0] == '/') reply (257, "\"%s\" new directory created.", name); else { /* We have to figure out what our current directory is so that we can give an absolute name in the reply. */ char *current = xgetcwd (); if (current) { if (current[1] == '\0') current[0] = '\0'; reply (257, "\"%s/%s\" new directory created.", current, name); free (current); } else reply (257, "(unknown absolute name) new directory created."); } } void removedir (const char *name) { LOGCMD ("rmdir", name); if (rmdir (name) < 0) perror_reply (550, name); else ack ("RMD"); } void pwd (void) { char *path = xgetcwd (); if (path) { reply (257, "\"%s\" is current directory.", path); free (path); } else reply (550, "%s.", strerror (errno)); } char * renamefrom (const char *name) { struct stat st; if (stat (name, &st) < 0) { perror_reply (550, name); return ((char *) 0); } reply (350, "File exists, ready for destination name"); return (char *) (name); } void renamecmd (const char *from, const char *to) { LOGCMD2 ("rename", from, to); if (rename (from, to) < 0) perror_reply (550, "rename"); else ack ("RNTO"); } static void dolog (struct sockaddr *sa, socklen_t salen, struct credentials *pcred) { (void) getnameinfo (sa, salen, addrstr, sizeof (addrstr), NULL, 0, 0); free (pcred->remotehost); pcred->remotehost = sgetsave (addrstr); #ifdef HAVE_SETPROCTITLE snprintf (proctitle, sizeof (proctitle), "%s: connected", pcred->remotehost); setproctitle ("%s", proctitle); #endif /* HAVE_SETPROCTITLE */ if (logging) syslog (LOG_INFO, "connection from %s", pcred->remotehost); } /* Record logout in wtmp file and exit with supplied status. */ void dologout (int status) { /* Race condition with SIGURG: If SIGURG is received here, it will jump back has root in the main loop. David Greenman:dg@root.com. */ transflag = 0; end_login (&cred); /* Beware of flushing buffers after a SIGPIPE. */ _exit (status); } static void myoob (int signo MAYBE_UNUSED) { char *cp; /* only process if transfer occurring */ if (!transflag) return; cp = tmpline; if (telnet_fgets (cp, 7, stdin) == NULL) { reply (221, "You could at least say goodbye."); dologout (0); } upper (cp); if (strcmp (cp, "ABOR\r\n") == 0) { tmpline[0] = '\0'; reply (426, "Transfer aborted. Data connection closed."); reply (226, "Abort successful"); longjmp (urgcatch, 1); } if (strcmp (cp, "STAT\r\n") == 0) { if (file_size != (off_t) - 1) reply (213, "Status: %s of %s bytes transferred", off_to_str (byte_count), off_to_str (file_size)); else reply (213, "Status: %s bytes transferred", off_to_str (byte_count)); } } /* Note: a response of 425 is not mentioned as a possible response to the PASV command in RFC959. However, it has been blessed as a legitimate response by Jon Postel in a telephone conversation with Rick Adams on 25 Jan 89. */ void passive (int epsv, int af) { char *p, *a; int try_af; /* EPSV might ask for a particular address family. */ if (epsv == PASSIVE_EPSV && af > 0) try_af = af; else try_af = ctrl_addr.ss_family; pdata = socket (try_af, SOCK_STREAM, 0); if (pdata < 0) { perror_reply (425, "Can't open passive connection"); return; } memcpy (&pasv_addr, &ctrl_addr, sizeof (pasv_addr)); pasv_addrlen = ctrl_addrlen; /* Erase the port number. */ if (pasv_addr.ss_family == AF_INET6) ((struct sockaddr_in6 *) &pasv_addr)->sin6_port = 0; else /* !AF_INET6 */ ((struct sockaddr_in *) &pasv_addr)->sin_port = 0; if (seteuid ((uid_t) 0) == -1) _exit (EXIT_FAILURE); if (bind (pdata, (struct sockaddr *) &pasv_addr, pasv_addrlen) < 0) { if (seteuid ((uid_t) cred.uid)) _exit (EXIT_FAILURE); goto pasv_error; } if (seteuid ((uid_t) cred.uid)) _exit (EXIT_FAILURE); pasv_addrlen = sizeof (pasv_addr); if (getsockname (pdata, (struct sockaddr *) &pasv_addr, &pasv_addrlen) < 0) goto pasv_error; if (listen (pdata, 1) < 0) goto pasv_error; if (epsv == PASSIVE_EPSV) { /* EPSV for IPv4 and IPv6. */ reply (229, "Entering Extended Passive Mode (|||%u|)", ntohs ((pasv_addr.ss_family == AF_INET) ? ((struct sockaddr_in *) &pasv_addr)->sin_port : ((struct sockaddr_in6 *) &pasv_addr)->sin6_port)); return; } else /* !EPSV */ { /* PASV for IPv4, or LPSV for IPv4 or IPv6. * * Some systems, like OpenSolaris, prefer to return * an IPv4-mapped-IPv6 address, which must be processed * for printout. */ #define UC(b) (((int) b) & 0xff) if (pasv_addr.ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED (& ((struct sockaddr_in6 *) &pasv_addr)->sin6_addr)) { a = (char *) &((struct sockaddr_in6 *) &pasv_addr)->sin6_addr; a += 3 * sizeof (struct in_addr); /* Skip padding up to IPv4 content. */ p = (char *) &((struct sockaddr_in6 *) &pasv_addr)->sin6_port; } else if (pasv_addr.ss_family == AF_INET6) { /* LPSV for IPv6, not mapped. */ a = (char *) &((struct sockaddr_in6 *) &pasv_addr)->sin6_addr; p = (char *) &((struct sockaddr_in6 *) &pasv_addr)->sin6_port; reply (228, "Entering Long Passive Mode " "(6,16,%d,%d,%d,%d,%d,%d,%d,%d" /* a[0..7] */ ",%d,%d,%d,%d,%d,%d,%d,%d" /* a[8..15] */ ",2,%d,%d)", /* p0, p1 */ UC (a[0]), UC (a[1]), UC (a[2]), UC (a[3]), UC (a[4]), UC (a[5]), UC (a[6]), UC (a[7]), UC (a[8]), UC (a[9]), UC (a[10]), UC (a[11]), UC (a[12]), UC (a[13]), UC (a[14]), UC (a[15]), UC (p[0]), UC (p[1])); return; } else { a = (char *) &((struct sockaddr_in *) &pasv_addr)->sin_addr; p = (char *) &((struct sockaddr_in *) &pasv_addr)->sin_port; } if (epsv == PASSIVE_LPSV) reply (228, "Entering Long Passive Mode " "(4,4,%d,%d,%d,%d,2,%d,%d)", UC (a[0]), UC (a[1]), UC (a[2]), UC (a[3]), UC (p[0]), UC (p[1])); else 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; } /* 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 (const char *local) { static char *string = 0; struct stat st; int count; char *cp; cp = strrchr (local, '/'); if (cp) *cp = '\0'; if (stat (cp ? local : ".", &st) < 0) { perror_reply (553, cp ? local : "."); return ((char *) 0); } if (cp) *cp = '/'; free (string); string = malloc (strlen (local) + 5); /* '.' + DIG + DIG + '\0' */ if (string) { strcpy (string, local); cp = string + strlen (string); *cp++ = '.'; for (count = 1; count < 100; count++) { sprintf (cp, "%d", count); if (stat (string, &st) < 0) return string; } } 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 send_file_list (const 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 *p = NULL; if (strpbrk (whichf, "~{[*?") != NULL) { int flags = GLOB_NOCHECK; #ifdef GLOB_BRACE flags |= GLOB_BRACE; #endif #ifdef GLOB_QUOTE flags |= GLOB_QUOTE; #endif #ifdef GLOB_TILDE flags |= GLOB_TILDE; #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 { p = strdup (whichf); onefile[0] = p; 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++; } fprintf (dout, "%s%s\n", dirname, type == TYPE_A ? "\r" : ""); byte_count += strlen (dirname) + 1; continue; } else if (!S_ISDIR (st.st_mode)) continue; dirp = opendir (dirname); if (dirp == NULL) continue; while ((dir = readdir (dirp)) != NULL) { char *nbuf; if (dir->d_name[0] == '.' && dir->d_name[1] == '\0') continue; if (dir->d_name[0] == '.' && dir->d_name[1] == '.' && dir->d_name[2] == '\0') continue; nbuf = alloca (strlen (dirname) + 1 + strlen (dir->d_name) + 1); sprintf (nbuf, "%s/%s", dirname, dir->d_name); /* We have to do a stat to insure it's not a directory or special file. */ if (simple || (stat (nbuf, &st) == 0 && S_ISREG (st.st_mode))) { if (dout == NULL) { dout = dataconn ("file list", (off_t) - 1, "w"); if (dout == NULL) goto out; transflag++; } if (nbuf[0] == '.' && nbuf[1] == '/') fprintf (dout, "%s%s\n", &nbuf[2], type == TYPE_A ? "\r" : ""); else fprintf (dout, "%s%s\n", nbuf, type == TYPE_A ? "\r" : ""); byte_count += strlen (nbuf) + 1; } } 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) fclose (dout); data = -1; pdata = -1; out: free (p); if (freeglob) { freeglob = 0; globfree (&gl); } }