ftpd.c revision 101809
11592Srgrimes/* 21592Srgrimes * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994 31592Srgrimes * The Regents of the University of California. All rights reserved. 41592Srgrimes * 51592Srgrimes * Redistribution and use in source and binary forms, with or without 61592Srgrimes * modification, are permitted provided that the following conditions 71592Srgrimes * are met: 81592Srgrimes * 1. Redistributions of source code must retain the above copyright 91592Srgrimes * notice, this list of conditions and the following disclaimer. 101592Srgrimes * 2. Redistributions in binary form must reproduce the above copyright 111592Srgrimes * notice, this list of conditions and the following disclaimer in the 121592Srgrimes * documentation and/or other materials provided with the distribution. 131592Srgrimes * 3. All advertising materials mentioning features or use of this software 141592Srgrimes * must display the following acknowledgement: 151592Srgrimes * This product includes software developed by the University of 161592Srgrimes * California, Berkeley and its contributors. 171592Srgrimes * 4. Neither the name of the University nor the names of its contributors 181592Srgrimes * may be used to endorse or promote products derived from this software 191592Srgrimes * without specific prior written permission. 201592Srgrimes * 211592Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 221592Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 231592Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 241592Srgrimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 251592Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 261592Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 271592Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 281592Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 291592Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 301592Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 311592Srgrimes * SUCH DAMAGE. 321592Srgrimes */ 331592Srgrimes 3417478Smarkm#if 0 351592Srgrimes#ifndef lint 361592Srgrimesstatic char copyright[] = 371592Srgrimes"@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994\n\ 381592Srgrimes The Regents of the University of California. All rights reserved.\n"; 391592Srgrimes#endif /* not lint */ 4017478Smarkm#endif 411592Srgrimes 4231329Scharnier#ifndef lint 4317478Smarkm#if 0 441592Srgrimesstatic char sccsid[] = "@(#)ftpd.c 8.4 (Berkeley) 4/16/94"; 4531329Scharnier#endif 4631329Scharnierstatic const char rcsid[] = 4750476Speter "$FreeBSD: head/libexec/ftpd/ftpd.c 101809 2002-08-13 14:08:38Z yar $"; 481592Srgrimes#endif /* not lint */ 491592Srgrimes 501592Srgrimes/* 511592Srgrimes * FTP server. 521592Srgrimes */ 531592Srgrimes#include <sys/param.h> 541592Srgrimes#include <sys/ioctl.h> 5566907Swollman#include <sys/mman.h> 561592Srgrimes#include <sys/socket.h> 5766907Swollman#include <sys/stat.h> 5866907Swollman#include <sys/time.h> 591592Srgrimes#include <sys/wait.h> 601592Srgrimes 611592Srgrimes#include <netinet/in.h> 621592Srgrimes#include <netinet/in_systm.h> 631592Srgrimes#include <netinet/ip.h> 648240Swollman#include <netinet/tcp.h> 651592Srgrimes 661592Srgrimes#define FTP_NAMES 671592Srgrimes#include <arpa/ftp.h> 681592Srgrimes#include <arpa/inet.h> 691592Srgrimes#include <arpa/telnet.h> 701592Srgrimes 711592Srgrimes#include <ctype.h> 721592Srgrimes#include <dirent.h> 731592Srgrimes#include <err.h> 741592Srgrimes#include <errno.h> 751592Srgrimes#include <fcntl.h> 761592Srgrimes#include <glob.h> 771592Srgrimes#include <limits.h> 781592Srgrimes#include <netdb.h> 791592Srgrimes#include <pwd.h> 8025187Sdavidn#include <grp.h> 8188763Sache#include <opie.h> 821592Srgrimes#include <signal.h> 831592Srgrimes#include <stdio.h> 841592Srgrimes#include <stdlib.h> 851592Srgrimes#include <string.h> 861592Srgrimes#include <syslog.h> 871592Srgrimes#include <time.h> 881592Srgrimes#include <unistd.h> 8913139Speter#include <libutil.h> 9025101Sdavidn#ifdef LOGIN_CAP 9125101Sdavidn#include <login_cap.h> 9225101Sdavidn#endif 931592Srgrimes 9474874Smarkm#ifdef USE_PAM 9551433Smarkm#include <security/pam_appl.h> 9651433Smarkm#endif 9751433Smarkm 981592Srgrimes#include "pathnames.h" 991592Srgrimes#include "extern.h" 1001592Srgrimes 1011592Srgrimes#include <stdarg.h> 1021592Srgrimes 10325165Sdavidnstatic char version[] = "Version 6.00LS"; 10425165Sdavidn#undef main 1051592Srgrimes 1061592Srgrimesextern off_t restart_point; 1071592Srgrimesextern char cbuf[]; 1081592Srgrimes 10956668Sshinunion sockunion server_addr; 11056668Sshinunion sockunion ctrl_addr; 11156668Sshinunion sockunion data_source; 11256668Sshinunion sockunion data_dest; 11356668Sshinunion sockunion his_addr; 11456668Sshinunion sockunion pasv_addr; 1151592Srgrimes 11615196Sdgint daemon_mode; 1171592Srgrimesint data; 1181592Srgrimesint logged_in; 1191592Srgrimesstruct passwd *pw; 12076096Smarkmint ftpdebug; 1211592Srgrimesint timeout = 900; /* timeout after 15 minutes of inactivity */ 1221592Srgrimesint maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */ 1231592Srgrimesint logging; 1249933Spstint restricted_data_ports = 1; 12517435Spstint paranoid = 1; /* be extra careful about security */ 12620042Storstenbint anon_only = 0; /* Only anonymous ftp allowed */ 1271592Srgrimesint guest; 12817435Spstint dochroot; 1296740Sguidoint stats; 1306740Sguidoint statfd = -1; 1311592Srgrimesint type; 1321592Srgrimesint form; 1331592Srgrimesint stru; /* avoid C keyword */ 1341592Srgrimesint mode; 1351592Srgrimesint usedefault = 1; /* for data transfers */ 1361592Srgrimesint pdata = -1; /* for passive mode */ 13770102Sphkint readonly=0; /* Server is in readonly mode. */ 13870102Sphkint noepsv=0; /* EPSV command is disabled. */ 13982460Snikint noretr=0; /* RETR command is disabled. */ 14082796Ssheldonhint noguestretr=0; /* RETR command is disabled for anon users. */ 14199195Smdoddint noguestmkd=0; /* MKD command is disabled for anon users. */ 142101537Syarint noguestmod=1; /* anon users may not modify existing files. */ 14382460Snik 14489935Syarstatic volatile sig_atomic_t recvurg; 1451592Srgrimessig_atomic_t transflag; 1461592Srgrimesoff_t file_size; 1471592Srgrimesoff_t byte_count; 1481592Srgrimes#if !defined(CMASK) || CMASK == 0 1491592Srgrimes#undef CMASK 1501592Srgrimes#define CMASK 027 1511592Srgrimes#endif 1521592Srgrimesint defumask = CMASK; /* default umask value */ 1531592Srgrimeschar tmpline[7]; 15427650Sdavidnchar *hostname; 15578153Sddint epsvall = 0; 15678153Sdd 15725283Sdavidn#ifdef VIRTUAL_HOSTING 15825283Sdavidnchar *ftpuser; 15925283Sdavidn 16025283Sdavidnstatic struct ftphost { 16125283Sdavidn struct ftphost *next; 16257124Sshin struct addrinfo *hostinfo; 16325283Sdavidn char *hostname; 16425283Sdavidn char *anonuser; 16525283Sdavidn char *statfile; 16625283Sdavidn char *welcome; 16725283Sdavidn char *loginmsg; 16825283Sdavidn} *thishost, *firsthost; 16925283Sdavidn 17025283Sdavidn#endif 17145422Sbrianchar remotehost[MAXHOSTNAMELEN]; 1726740Sguidochar *ident = NULL; 17317435Spst 17417435Spststatic char ttyline[20]; 17517435Spstchar *tty = ttyline; /* for klogin */ 17617435Spst 17774874Smarkm#ifdef USE_PAM 17890148Simpstatic int auth_pam(struct passwd**, const char*); 17974874Smarkmpam_handle_t *pamh = NULL; 18088763Sache#endif 18179469Smarkm 18279469Smarkmstatic struct opie opiedata; 18379469Smarkmstatic char opieprompt[OPIE_CHALLENGE_MAX+1]; 18488763Sachestatic int pwok; 18517478Smarkm 18617483Sjulianchar *pid_file = NULL; 18717483Sjulian 1881592Srgrimes/* 18974470Sjlemon * Limit number of pathnames that glob can return. 19074470Sjlemon * A limit of 0 indicates the number of pathnames is unlimited. 19174470Sjlemon */ 19274470Sjlemon#define MAXGLOBARGS 16384 19374470Sjlemon# 19474470Sjlemon 19574470Sjlemon/* 1961592Srgrimes * Timeout intervals for retrying connections 1971592Srgrimes * to hosts that don't accept PORT cmds. This 1981592Srgrimes * is a kludge, but given the problems with TCP... 1991592Srgrimes */ 2001592Srgrimes#define SWAITMAX 90 /* wait at most 90 seconds */ 2011592Srgrimes#define SWAITINT 5 /* interval between retries */ 2021592Srgrimes 2031592Srgrimesint swaitmax = SWAITMAX; 2041592Srgrimesint swaitint = SWAITINT; 2051592Srgrimes 2061592Srgrimes#ifdef SETPROCTITLE 20713139Speter#ifdef OLD_SETPROCTITLE 2081592Srgrimeschar **Argv = NULL; /* pointer to argument vector */ 2091592Srgrimeschar *LastArgv = NULL; /* end of argv */ 21013139Speter#endif /* OLD_SETPROCTITLE */ 2111592Srgrimeschar proctitle[LINE_MAX]; /* initial part of title */ 2121592Srgrimes#endif /* SETPROCTITLE */ 2131592Srgrimes 2141592Srgrimes#define LOGCMD(cmd, file) \ 2151592Srgrimes if (logging > 1) \ 2161592Srgrimes syslog(LOG_INFO,"%s %s%s", cmd, \ 2171592Srgrimes *(file) == '/' ? "" : curdir(), file); 2181592Srgrimes#define LOGCMD2(cmd, file1, file2) \ 2191592Srgrimes if (logging > 1) \ 2201592Srgrimes syslog(LOG_INFO,"%s %s%s %s%s", cmd, \ 2211592Srgrimes *(file1) == '/' ? "" : curdir(), file1, \ 2221592Srgrimes *(file2) == '/' ? "" : curdir(), file2); 2231592Srgrimes#define LOGBYTES(cmd, file, cnt) \ 2241592Srgrimes if (logging > 1) { \ 2251592Srgrimes if (cnt == (off_t)-1) \ 2261592Srgrimes syslog(LOG_INFO,"%s %s%s", cmd, \ 2271592Srgrimes *(file) == '/' ? "" : curdir(), file); \ 2281592Srgrimes else \ 2291592Srgrimes syslog(LOG_INFO, "%s %s%s = %qd bytes", \ 2301592Srgrimes cmd, (*(file) == '/') ? "" : curdir(), file, cnt); \ 2311592Srgrimes } 2321592Srgrimes 23325283Sdavidn#ifdef VIRTUAL_HOSTING 23490148Simpstatic void inithosts(void); 23590148Simpstatic void selecthost(union sockunion *); 23625283Sdavidn#endif 23790148Simpstatic void ack(char *); 23890148Simpstatic void sigurg(int); 23990148Simpstatic void myoob(void); 24090148Simpstatic int checkuser(char *, char *, int); 24190148Simpstatic FILE *dataconn(char *, off_t, char *); 24290148Simpstatic void dolog(struct sockaddr *); 24390148Simpstatic char *curdir(void); 24490148Simpstatic void end_login(void); 24590148Simpstatic FILE *getdatasock(char *); 246101537Syarstatic int guniquefd(char *, char **); 24790148Simpstatic void lostconn(int); 24890148Simpstatic void sigquit(int); 24990148Simpstatic int receive_data(FILE *, FILE *); 25090148Simpstatic int send_data(FILE *, FILE *, off_t, off_t, int); 2511592Srgrimesstatic struct passwd * 25290148Simp sgetpwnam(char *); 25390148Simpstatic char *sgetsave(char *); 25490148Simpstatic void reapchild(int); 25590148Simpstatic void logxfer(char *, off_t, time_t); 256100486Syarstatic char *doublequote(char *); 2571592Srgrimes 2581592Srgrimesstatic char * 25990148Simpcurdir(void) 2601592Srgrimes{ 2611592Srgrimes static char path[MAXPATHLEN+1+1]; /* path + '/' + '\0' */ 2621592Srgrimes 2631592Srgrimes if (getcwd(path, sizeof(path)-2) == NULL) 2641592Srgrimes return (""); 2651592Srgrimes if (path[1] != '\0') /* special case for root dir. */ 2661592Srgrimes strcat(path, "/"); 2671592Srgrimes /* For guest account, skip / since it's chrooted */ 2681592Srgrimes return (guest ? path+1 : path); 2691592Srgrimes} 2701592Srgrimes 2711592Srgrimesint 27290148Simpmain(int argc, char *argv[], char **envp) 2731592Srgrimes{ 2741592Srgrimes int addrlen, ch, on = 1, tos; 2751592Srgrimes char *cp, line[LINE_MAX]; 2761592Srgrimes FILE *fd; 27756668Sshin int error; 27856668Sshin char *bindname = NULL; 27956668Sshin int family = AF_UNSPEC; 28056668Sshin int enable_v4 = 0; 28189935Syar struct sigaction sa; 2821592Srgrimes 28336105Sache tzset(); /* in case no timezone database in ~ftp */ 28489935Syar sigemptyset(&sa.sa_mask); 28589935Syar sa.sa_flags = SA_RESTART; 28636105Sache 28713139Speter#ifdef OLD_SETPROCTITLE 2881592Srgrimes /* 2891592Srgrimes * Save start and extent of argv for setproctitle. 2901592Srgrimes */ 2911592Srgrimes Argv = argv; 2921592Srgrimes while (*envp) 2931592Srgrimes envp++; 2941592Srgrimes LastArgv = envp[-1] + strlen(envp[-1]); 29513139Speter#endif /* OLD_SETPROCTITLE */ 2961592Srgrimes 2976740Sguido 298101537Syar while ((ch = getopt(argc, argv, "46a:AdDElmMoOp:rRSt:T:u:Uv")) != -1) { 2991592Srgrimes switch (ch) { 300100717Syar case '4': 301100717Syar enable_v4 = 1; 302100717Syar if (family == AF_UNSPEC) 303100717Syar family = AF_INET; 30415196Sdg break; 30515196Sdg 306100717Syar case '6': 307100717Syar family = AF_INET6; 308100717Syar break; 309100717Syar 310100717Syar case 'a': 311100717Syar bindname = optarg; 312100717Syar break; 313100717Syar 314100717Syar case 'A': 315100717Syar anon_only = 1; 316100717Syar break; 317100717Syar 3181592Srgrimes case 'd': 31976096Smarkm ftpdebug++; 3201592Srgrimes break; 3211592Srgrimes 322100717Syar case 'D': 323100717Syar daemon_mode++; 324100717Syar break; 325100717Syar 32670102Sphk case 'E': 32770102Sphk noepsv = 1; 32870102Sphk break; 32970102Sphk 3301592Srgrimes case 'l': 3311592Srgrimes logging++; /* > 1 == extra logging */ 3321592Srgrimes break; 3331592Srgrimes 334101537Syar case 'm': 335101537Syar noguestmod = 0; 336101537Syar break; 337101537Syar 338100717Syar case 'M': 339100717Syar noguestmkd = 1; 340100717Syar break; 341100717Syar 342100717Syar case 'o': 343100717Syar noretr = 1; 344100717Syar break; 345100717Syar 346100717Syar case 'O': 347100717Syar noguestretr = 1; 348100717Syar break; 349100717Syar 350100717Syar case 'p': 351100717Syar pid_file = optarg; 352100717Syar break; 353100717Syar 35470102Sphk case 'r': 35570102Sphk readonly = 1; 35670102Sphk break; 35770102Sphk 35817435Spst case 'R': 35917435Spst paranoid = 0; 3609933Spst break; 3619933Spst 3626740Sguido case 'S': 36317435Spst stats++; 3646740Sguido break; 36517435Spst 36617435Spst case 't': 36717435Spst timeout = atoi(optarg); 36817435Spst if (maxtimeout < timeout) 36917435Spst maxtimeout = timeout; 37017435Spst break; 37117435Spst 372100717Syar case 'T': 373100717Syar maxtimeout = atoi(optarg); 374100717Syar if (timeout > maxtimeout) 375100717Syar timeout = maxtimeout; 37617435Spst break; 37717435Spst 3781592Srgrimes case 'u': 3791592Srgrimes { 3801592Srgrimes long val = 0; 3811592Srgrimes 3821592Srgrimes val = strtol(optarg, &optarg, 8); 3831592Srgrimes if (*optarg != '\0' || val < 0) 3841592Srgrimes warnx("bad value for -u"); 3851592Srgrimes else 3861592Srgrimes defumask = val; 3871592Srgrimes break; 3881592Srgrimes } 389100717Syar case 'U': 390100717Syar restricted_data_ports = 0; 39120042Storstenb break; 3921592Srgrimes 3931592Srgrimes case 'v': 394100720Syar ftpdebug++; 3951592Srgrimes break; 3961592Srgrimes 3971592Srgrimes default: 3981592Srgrimes warnx("unknown flag -%c ignored", optopt); 3991592Srgrimes break; 4001592Srgrimes } 4011592Srgrimes } 40215196Sdg 40325283Sdavidn#ifdef VIRTUAL_HOSTING 40425283Sdavidn inithosts(); 40525283Sdavidn#endif 4061592Srgrimes (void) freopen(_PATH_DEVNULL, "w", stderr); 40715196Sdg 40815196Sdg /* 40915196Sdg * LOG_NDELAY sets up the logging connection immediately, 41015196Sdg * necessary for anonymous ftp's that chroot and can't do it later. 41115196Sdg */ 41215196Sdg openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP); 41315196Sdg 41415196Sdg if (daemon_mode) { 41515196Sdg int ctl_sock, fd; 41656668Sshin struct addrinfo hints, *res; 41715196Sdg 41815196Sdg /* 41915196Sdg * Detach from parent. 42015196Sdg */ 42115196Sdg if (daemon(1, 1) < 0) { 42215196Sdg syslog(LOG_ERR, "failed to become a daemon"); 42315196Sdg exit(1); 42415196Sdg } 42589935Syar sa.sa_handler = reapchild; 42689935Syar (void)sigaction(SIGCHLD, &sa, NULL); 42756668Sshin /* init bind_sa */ 42856668Sshin memset(&hints, 0, sizeof(hints)); 42956668Sshin 43056668Sshin hints.ai_family = family == AF_UNSPEC ? AF_INET : family; 43156668Sshin hints.ai_socktype = SOCK_STREAM; 43256668Sshin hints.ai_protocol = 0; 43356668Sshin hints.ai_flags = AI_PASSIVE; 43456668Sshin error = getaddrinfo(bindname, "ftp", &hints, &res); 43556668Sshin if (error) { 43656668Sshin if (family == AF_UNSPEC) { 43756668Sshin hints.ai_family = AF_UNSPEC; 43856668Sshin error = getaddrinfo(bindname, "ftp", &hints, 43956668Sshin &res); 44056668Sshin } 44156668Sshin } 44256668Sshin if (error) { 44368901Skris syslog(LOG_ERR, "%s", gai_strerror(error)); 44456668Sshin if (error == EAI_SYSTEM) 44568901Skris syslog(LOG_ERR, "%s", strerror(errno)); 44615196Sdg exit(1); 44715196Sdg } 44856668Sshin if (res->ai_addr == NULL) { 44956668Sshin syslog(LOG_ERR, "-a %s: getaddrinfo failed", hostname); 45056668Sshin exit(1); 45164699Sru } else 45264699Sru family = res->ai_addr->sa_family; 45315196Sdg /* 45415196Sdg * Open a socket, bind it to the FTP port, and start 45515196Sdg * listening. 45615196Sdg */ 45756668Sshin ctl_sock = socket(family, SOCK_STREAM, 0); 45815196Sdg if (ctl_sock < 0) { 45915196Sdg syslog(LOG_ERR, "control socket: %m"); 46015196Sdg exit(1); 46115196Sdg } 46215196Sdg if (setsockopt(ctl_sock, SOL_SOCKET, SO_REUSEADDR, 463100612Syar &on, sizeof(on)) < 0) 464100609Syar syslog(LOG_WARNING, 465100609Syar "control setsockopt (SO_REUSEADDR): %m"); 46656668Sshin if (family == AF_INET6 && enable_v4 == 0) { 467100505Sume if (setsockopt(ctl_sock, IPPROTO_IPV6, IPV6_V6ONLY, 468100612Syar &on, sizeof (on)) < 0) 469100609Syar syslog(LOG_WARNING, 470100609Syar "control setsockopt (IPV6_V6ONLY): %m"); 47156668Sshin } 47256668Sshin memcpy(&server_addr, res->ai_addr, res->ai_addr->sa_len); 47356668Sshin if (bind(ctl_sock, (struct sockaddr *)&server_addr, 47456668Sshin server_addr.su_len) < 0) { 47515196Sdg syslog(LOG_ERR, "control bind: %m"); 47615196Sdg exit(1); 47715196Sdg } 47815196Sdg if (listen(ctl_sock, 32) < 0) { 47915196Sdg syslog(LOG_ERR, "control listen: %m"); 48015196Sdg exit(1); 48115196Sdg } 48215196Sdg /* 48317483Sjulian * Atomically write process ID 48417483Sjulian */ 48517483Sjulian if (pid_file) 48699213Smaxim { 48717483Sjulian int fd; 48817483Sjulian char buf[20]; 48917483Sjulian 49017483Sjulian fd = open(pid_file, O_CREAT | O_WRONLY | O_TRUNC 49117483Sjulian | O_NONBLOCK | O_EXLOCK, 0644); 49246078Simp if (fd < 0) { 49317483Sjulian if (errno == EAGAIN) 49417483Sjulian errx(1, "%s: file locked", pid_file); 49517483Sjulian else 49617483Sjulian err(1, "%s", pid_file); 49746078Simp } 49817483Sjulian snprintf(buf, sizeof(buf), 49917483Sjulian "%lu\n", (unsigned long) getpid()); 50017483Sjulian if (write(fd, buf, strlen(buf)) < 0) 50117483Sjulian err(1, "%s: write", pid_file); 50217483Sjulian /* Leave the pid file open and locked */ 50317483Sjulian } 50417483Sjulian /* 50515196Sdg * Loop forever accepting connection requests and forking off 50615196Sdg * children to handle them. 50715196Sdg */ 50815196Sdg while (1) { 50956668Sshin addrlen = server_addr.su_len; 51015196Sdg fd = accept(ctl_sock, (struct sockaddr *)&his_addr, &addrlen); 51115196Sdg if (fork() == 0) { 51215196Sdg /* child */ 51315196Sdg (void) dup2(fd, 0); 51415196Sdg (void) dup2(fd, 1); 51515196Sdg close(ctl_sock); 51615196Sdg break; 51715196Sdg } 51815196Sdg close(fd); 51915196Sdg } 52015196Sdg } else { 52115196Sdg addrlen = sizeof(his_addr); 52215196Sdg if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) { 52315196Sdg syslog(LOG_ERR, "getpeername (%s): %m",argv[0]); 52415196Sdg exit(1); 52515196Sdg } 52615196Sdg } 52715196Sdg 52889935Syar sa.sa_handler = SIG_DFL; 52989935Syar (void)sigaction(SIGCHLD, &sa, NULL); 5301592Srgrimes 53189935Syar sa.sa_handler = sigurg; 53289935Syar sa.sa_flags = 0; /* don't restart syscalls for SIGURG */ 53389935Syar (void)sigaction(SIGURG, &sa, NULL); 53489935Syar 53589935Syar sigfillset(&sa.sa_mask); /* block all signals in handler */ 53689935Syar sa.sa_flags = SA_RESTART; 53789935Syar sa.sa_handler = sigquit; 53889935Syar (void)sigaction(SIGHUP, &sa, NULL); 53989935Syar (void)sigaction(SIGINT, &sa, NULL); 54089935Syar (void)sigaction(SIGQUIT, &sa, NULL); 54189935Syar (void)sigaction(SIGTERM, &sa, NULL); 54289935Syar 54389935Syar sa.sa_handler = lostconn; 54489935Syar (void)sigaction(SIGPIPE, &sa, NULL); 54589935Syar 54615196Sdg addrlen = sizeof(ctrl_addr); 54715196Sdg if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) { 54815196Sdg syslog(LOG_ERR, "getsockname (%s): %m",argv[0]); 54915196Sdg exit(1); 55015196Sdg } 55125283Sdavidn#ifdef VIRTUAL_HOSTING 55225283Sdavidn /* select our identity from virtual host table */ 55356668Sshin selecthost(&ctrl_addr); 55425283Sdavidn#endif 55515196Sdg#ifdef IP_TOS 55656668Sshin if (ctrl_addr.su_family == AF_INET) 55756668Sshin { 55815196Sdg tos = IPTOS_LOWDELAY; 559100612Syar if (setsockopt(0, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0) 560100609Syar syslog(LOG_WARNING, "control setsockopt (IP_TOS): %m"); 56156668Sshin } 56215196Sdg#endif 56335482Sdg /* 56435482Sdg * Disable Nagle on the control channel so that we don't have to wait 56535482Sdg * for peer's ACK before issuing our next reply. 56635482Sdg */ 56735482Sdg if (setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) 568100609Syar syslog(LOG_WARNING, "control setsockopt (TCP_NODELAY): %m"); 56935482Sdg 57056668Sshin data_source.su_port = htons(ntohs(ctrl_addr.su_port) - 1); 57115196Sdg 57217435Spst /* set this here so klogin can use it... */ 57331973Simp (void)snprintf(ttyline, sizeof(ttyline), "ftp%d", getpid()); 57417435Spst 5751592Srgrimes /* Try to handle urgent data inline */ 5761592Srgrimes#ifdef SO_OOBINLINE 577100612Syar if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on)) < 0) 578100609Syar syslog(LOG_WARNING, "control setsockopt (SO_OOBINLINE): %m"); 5791592Srgrimes#endif 5801592Srgrimes 5811592Srgrimes#ifdef F_SETOWN 5821592Srgrimes if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1) 5831592Srgrimes syslog(LOG_ERR, "fcntl F_SETOWN: %m"); 5841592Srgrimes#endif 58556668Sshin dolog((struct sockaddr *)&his_addr); 5861592Srgrimes /* 5871592Srgrimes * Set up default state 5881592Srgrimes */ 5891592Srgrimes data = -1; 5901592Srgrimes type = TYPE_A; 5911592Srgrimes form = FORM_N; 5921592Srgrimes stru = STRU_F; 5931592Srgrimes mode = MODE_S; 5941592Srgrimes tmpline[0] = '\0'; 5951592Srgrimes 5961592Srgrimes /* If logins are disabled, print out the message. */ 5971592Srgrimes if ((fd = fopen(_PATH_NOLOGIN,"r")) != NULL) { 5981592Srgrimes while (fgets(line, sizeof(line), fd) != NULL) { 5991592Srgrimes if ((cp = strchr(line, '\n')) != NULL) 6001592Srgrimes *cp = '\0'; 6011592Srgrimes lreply(530, "%s", line); 6021592Srgrimes } 6031592Srgrimes (void) fflush(stdout); 6041592Srgrimes (void) fclose(fd); 6051592Srgrimes reply(530, "System not available."); 6061592Srgrimes exit(0); 6071592Srgrimes } 60825283Sdavidn#ifdef VIRTUAL_HOSTING 60925283Sdavidn if ((fd = fopen(thishost->welcome, "r")) != NULL) { 61025283Sdavidn#else 6111592Srgrimes if ((fd = fopen(_PATH_FTPWELCOME, "r")) != NULL) { 61225283Sdavidn#endif 6131592Srgrimes while (fgets(line, sizeof(line), fd) != NULL) { 6141592Srgrimes if ((cp = strchr(line, '\n')) != NULL) 6151592Srgrimes *cp = '\0'; 6161592Srgrimes lreply(220, "%s", line); 6171592Srgrimes } 6181592Srgrimes (void) fflush(stdout); 6191592Srgrimes (void) fclose(fd); 6201592Srgrimes /* reply(220,) must follow */ 6211592Srgrimes } 62225283Sdavidn#ifndef VIRTUAL_HOSTING 62327650Sdavidn if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL) 62476096Smarkm fatalerror("Ran out of memory."); 62545422Sbrian (void) gethostname(hostname, MAXHOSTNAMELEN - 1); 62645422Sbrian hostname[MAXHOSTNAMELEN - 1] = '\0'; 62725283Sdavidn#endif 6281592Srgrimes reply(220, "%s FTP server (%s) ready.", hostname, version); 6291592Srgrimes for (;;) 6301592Srgrimes (void) yyparse(); 6311592Srgrimes /* NOTREACHED */ 6321592Srgrimes} 6331592Srgrimes 6341592Srgrimesstatic void 63590148Simplostconn(int signo) 6361592Srgrimes{ 6371592Srgrimes 63876096Smarkm if (ftpdebug) 6391592Srgrimes syslog(LOG_DEBUG, "lost connection"); 64031329Scharnier dologout(1); 6411592Srgrimes} 6421592Srgrimes 64389935Syarstatic void 64490148Simpsigquit(int signo) 64589935Syar{ 64689935Syar 64789935Syar syslog(LOG_ERR, "got signal %d", signo); 64889935Syar dologout(1); 64989935Syar} 65089935Syar 65125283Sdavidn#ifdef VIRTUAL_HOSTING 6521592Srgrimes/* 65325283Sdavidn * read in virtual host tables (if they exist) 65425283Sdavidn */ 65525283Sdavidn 65625283Sdavidnstatic void 65790148Simpinithosts(void) 65825283Sdavidn{ 659100182Syar int insert; 66099877Syar size_t len; 66125283Sdavidn FILE *fp; 66299877Syar char *cp, *mp, *line; 66399877Syar char *hostname; 664100182Syar char *vhost, *anonuser, *statfile, *welcome, *loginmsg; 66525283Sdavidn struct ftphost *hrp, *lhrp; 66656668Sshin struct addrinfo hints, *res, *ai; 66725283Sdavidn 66825283Sdavidn /* 66925283Sdavidn * Fill in the default host information 67025283Sdavidn */ 67199877Syar if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL) 67276096Smarkm fatalerror("Ran out of memory."); 67399877Syar if (gethostname(hostname, MAXHOSTNAMELEN) < 0) 67499877Syar hostname[0] = '\0'; 67599877Syar hostname[MAXHOSTNAMELEN - 1] = '\0'; 67699877Syar if ((hrp = malloc(sizeof(struct ftphost))) == NULL) 67799877Syar fatalerror("Ran out of memory."); 67899877Syar hrp->hostname = hostname; 67957124Sshin hrp->hostinfo = NULL; 68056668Sshin 68156668Sshin memset(&hints, 0, sizeof(hints)); 68256668Sshin hints.ai_flags = AI_CANONNAME; 68356668Sshin hints.ai_family = AF_UNSPEC; 68456668Sshin getaddrinfo(hrp->hostname, NULL, &hints, &res); 68556668Sshin if (res) 68657124Sshin hrp->hostinfo = res; 68725283Sdavidn hrp->statfile = _PATH_FTPDSTATFILE; 68825283Sdavidn hrp->welcome = _PATH_FTPWELCOME; 68925283Sdavidn hrp->loginmsg = _PATH_FTPLOGINMESG; 69025283Sdavidn hrp->anonuser = "ftp"; 69125283Sdavidn hrp->next = NULL; 69225283Sdavidn thishost = firsthost = lhrp = hrp; 69325283Sdavidn if ((fp = fopen(_PATH_FTPHOSTS, "r")) != NULL) { 69462100Sdavidn int addrsize, error, gothost; 69556668Sshin void *addr; 69656668Sshin struct hostent *hp; 69756668Sshin 69899877Syar while ((line = fgetln(fp, &len)) != NULL) { 69956668Sshin int i, hp_error; 70025283Sdavidn 70199877Syar /* skip comments */ 70299877Syar if (line[0] == '#') 70325283Sdavidn continue; 70499877Syar if (line[len - 1] == '\n') { 70599877Syar line[len - 1] = '\0'; 70699877Syar mp = NULL; 70799877Syar } else { 70899877Syar if ((mp = malloc(len + 1)) == NULL) 70999877Syar fatalerror("Ran out of memory."); 71099877Syar memcpy(mp, line, len); 71199877Syar mp[len] = '\0'; 71299877Syar line = mp; 71325283Sdavidn } 71425283Sdavidn cp = strtok(line, " \t"); 71599877Syar /* skip empty lines */ 71699877Syar if (cp == NULL) 71799877Syar goto nextline; 718100182Syar vhost = cp; 71956668Sshin 720100182Syar /* set defaults */ 721100182Syar anonuser = "ftp"; 722100182Syar statfile = _PATH_FTPDSTATFILE; 723100182Syar welcome = _PATH_FTPWELCOME; 724100182Syar loginmsg = _PATH_FTPLOGINMESG; 725100182Syar 726100182Syar /* 727100182Syar * Preparse the line so we can use its info 728100182Syar * for all the addresses associated with 729100182Syar * the virtual host name. 730100182Syar * Field 0, the virtual host name, is special: 731100182Syar * it's already parsed off and will be strdup'ed 732100182Syar * later, after we know its canonical form. 733100182Syar */ 734100182Syar for (i = 1; i < 5 && (cp = strtok(NULL, " \t")); i++) 735100182Syar if (*cp != '-' && (cp = strdup(cp))) 736100182Syar switch (i) { 737100182Syar case 1: /* anon user permissions */ 738100182Syar anonuser = cp; 739100182Syar break; 740100182Syar case 2: /* statistics file */ 741100182Syar statfile = cp; 742100182Syar break; 743100182Syar case 3: /* welcome message */ 744100182Syar welcome = cp; 745100182Syar break; 746100182Syar case 4: /* login message */ 747100182Syar loginmsg = cp; 748100182Syar break; 749100182Syar default: /* programming error */ 750100182Syar abort(); 751100182Syar /* NOTREACHED */ 752100182Syar } 753100182Syar 75456668Sshin hints.ai_flags = 0; 75556668Sshin hints.ai_family = AF_UNSPEC; 75656668Sshin hints.ai_flags = AI_PASSIVE; 757100182Syar error = getaddrinfo(vhost, NULL, &hints, &res); 75856668Sshin if (error != NULL) 75999877Syar goto nextline; 76056668Sshin for (ai = res; ai != NULL && ai->ai_addr != NULL; 76162100Sdavidn ai = ai->ai_next) { 76256668Sshin 76362100Sdavidn gothost = 0; 76425283Sdavidn for (hrp = firsthost; hrp != NULL; hrp = hrp->next) { 76557124Sshin struct addrinfo *hi; 76657124Sshin 76757124Sshin for (hi = hrp->hostinfo; hi != NULL; 76857124Sshin hi = hi->ai_next) 76957124Sshin if (hi->ai_addrlen == ai->ai_addrlen && 77057124Sshin memcmp(hi->ai_addr, 77157124Sshin ai->ai_addr, 77262100Sdavidn ai->ai_addr->sa_len) == 0) { 77362100Sdavidn gothost++; 77457124Sshin break; 775100183Syar } 77662100Sdavidn if (gothost) 77762100Sdavidn break; 77825283Sdavidn } 77925283Sdavidn if (hrp == NULL) { 78025283Sdavidn if ((hrp = malloc(sizeof(struct ftphost))) == NULL) 78199877Syar goto nextline; 782100182Syar insert = 1; 783100182Syar } else 784100182Syar insert = 0; /* host already in the chain */ 78557124Sshin hrp->hostinfo = res; 78657124Sshin 78725283Sdavidn /* 78825283Sdavidn * determine hostname to use. 78956668Sshin * force defined name if there is a valid alias 79025283Sdavidn * otherwise fallback to primary hostname 79125283Sdavidn */ 79256668Sshin /* XXX: getaddrinfo() can't do alias check */ 79357124Sshin switch(hrp->hostinfo->ai_family) { 79456668Sshin case AF_INET: 795100259Syar addr = &((struct sockaddr_in *)hrp->hostinfo->ai_addr)->sin_addr; 796100259Syar addrsize = sizeof(struct in_addr); 79756668Sshin break; 79856668Sshin case AF_INET6: 799100259Syar addr = &((struct sockaddr_in6 *)hrp->hostinfo->ai_addr)->sin6_addr; 800100259Syar addrsize = sizeof(struct in6_addr); 80156668Sshin break; 80256668Sshin default: 80356668Sshin /* should not reach here */ 80457124Sshin if (hrp->hostinfo != NULL) 80557124Sshin freeaddrinfo(hrp->hostinfo); 80656668Sshin free(hrp); 80799877Syar goto nextline; 80856668Sshin /* NOTREACHED */ 80956668Sshin } 810100612Syar if ((hp = getipnodebyaddr(addr, addrsize, 81157124Sshin hrp->hostinfo->ai_family, 81256668Sshin &hp_error)) != NULL) { 813100182Syar if (strcmp(vhost, hp->h_name) != 0) { 81425283Sdavidn if (hp->h_aliases == NULL) 815100182Syar vhost = hp->h_name; 81625283Sdavidn else { 81725283Sdavidn i = 0; 81825283Sdavidn while (hp->h_aliases[i] && 819100182Syar strcmp(vhost, hp->h_aliases[i]) != 0) 82025283Sdavidn ++i; 82125283Sdavidn if (hp->h_aliases[i] == NULL) 822100182Syar vhost = hp->h_name; 82325283Sdavidn } 82425283Sdavidn } 82525283Sdavidn } 826100182Syar if ((hrp->hostname = strdup(vhost)) == NULL) 827100182Syar goto nextline; 828100182Syar hrp->anonuser = anonuser; 829100182Syar hrp->statfile = statfile; 830100182Syar hrp->welcome = welcome; 831100182Syar hrp->loginmsg = loginmsg; 832100182Syar if (insert) { 833100182Syar hrp->next = NULL; 834100182Syar lhrp->next = hrp; 835100182Syar lhrp = hrp; 836100182Syar } 837100263Syar if (hp) 838100263Syar freehostent(hp); 83956668Sshin } 84099877Syarnextline: 84199877Syar if (mp) 84299877Syar free(mp); 84325283Sdavidn } 84425283Sdavidn (void) fclose(fp); 84525283Sdavidn } 84625283Sdavidn} 84725283Sdavidn 84825283Sdavidnstatic void 84990148Simpselecthost(union sockunion *su) 85025283Sdavidn{ 85125283Sdavidn struct ftphost *hrp; 85256668Sshin u_int16_t port; 85356668Sshin#ifdef INET6 85456668Sshin struct in6_addr *mapped_in6 = NULL; 85556668Sshin#endif 85657124Sshin struct addrinfo *hi; 85725283Sdavidn 85856668Sshin#ifdef INET6 85956668Sshin /* 86056668Sshin * XXX IPv4 mapped IPv6 addr consideraton, 86156668Sshin * specified in rfc2373. 86256668Sshin */ 86356668Sshin if (su->su_family == AF_INET6 && 86456668Sshin IN6_IS_ADDR_V4MAPPED(&su->su_sin6.sin6_addr)) 86556668Sshin mapped_in6 = &su->su_sin6.sin6_addr; 86656668Sshin#endif 86756668Sshin 86825283Sdavidn hrp = thishost = firsthost; /* default */ 86956668Sshin port = su->su_port; 87056668Sshin su->su_port = 0; 87125283Sdavidn while (hrp != NULL) { 87262100Sdavidn for (hi = hrp->hostinfo; hi != NULL; hi = hi->ai_next) { 87357124Sshin if (memcmp(su, hi->ai_addr, hi->ai_addrlen) == 0) { 87425283Sdavidn thishost = hrp; 87525283Sdavidn break; 87625283Sdavidn } 87756668Sshin#ifdef INET6 87856668Sshin /* XXX IPv4 mapped IPv6 addr consideraton */ 87957124Sshin if (hi->ai_addr->sa_family == AF_INET && mapped_in6 != NULL && 88056668Sshin (memcmp(&mapped_in6->s6_addr[12], 88157124Sshin &((struct sockaddr_in *)hi->ai_addr)->sin_addr, 88256668Sshin sizeof(struct in_addr)) == 0)) { 88356668Sshin thishost = hrp; 88456668Sshin break; 88556668Sshin } 88656668Sshin#endif 88762100Sdavidn } 88862100Sdavidn hrp = hrp->next; 88925283Sdavidn } 89056668Sshin su->su_port = port; 89125283Sdavidn /* setup static variables as appropriate */ 89225283Sdavidn hostname = thishost->hostname; 89325283Sdavidn ftpuser = thishost->anonuser; 89425283Sdavidn} 89525283Sdavidn#endif 89625283Sdavidn 89725283Sdavidn/* 8981592Srgrimes * Helper function for sgetpwnam(). 8991592Srgrimes */ 9001592Srgrimesstatic char * 90190148Simpsgetsave(char *s) 9021592Srgrimes{ 9031592Srgrimes char *new = malloc((unsigned) strlen(s) + 1); 9041592Srgrimes 9051592Srgrimes if (new == NULL) { 9061592Srgrimes perror_reply(421, "Local resource failure: malloc"); 9071592Srgrimes dologout(1); 9081592Srgrimes /* NOTREACHED */ 9091592Srgrimes } 9101592Srgrimes (void) strcpy(new, s); 9111592Srgrimes return (new); 9121592Srgrimes} 9131592Srgrimes 9141592Srgrimes/* 9151592Srgrimes * Save the result of a getpwnam. Used for USER command, since 9161592Srgrimes * the data returned must not be clobbered by any other command 9171592Srgrimes * (e.g., globbing). 9181592Srgrimes */ 9191592Srgrimesstatic struct passwd * 92090148Simpsgetpwnam(char *name) 9211592Srgrimes{ 9221592Srgrimes static struct passwd save; 9231592Srgrimes struct passwd *p; 9241592Srgrimes 9251592Srgrimes if ((p = getpwnam(name)) == NULL) 9261592Srgrimes return (p); 9271592Srgrimes if (save.pw_name) { 9281592Srgrimes free(save.pw_name); 9291592Srgrimes free(save.pw_passwd); 9301592Srgrimes free(save.pw_gecos); 9311592Srgrimes free(save.pw_dir); 9321592Srgrimes free(save.pw_shell); 9331592Srgrimes } 9341592Srgrimes save = *p; 9351592Srgrimes save.pw_name = sgetsave(p->pw_name); 9361592Srgrimes save.pw_passwd = sgetsave(p->pw_passwd); 9371592Srgrimes save.pw_gecos = sgetsave(p->pw_gecos); 9381592Srgrimes save.pw_dir = sgetsave(p->pw_dir); 9391592Srgrimes save.pw_shell = sgetsave(p->pw_shell); 9401592Srgrimes return (&save); 9411592Srgrimes} 9421592Srgrimes 9431592Srgrimesstatic int login_attempts; /* number of failed login attempts */ 9441592Srgrimesstatic int askpasswd; /* had user command, ask for passwd */ 94564778Ssheldonhstatic char curname[MAXLOGNAME]; /* current USER name */ 9461592Srgrimes 9471592Srgrimes/* 9481592Srgrimes * USER command. 9491592Srgrimes * Sets global passwd pointer pw if named account exists and is acceptable; 9501592Srgrimes * sets askpasswd if a PASS command is expected. If logged in previously, 9511592Srgrimes * need to reset state. If name is "ftp" or "anonymous", the name is not in 9521592Srgrimes * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return. 9531592Srgrimes * If account doesn't exist, ask for passwd anyway. Otherwise, check user 9541592Srgrimes * requesting login privileges. Disallow anyone who does not have a standard 9551592Srgrimes * shell as returned by getusershell(). Disallow anyone mentioned in the file 9561592Srgrimes * _PATH_FTPUSERS to allow people such as root and uucp to be avoided. 9571592Srgrimes */ 9581592Srgrimesvoid 95990148Simpuser(char *name) 9601592Srgrimes{ 9611592Srgrimes char *cp, *shell; 9621592Srgrimes 9631592Srgrimes if (logged_in) { 9641592Srgrimes if (guest) { 9651592Srgrimes reply(530, "Can't change user from guest login."); 9661592Srgrimes return; 96717435Spst } else if (dochroot) { 96817435Spst reply(530, "Can't change user from chroot user."); 96917435Spst return; 9701592Srgrimes } 9711592Srgrimes end_login(); 9721592Srgrimes } 9731592Srgrimes 9741592Srgrimes guest = 0; 9751592Srgrimes if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) { 97636349Ssteve if (checkuser(_PATH_FTPUSERS, "ftp", 0) || 97736349Ssteve checkuser(_PATH_FTPUSERS, "anonymous", 0)) 9781592Srgrimes reply(530, "User %s access denied.", name); 97925283Sdavidn#ifdef VIRTUAL_HOSTING 98025283Sdavidn else if ((pw = sgetpwnam(thishost->anonuser)) != NULL) { 98125283Sdavidn#else 9821592Srgrimes else if ((pw = sgetpwnam("ftp")) != NULL) { 98325283Sdavidn#endif 9841592Srgrimes guest = 1; 9851592Srgrimes askpasswd = 1; 9861592Srgrimes reply(331, 9873938Spst "Guest login ok, send your email address as password."); 9881592Srgrimes } else 9891592Srgrimes reply(530, "User %s unknown.", name); 9901592Srgrimes if (!askpasswd && logging) 9911592Srgrimes syslog(LOG_NOTICE, 9921592Srgrimes "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost); 9931592Srgrimes return; 9941592Srgrimes } 99520042Storstenb if (anon_only != 0) { 99620042Storstenb reply(530, "Sorry, only anonymous ftp allowed."); 99720042Storstenb return; 99820042Storstenb } 99920042Storstenb 100017478Smarkm if ((pw = sgetpwnam(name))) { 10011592Srgrimes if ((shell = pw->pw_shell) == NULL || *shell == 0) 10021592Srgrimes shell = _PATH_BSHELL; 10031592Srgrimes while ((cp = getusershell()) != NULL) 10041592Srgrimes if (strcmp(cp, shell) == 0) 10051592Srgrimes break; 10061592Srgrimes endusershell(); 10071592Srgrimes 100836349Ssteve if (cp == NULL || checkuser(_PATH_FTPUSERS, name, 1)) { 10091592Srgrimes reply(530, "User %s access denied.", name); 10101592Srgrimes if (logging) 10111592Srgrimes syslog(LOG_NOTICE, 10121592Srgrimes "FTP LOGIN REFUSED FROM %s, %s", 10131592Srgrimes remotehost, name); 10141592Srgrimes pw = (struct passwd *) NULL; 10151592Srgrimes return; 10161592Srgrimes } 10171592Srgrimes } 10181592Srgrimes if (logging) 10191592Srgrimes strncpy(curname, name, sizeof(curname)-1); 102088763Sache 102188763Sache pwok = 0; 102279469Smarkm#ifdef USE_PAM 102379469Smarkm /* XXX Kluge! The conversation mechanism needs to be fixed. */ 102488763Sache#endif 102588763Sache if (opiechallenge(&opiedata, name, opieprompt) == 0) { 102688763Sache pwok = (pw != NULL) && 102788763Sache opieaccessfile(remotehost) && 102888763Sache opiealways(pw->pw_dir); 102988763Sache reply(331, "Response to %s %s for %s.", 103088763Sache opieprompt, pwok ? "requested" : "required", name); 103188763Sache } else { 103288763Sache pwok = 1; 103384146Sache reply(331, "Password required for %s.", name); 103488763Sache } 10351592Srgrimes askpasswd = 1; 10361592Srgrimes /* 10371592Srgrimes * Delay before reading passwd after first failed 10381592Srgrimes * attempt to slow down passwd-guessing programs. 10391592Srgrimes */ 10401592Srgrimes if (login_attempts) 10411592Srgrimes sleep((unsigned) login_attempts); 10421592Srgrimes} 10431592Srgrimes 10441592Srgrimes/* 104517435Spst * Check if a user is in the file "fname" 10461592Srgrimes */ 10471592Srgrimesstatic int 104890148Simpcheckuser(char *fname, char *name, int pwset) 10491592Srgrimes{ 10501592Srgrimes FILE *fd; 10511592Srgrimes int found = 0; 105299877Syar size_t len; 105399877Syar char *line, *mp, *p; 10541592Srgrimes 105517435Spst if ((fd = fopen(fname, "r")) != NULL) { 105699877Syar while (!found && (line = fgetln(fd, &len)) != NULL) { 105799877Syar /* skip comments */ 105899877Syar if (line[0] == '#') 105999877Syar continue; 106099877Syar if (line[len - 1] == '\n') { 106199877Syar line[len - 1] = '\0'; 106299877Syar mp = NULL; 106399877Syar } else { 106499877Syar if ((mp = malloc(len + 1)) == NULL) 106599877Syar fatalerror("Ran out of memory."); 106699877Syar memcpy(mp, line, len); 106799877Syar mp[len] = '\0'; 106899877Syar line = mp; 106999877Syar } 107099877Syar /* avoid possible leading and trailing whitespace */ 107199877Syar p = strtok(line, " \t"); 107299877Syar /* skip empty lines */ 107399877Syar if (p == NULL) 107499877Syar goto nextline; 107599877Syar /* 107699877Syar * if first chr is '@', check group membership 107799877Syar */ 107899877Syar if (p[0] == '@') { 107999877Syar int i = 0; 108099877Syar struct group *grp; 108199877Syar 108299877Syar if ((grp = getgrnam(p+1)) == NULL) 108399877Syar goto nextline; 108425187Sdavidn /* 108599877Syar * Check user's default group 108625187Sdavidn */ 108799877Syar if (pwset && grp->gr_gid == pw->pw_gid) 108899877Syar found = 1; 108925187Sdavidn /* 109099877Syar * Check supplementary groups 109125187Sdavidn */ 109299877Syar while (!found && grp->gr_mem[i]) 109399877Syar found = strcmp(name, 109499877Syar grp->gr_mem[i++]) 109599877Syar == 0; 10961592Srgrimes } 109799877Syar /* 109899877Syar * Otherwise, just check for username match 109999877Syar */ 110099877Syar else 110199877Syar found = strcmp(p, name) == 0; 110299877Syarnextline: 110399877Syar if (mp) 110499877Syar free(mp); 110599877Syar } 11061592Srgrimes (void) fclose(fd); 11071592Srgrimes } 11081592Srgrimes return (found); 11091592Srgrimes} 11101592Srgrimes 11111592Srgrimes/* 11121592Srgrimes * Terminate login as previous user, if any, resetting state; 11131592Srgrimes * used when USER command is given or login fails. 11141592Srgrimes */ 11151592Srgrimesstatic void 111690148Simpend_login(void) 11171592Srgrimes{ 111874874Smarkm#ifdef USE_PAM 111974874Smarkm int e; 112074874Smarkm#endif 11211592Srgrimes 11221592Srgrimes (void) seteuid((uid_t)0); 11231592Srgrimes if (logged_in) 112489920Sume ftpd_logwtmp(ttyline, "", NULL); 11251592Srgrimes pw = NULL; 112625101Sdavidn#ifdef LOGIN_CAP 112725101Sdavidn setusercontext(NULL, getpwuid(0), (uid_t)0, 112825101Sdavidn LOGIN_SETPRIORITY|LOGIN_SETRESOURCES|LOGIN_SETUMASK); 112925101Sdavidn#endif 113074874Smarkm#ifdef USE_PAM 113174874Smarkm if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS) 113274874Smarkm syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e)); 113374874Smarkm if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS) 113474874Smarkm syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e)); 113574874Smarkm if ((e = pam_end(pamh, e)) != PAM_SUCCESS) 113674874Smarkm syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); 113774874Smarkm pamh = NULL; 113874874Smarkm#endif 11391592Srgrimes logged_in = 0; 11401592Srgrimes guest = 0; 114117435Spst dochroot = 0; 11421592Srgrimes} 11431592Srgrimes 114474874Smarkm#ifdef USE_PAM 114551433Smarkm 114651433Smarkm/* 114751433Smarkm * the following code is stolen from imap-uw PAM authentication module and 114851433Smarkm * login.c 114951433Smarkm */ 115051433Smarkm#define COPY_STRING(s) (s ? strdup(s) : NULL) 115151433Smarkm 115251433Smarkmstruct cred_t { 115351433Smarkm const char *uname; /* user name */ 115451433Smarkm const char *pass; /* password */ 115551433Smarkm}; 115651433Smarkmtypedef struct cred_t cred_t; 115751433Smarkm 115851433Smarkmstatic int 115951433Smarkmauth_conv(int num_msg, const struct pam_message **msg, 116051433Smarkm struct pam_response **resp, void *appdata) 116151433Smarkm{ 116251433Smarkm int i; 116351433Smarkm cred_t *cred = (cred_t *) appdata; 116491244Sdes struct pam_response *reply; 116551433Smarkm 116691244Sdes reply = calloc(num_msg, sizeof *reply); 116791244Sdes if (reply == NULL) 116891244Sdes return PAM_BUF_ERR; 116991244Sdes 117051433Smarkm for (i = 0; i < num_msg; i++) { 117151433Smarkm switch (msg[i]->msg_style) { 117251433Smarkm case PAM_PROMPT_ECHO_ON: /* assume want user name */ 117351433Smarkm reply[i].resp_retcode = PAM_SUCCESS; 117451433Smarkm reply[i].resp = COPY_STRING(cred->uname); 117551433Smarkm /* PAM frees resp. */ 117651433Smarkm break; 117751433Smarkm case PAM_PROMPT_ECHO_OFF: /* assume want password */ 117851433Smarkm reply[i].resp_retcode = PAM_SUCCESS; 117951433Smarkm reply[i].resp = COPY_STRING(cred->pass); 118051433Smarkm /* PAM frees resp. */ 118151433Smarkm break; 118251433Smarkm case PAM_TEXT_INFO: 118351433Smarkm case PAM_ERROR_MSG: 118451433Smarkm reply[i].resp_retcode = PAM_SUCCESS; 118551433Smarkm reply[i].resp = NULL; 118651433Smarkm break; 118751433Smarkm default: /* unknown message style */ 118851433Smarkm free(reply); 118951433Smarkm return PAM_CONV_ERR; 119051433Smarkm } 119151433Smarkm } 119251433Smarkm 119351433Smarkm *resp = reply; 119451433Smarkm return PAM_SUCCESS; 119551433Smarkm} 119651433Smarkm 119751433Smarkm/* 119851433Smarkm * Attempt to authenticate the user using PAM. Returns 0 if the user is 119951433Smarkm * authenticated, or 1 if not authenticated. If some sort of PAM system 120051433Smarkm * error occurs (e.g., the "/etc/pam.conf" file is missing) then this 120151433Smarkm * function returns -1. This can be used as an indication that we should 120251433Smarkm * fall back to a different authentication mechanism. 120351433Smarkm */ 120451433Smarkmstatic int 120551433Smarkmauth_pam(struct passwd **ppw, const char *pass) 120651433Smarkm{ 120751433Smarkm pam_handle_t *pamh = NULL; 120851433Smarkm const char *tmpl_user; 120951433Smarkm const void *item; 121051433Smarkm int rval; 121151433Smarkm int e; 121251433Smarkm cred_t auth_cred = { (*ppw)->pw_name, pass }; 121351433Smarkm struct pam_conv conv = { &auth_conv, &auth_cred }; 121451433Smarkm 121551433Smarkm e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh); 121651433Smarkm if (e != PAM_SUCCESS) { 121751433Smarkm syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, e)); 121851433Smarkm return -1; 121951433Smarkm } 122051433Smarkm 122167007Sguido e = pam_set_item(pamh, PAM_RHOST, remotehost); 122267007Sguido if (e != PAM_SUCCESS) { 122367007Sguido syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s", 122467007Sguido pam_strerror(pamh, e)); 122567007Sguido return -1; 122667007Sguido } 122767007Sguido 122851433Smarkm e = pam_authenticate(pamh, 0); 122951433Smarkm switch (e) { 123051433Smarkm case PAM_SUCCESS: 123151433Smarkm /* 123251433Smarkm * With PAM we support the concept of a "template" 123351433Smarkm * user. The user enters a login name which is 123451433Smarkm * authenticated by PAM, usually via a remote service 123551433Smarkm * such as RADIUS or TACACS+. If authentication 123651433Smarkm * succeeds, a different but related "template" name 123751433Smarkm * is used for setting the credentials, shell, and 123851433Smarkm * home directory. The name the user enters need only 123951433Smarkm * exist on the remote authentication server, but the 124051433Smarkm * template name must be present in the local password 124151433Smarkm * database. 124251433Smarkm * 124351433Smarkm * This is supported by two various mechanisms in the 124451433Smarkm * individual modules. However, from the application's 124551433Smarkm * point of view, the template user is always passed 124651433Smarkm * back as a changed value of the PAM_USER item. 124751433Smarkm */ 124851433Smarkm if ((e = pam_get_item(pamh, PAM_USER, &item)) == 124951433Smarkm PAM_SUCCESS) { 125051433Smarkm tmpl_user = (const char *) item; 125151433Smarkm if (strcmp((*ppw)->pw_name, tmpl_user) != 0) 125251433Smarkm *ppw = getpwnam(tmpl_user); 125351433Smarkm } else 125451433Smarkm syslog(LOG_ERR, "Couldn't get PAM_USER: %s", 125551433Smarkm pam_strerror(pamh, e)); 125651433Smarkm rval = 0; 125751433Smarkm break; 125851433Smarkm 125951433Smarkm case PAM_AUTH_ERR: 126051433Smarkm case PAM_USER_UNKNOWN: 126151433Smarkm case PAM_MAXTRIES: 126251433Smarkm rval = 1; 126351433Smarkm break; 126451433Smarkm 126551433Smarkm default: 126674874Smarkm syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e)); 126751433Smarkm rval = -1; 126851433Smarkm break; 126951433Smarkm } 127051433Smarkm 127174874Smarkm if (rval == 0) { 127274874Smarkm e = pam_acct_mgmt(pamh, 0); 127374874Smarkm if (e == PAM_NEW_AUTHTOK_REQD) { 127474874Smarkm e = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); 127574874Smarkm if (e != PAM_SUCCESS) { 127674874Smarkm syslog(LOG_ERR, "pam_chauthtok: %s", pam_strerror(pamh, e)); 127774874Smarkm rval = 1; 127874874Smarkm } 127974874Smarkm } else if (e != PAM_SUCCESS) { 128074874Smarkm rval = 1; 128174874Smarkm } 128251433Smarkm } 128374874Smarkm 128474874Smarkm if (rval != 0) { 128574874Smarkm if ((e = pam_end(pamh, e)) != PAM_SUCCESS) { 128674874Smarkm syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); 128774874Smarkm } 128874874Smarkm pamh = NULL; 128974874Smarkm } 129051433Smarkm return rval; 129151433Smarkm} 129251433Smarkm 129374874Smarkm#endif /* USE_PAM */ 129451433Smarkm 12951592Srgrimesvoid 129690148Simppass(char *passwd) 12971592Srgrimes{ 129817435Spst int rval; 12991592Srgrimes FILE *fd; 130025101Sdavidn#ifdef LOGIN_CAP 130125101Sdavidn login_cap_t *lc = NULL; 130225101Sdavidn#endif 130374874Smarkm#ifdef USE_PAM 130474874Smarkm int e; 130574874Smarkm#endif 130688763Sache char *xpasswd; 13071592Srgrimes 13081592Srgrimes if (logged_in || askpasswd == 0) { 13091592Srgrimes reply(503, "Login with USER first."); 13101592Srgrimes return; 13111592Srgrimes } 13121592Srgrimes askpasswd = 0; 13131592Srgrimes if (!guest) { /* "ftp" is only account allowed no password */ 131417435Spst if (pw == NULL) { 131517435Spst rval = 1; /* failure below */ 131617435Spst goto skip; 131717435Spst } 131874874Smarkm#ifdef USE_PAM 131951433Smarkm rval = auth_pam(&pw, passwd); 132089622Sache if (rval >= 0) { 132189622Sache opieunlock(); 132217435Spst goto skip; 132389622Sache } 132489622Sache#endif 132588763Sache if (opieverify(&opiedata, passwd) == 0) 132688763Sache xpasswd = pw->pw_passwd; 132789622Sache else if (pwok) { 132888763Sache xpasswd = crypt(passwd, pw->pw_passwd); 132989622Sache if (passwd[0] == '\0' && pw->pw_passwd[0] != '\0') 133089622Sache xpasswd = ":"; 133189622Sache } else { 133288763Sache rval = 1; 133388763Sache goto skip; 133488763Sache } 133588763Sache rval = strcmp(pw->pw_passwd, xpasswd); 133689622Sache if (pw->pw_expire && time(NULL) >= pw->pw_expire) 133717435Spst rval = 1; /* failure */ 133817435Spstskip: 133917435Spst /* 134017435Spst * If rval == 1, the user failed the authentication check 134151433Smarkm * above. If rval == 0, either PAM or local authentication 134217435Spst * succeeded. 134317435Spst */ 134417435Spst if (rval) { 13451592Srgrimes reply(530, "Login incorrect."); 13461592Srgrimes if (logging) 13471592Srgrimes syslog(LOG_NOTICE, 13481592Srgrimes "FTP LOGIN FAILED FROM %s, %s", 13491592Srgrimes remotehost, curname); 13501592Srgrimes pw = NULL; 13511592Srgrimes if (login_attempts++ >= 5) { 13521592Srgrimes syslog(LOG_NOTICE, 13531592Srgrimes "repeated login failures from %s", 13541592Srgrimes remotehost); 13551592Srgrimes exit(0); 13561592Srgrimes } 13571592Srgrimes return; 13581592Srgrimes } 13591592Srgrimes } 13601592Srgrimes login_attempts = 0; /* this time successful */ 13611592Srgrimes if (setegid((gid_t)pw->pw_gid) < 0) { 13621592Srgrimes reply(550, "Can't set gid."); 13631592Srgrimes return; 13641592Srgrimes } 136525101Sdavidn /* May be overridden by login.conf */ 136625101Sdavidn (void) umask(defumask); 136725101Sdavidn#ifdef LOGIN_CAP 136825674Sdavidn if ((lc = login_getpwclass(pw)) != NULL) { 136925101Sdavidn char remote_ip[MAXHOSTNAMELEN]; 137025101Sdavidn 137156668Sshin getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len, 137256668Sshin remote_ip, sizeof(remote_ip) - 1, NULL, 0, 137399255Sume NI_NUMERICHOST); 137425101Sdavidn remote_ip[sizeof(remote_ip) - 1] = 0; 137525101Sdavidn if (!auth_hostok(lc, remotehost, remote_ip)) { 137625101Sdavidn syslog(LOG_INFO|LOG_AUTH, 137725101Sdavidn "FTP LOGIN FAILED (HOST) as %s: permission denied.", 137825101Sdavidn pw->pw_name); 137925101Sdavidn reply(530, "Permission denied.\n"); 138025101Sdavidn pw = NULL; 138125101Sdavidn return; 138225101Sdavidn } 138325101Sdavidn if (!auth_timeok(lc, time(NULL))) { 138425101Sdavidn reply(530, "Login not available right now.\n"); 138525101Sdavidn pw = NULL; 138625101Sdavidn return; 138725101Sdavidn } 138825101Sdavidn } 138925101Sdavidn setusercontext(lc, pw, (uid_t)0, 139040310Sdes LOGIN_SETLOGIN|LOGIN_SETGROUP|LOGIN_SETPRIORITY| 139140310Sdes LOGIN_SETRESOURCES|LOGIN_SETUMASK); 139225101Sdavidn#else 139340310Sdes setlogin(pw->pw_name); 13941592Srgrimes (void) initgroups(pw->pw_name, pw->pw_gid); 139525101Sdavidn#endif 13961592Srgrimes 139774874Smarkm#ifdef USE_PAM 139874874Smarkm if (pamh) { 139974874Smarkm if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) { 140074874Smarkm syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, e)); 140174874Smarkm } else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) { 140274874Smarkm syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e)); 140374874Smarkm } 140474874Smarkm } 140574874Smarkm#endif 140674874Smarkm 14071592Srgrimes /* open wtmp before chroot */ 140889920Sume ftpd_logwtmp(ttyline, pw->pw_name, (struct sockaddr *)&his_addr); 14091592Srgrimes logged_in = 1; 14101592Srgrimes 141117435Spst if (guest && stats && statfd < 0) 141225283Sdavidn#ifdef VIRTUAL_HOSTING 141325283Sdavidn if ((statfd = open(thishost->statfile, O_WRONLY|O_APPEND)) < 0) 141425283Sdavidn#else 14156740Sguido if ((statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND)) < 0) 141625283Sdavidn#endif 14176740Sguido stats = 0; 14186740Sguido 141925101Sdavidn dochroot = 142025101Sdavidn#ifdef LOGIN_CAP /* Allow login.conf configuration as well */ 142125101Sdavidn login_getcapbool(lc, "ftp-chroot", 0) || 142225101Sdavidn#endif 142336349Ssteve checkuser(_PATH_FTPCHROOT, pw->pw_name, 1); 14241592Srgrimes if (guest) { 14251592Srgrimes /* 14261592Srgrimes * We MUST do a chdir() after the chroot. Otherwise 14271592Srgrimes * the old current directory will be accessible as "." 14281592Srgrimes * outside the new root! 14291592Srgrimes */ 14301592Srgrimes if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) { 14311592Srgrimes reply(550, "Can't set guest privileges."); 14321592Srgrimes goto bad; 14331592Srgrimes } 143417435Spst } else if (dochroot) { 143517435Spst if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) { 143617435Spst reply(550, "Can't change root."); 143717435Spst goto bad; 143817435Spst } 14391592Srgrimes } else if (chdir(pw->pw_dir) < 0) { 14401592Srgrimes if (chdir("/") < 0) { 14411592Srgrimes reply(530, "User %s: can't change directory to %s.", 14421592Srgrimes pw->pw_name, pw->pw_dir); 14431592Srgrimes goto bad; 14441592Srgrimes } else 14451592Srgrimes lreply(230, "No directory! Logging in with home=/"); 14461592Srgrimes } 14471592Srgrimes if (seteuid((uid_t)pw->pw_uid) < 0) { 14481592Srgrimes reply(550, "Can't set uid."); 14491592Srgrimes goto bad; 14501592Srgrimes } 14518696Sdg 14521592Srgrimes /* 14531592Srgrimes * Display a login message, if it exists. 14541592Srgrimes * N.B. reply(230,) must follow the message. 14551592Srgrimes */ 145625283Sdavidn#ifdef VIRTUAL_HOSTING 145725283Sdavidn if ((fd = fopen(thishost->loginmsg, "r")) != NULL) { 145825283Sdavidn#else 14591592Srgrimes if ((fd = fopen(_PATH_FTPLOGINMESG, "r")) != NULL) { 146025283Sdavidn#endif 14611592Srgrimes char *cp, line[LINE_MAX]; 14621592Srgrimes 14631592Srgrimes while (fgets(line, sizeof(line), fd) != NULL) { 14641592Srgrimes if ((cp = strchr(line, '\n')) != NULL) 14651592Srgrimes *cp = '\0'; 14661592Srgrimes lreply(230, "%s", line); 14671592Srgrimes } 14681592Srgrimes (void) fflush(stdout); 14691592Srgrimes (void) fclose(fd); 14701592Srgrimes } 14711592Srgrimes if (guest) { 14726740Sguido if (ident != NULL) 14736740Sguido free(ident); 147417433Spst ident = strdup(passwd); 147517433Spst if (ident == NULL) 147676096Smarkm fatalerror("Ran out of memory."); 147717433Spst 14781592Srgrimes reply(230, "Guest login ok, access restrictions apply."); 14791592Srgrimes#ifdef SETPROCTITLE 148025283Sdavidn#ifdef VIRTUAL_HOSTING 148125283Sdavidn if (thishost != firsthost) 148225283Sdavidn snprintf(proctitle, sizeof(proctitle), 148383308Smikeh "%s: anonymous(%s)/%s", remotehost, hostname, 148483308Smikeh passwd); 148525283Sdavidn else 148625283Sdavidn#endif 148725283Sdavidn snprintf(proctitle, sizeof(proctitle), 148883308Smikeh "%s: anonymous/%s", remotehost, passwd); 148913139Speter setproctitle("%s", proctitle); 14901592Srgrimes#endif /* SETPROCTITLE */ 14911592Srgrimes if (logging) 14921592Srgrimes syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s", 14931592Srgrimes remotehost, passwd); 14941592Srgrimes } else { 149584841Syar if (dochroot) 149684841Syar reply(230, "User %s logged in, " 149784841Syar "access restrictions apply.", pw->pw_name); 149884841Syar else 149984841Syar reply(230, "User %s logged in.", pw->pw_name); 150025986Sdanny 15011592Srgrimes#ifdef SETPROCTITLE 15021592Srgrimes snprintf(proctitle, sizeof(proctitle), 150384842Syar "%s: user/%s", remotehost, pw->pw_name); 150413139Speter setproctitle("%s", proctitle); 15051592Srgrimes#endif /* SETPROCTITLE */ 15061592Srgrimes if (logging) 15071592Srgrimes syslog(LOG_INFO, "FTP LOGIN FROM %s as %s", 15081592Srgrimes remotehost, pw->pw_name); 15091592Srgrimes } 151025101Sdavidn#ifdef LOGIN_CAP 151125101Sdavidn login_close(lc); 151225101Sdavidn#endif 15131592Srgrimes return; 15141592Srgrimesbad: 15151592Srgrimes /* Forget all about it... */ 151625101Sdavidn#ifdef LOGIN_CAP 151725101Sdavidn login_close(lc); 151825101Sdavidn#endif 15191592Srgrimes end_login(); 15201592Srgrimes} 15211592Srgrimes 15221592Srgrimesvoid 152390148Simpretrieve(char *cmd, char *name) 15241592Srgrimes{ 15251592Srgrimes FILE *fin, *dout; 15261592Srgrimes struct stat st; 152790148Simp int (*closefunc)(FILE *); 152836612Sjb time_t start; 15291592Srgrimes 15301592Srgrimes if (cmd == 0) { 15311592Srgrimes fin = fopen(name, "r"), closefunc = fclose; 15321592Srgrimes st.st_size = 0; 15331592Srgrimes } else { 15341592Srgrimes char line[BUFSIZ]; 15351592Srgrimes 153631973Simp (void) snprintf(line, sizeof(line), cmd, name), name = line; 15371592Srgrimes fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose; 15381592Srgrimes st.st_size = -1; 15391592Srgrimes st.st_blksize = BUFSIZ; 15401592Srgrimes } 15411592Srgrimes if (fin == NULL) { 15421592Srgrimes if (errno != 0) { 15431592Srgrimes perror_reply(550, name); 15441592Srgrimes if (cmd == 0) { 15451592Srgrimes LOGCMD("get", name); 15461592Srgrimes } 15471592Srgrimes } 15481592Srgrimes return; 15491592Srgrimes } 15501592Srgrimes byte_count = -1; 15511592Srgrimes if (cmd == 0 && (fstat(fileno(fin), &st) < 0 || !S_ISREG(st.st_mode))) { 15521592Srgrimes reply(550, "%s: not a plain file.", name); 15531592Srgrimes goto done; 15541592Srgrimes } 15551592Srgrimes if (restart_point) { 15561592Srgrimes if (type == TYPE_A) { 15571592Srgrimes off_t i, n; 15581592Srgrimes int c; 15591592Srgrimes 15601592Srgrimes n = restart_point; 15611592Srgrimes i = 0; 15621592Srgrimes while (i++ < n) { 15631592Srgrimes if ((c=getc(fin)) == EOF) { 15641592Srgrimes perror_reply(550, name); 15651592Srgrimes goto done; 15661592Srgrimes } 15671592Srgrimes if (c == '\n') 15681592Srgrimes i++; 15691592Srgrimes } 15701592Srgrimes } else if (lseek(fileno(fin), restart_point, L_SET) < 0) { 15711592Srgrimes perror_reply(550, name); 15721592Srgrimes goto done; 15731592Srgrimes } 15741592Srgrimes } 15751592Srgrimes dout = dataconn(name, st.st_size, "w"); 15761592Srgrimes if (dout == NULL) 15771592Srgrimes goto done; 15786740Sguido time(&start); 15798240Swollman send_data(fin, dout, st.st_blksize, st.st_size, 15808240Swollman restart_point == 0 && cmd == 0 && S_ISREG(st.st_mode)); 15816740Sguido if (cmd == 0 && guest && stats) 158217433Spst logxfer(name, st.st_size, start); 15831592Srgrimes (void) fclose(dout); 15841592Srgrimes data = -1; 15851592Srgrimes pdata = -1; 15861592Srgrimesdone: 15871592Srgrimes if (cmd == 0) 15881592Srgrimes LOGBYTES("get", name, byte_count); 15891592Srgrimes (*closefunc)(fin); 15901592Srgrimes} 15911592Srgrimes 15921592Srgrimesvoid 159390148Simpstore(char *name, char *mode, int unique) 15941592Srgrimes{ 1595101537Syar int fd; 15961592Srgrimes FILE *fout, *din; 15971592Srgrimes struct stat st; 159890148Simp int (*closefunc)(FILE *); 15991592Srgrimes 1600101537Syar if (*mode == 'a') { /* APPE */ 1601101537Syar if (unique) { 1602101537Syar /* Programming error */ 1603101537Syar syslog(LOG_ERR, "Internal: unique flag to APPE"); 1604101537Syar unique = 0; 1605101537Syar } 1606101537Syar if (guest && noguestmod) { 1607101537Syar reply(550, "Appending to existing file denied"); 1608101537Syar goto err; 1609101537Syar } 1610101537Syar restart_point = 0; /* not affected by preceding REST */ 16111592Srgrimes } 1612101537Syar if (unique) /* STOU overrides REST */ 1613101537Syar restart_point = 0; 1614101537Syar if (guest && noguestmod) { 1615101537Syar if (restart_point) { /* guest STOR w/REST */ 1616101537Syar reply(550, "Modifying existing file denied"); 1617101537Syar goto err; 1618101537Syar } else /* treat guest STOR as STOU */ 1619101537Syar unique = 1; 1620101537Syar } 16211592Srgrimes 16221592Srgrimes if (restart_point) 1623101537Syar mode = "r+"; /* so ASCII manual seek can work */ 1624101537Syar if (unique) { 1625101537Syar if ((fd = guniquefd(name, &name)) < 0) 1626101537Syar goto err; 1627101537Syar fout = fdopen(fd, mode); 1628101537Syar } else 1629101537Syar fout = fopen(name, mode); 16301592Srgrimes closefunc = fclose; 16311592Srgrimes if (fout == NULL) { 16321592Srgrimes perror_reply(553, name); 1633101537Syar goto err; 16341592Srgrimes } 16351592Srgrimes byte_count = -1; 16361592Srgrimes if (restart_point) { 16371592Srgrimes if (type == TYPE_A) { 16381592Srgrimes off_t i, n; 16391592Srgrimes int c; 16401592Srgrimes 16411592Srgrimes n = restart_point; 16421592Srgrimes i = 0; 16431592Srgrimes while (i++ < n) { 16441592Srgrimes if ((c=getc(fout)) == EOF) { 16451592Srgrimes perror_reply(550, name); 16461592Srgrimes goto done; 16471592Srgrimes } 16481592Srgrimes if (c == '\n') 16491592Srgrimes i++; 16501592Srgrimes } 16511592Srgrimes /* 16521592Srgrimes * We must do this seek to "current" position 16531592Srgrimes * because we are changing from reading to 16541592Srgrimes * writing. 16551592Srgrimes */ 165682792Sache if (fseeko(fout, (off_t)0, SEEK_CUR) < 0) { 16571592Srgrimes perror_reply(550, name); 16581592Srgrimes goto done; 16591592Srgrimes } 16601592Srgrimes } else if (lseek(fileno(fout), restart_point, L_SET) < 0) { 16611592Srgrimes perror_reply(550, name); 16621592Srgrimes goto done; 16631592Srgrimes } 16641592Srgrimes } 16651592Srgrimes din = dataconn(name, (off_t)-1, "r"); 16661592Srgrimes if (din == NULL) 16671592Srgrimes goto done; 16681592Srgrimes if (receive_data(din, fout) == 0) { 16691592Srgrimes if (unique) 16701592Srgrimes reply(226, "Transfer complete (unique file name:%s).", 16711592Srgrimes name); 16721592Srgrimes else 16731592Srgrimes reply(226, "Transfer complete."); 16741592Srgrimes } 16751592Srgrimes (void) fclose(din); 16761592Srgrimes data = -1; 16771592Srgrimes pdata = -1; 16781592Srgrimesdone: 16791592Srgrimes LOGBYTES(*mode == 'w' ? "put" : "append", name, byte_count); 16801592Srgrimes (*closefunc)(fout); 1681101537Syar return; 1682101537Syarerr: 1683101537Syar LOGCMD(*mode == 'a' ? "append" : "put" , name); 1684101537Syar return; 16851592Srgrimes} 16861592Srgrimes 16871592Srgrimesstatic FILE * 168890148Simpgetdatasock(char *mode) 16891592Srgrimes{ 16901592Srgrimes int on = 1, s, t, tries; 16911592Srgrimes 16921592Srgrimes if (data >= 0) 16931592Srgrimes return (fdopen(data, mode)); 16941592Srgrimes (void) seteuid((uid_t)0); 169556668Sshin 169656668Sshin s = socket(data_dest.su_family, SOCK_STREAM, 0); 16971592Srgrimes if (s < 0) 16981592Srgrimes goto bad; 1699100612Syar if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) 1700100609Syar syslog(LOG_WARNING, "data setsockopt (SO_REUSEADDR): %m"); 17011592Srgrimes /* anchor socket to avoid multi-homing problems */ 170256668Sshin data_source = ctrl_addr; 170356668Sshin data_source.su_port = htons(20); /* ftp-data port */ 17041592Srgrimes for (tries = 1; ; tries++) { 17051592Srgrimes if (bind(s, (struct sockaddr *)&data_source, 170656668Sshin data_source.su_len) >= 0) 17071592Srgrimes break; 17081592Srgrimes if (errno != EADDRINUSE || tries > 10) 17091592Srgrimes goto bad; 17101592Srgrimes sleep(tries); 17111592Srgrimes } 17121592Srgrimes (void) seteuid((uid_t)pw->pw_uid); 17131592Srgrimes#ifdef IP_TOS 171456668Sshin if (data_source.su_family == AF_INET) 171556668Sshin { 17161592Srgrimes on = IPTOS_THROUGHPUT; 1717100612Syar if (setsockopt(s, IPPROTO_IP, IP_TOS, &on, sizeof(int)) < 0) 1718100609Syar syslog(LOG_WARNING, "data setsockopt (IP_TOS): %m"); 171956668Sshin } 17201592Srgrimes#endif 17218240Swollman#ifdef TCP_NOPUSH 17228240Swollman /* 17238240Swollman * Turn off push flag to keep sender TCP from sending short packets 17248240Swollman * at the boundaries of each write(). Should probably do a SO_SNDBUF 17258240Swollman * to set the send buffer size as well, but that may not be desirable 17268240Swollman * in heavy-load situations. 17278240Swollman */ 17288240Swollman on = 1; 1729100612Syar if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, &on, sizeof on) < 0) 1730100609Syar syslog(LOG_WARNING, "data setsockopt (TCP_NOPUSH): %m"); 17318240Swollman#endif 17328240Swollman#ifdef SO_SNDBUF 17338240Swollman on = 65536; 1734100612Syar if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &on, sizeof on) < 0) 1735100609Syar syslog(LOG_WARNING, "data setsockopt (SO_SNDBUF): %m"); 17368240Swollman#endif 17378240Swollman 17381592Srgrimes return (fdopen(s, mode)); 17391592Srgrimesbad: 17401592Srgrimes /* Return the real value of errno (close may change it) */ 17411592Srgrimes t = errno; 17421592Srgrimes (void) seteuid((uid_t)pw->pw_uid); 17431592Srgrimes (void) close(s); 17441592Srgrimes errno = t; 17451592Srgrimes return (NULL); 17461592Srgrimes} 17471592Srgrimes 17481592Srgrimesstatic FILE * 174990148Simpdataconn(char *name, off_t size, char *mode) 17501592Srgrimes{ 17511592Srgrimes char sizebuf[32]; 17521592Srgrimes FILE *file; 17531592Srgrimes int retry = 0, tos; 17541592Srgrimes 17551592Srgrimes file_size = size; 17561592Srgrimes byte_count = 0; 17571592Srgrimes if (size != (off_t) -1) 175831973Simp (void) snprintf(sizebuf, sizeof(sizebuf), " (%qd bytes)", size); 17591592Srgrimes else 176031973Simp *sizebuf = '\0'; 17611592Srgrimes if (pdata >= 0) { 176256668Sshin union sockunion from; 176386628Syar int flags; 176456668Sshin int s, fromlen = ctrl_addr.su_len; 176512532Sguido struct timeval timeout; 176612532Sguido fd_set set; 17671592Srgrimes 176812532Sguido FD_ZERO(&set); 176912532Sguido FD_SET(pdata, &set); 177012532Sguido 177112532Sguido timeout.tv_usec = 0; 177212532Sguido timeout.tv_sec = 120; 177312532Sguido 177486628Syar /* 177586628Syar * Granted a socket is in the blocking I/O mode, 177686628Syar * accept() will block after a successful select() 177786628Syar * if the selected connection dies in between. 177886628Syar * Therefore set the non-blocking I/O flag here. 177986628Syar */ 178086628Syar if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 || 178186628Syar fcntl(pdata, F_SETFL, flags | O_NONBLOCK) == -1) 178286628Syar goto pdata_err; 178386628Syar if (select(pdata+1, &set, (fd_set *) 0, (fd_set *) 0, &timeout) <= 0 || 178486628Syar (s = accept(pdata, (struct sockaddr *) &from, &fromlen)) < 0) 178586628Syar goto pdata_err; 17861592Srgrimes (void) close(pdata); 17871592Srgrimes pdata = s; 178886628Syar /* 1789101809Syar * Unset the inherited non-blocking I/O flag 1790101809Syar * on the child socket so stdio can work on it. 179186628Syar */ 179286628Syar if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 || 179386628Syar fcntl(pdata, F_SETFL, flags & ~O_NONBLOCK) == -1) 179486628Syar goto pdata_err; 17951592Srgrimes#ifdef IP_TOS 179656668Sshin if (from.su_family == AF_INET) 179756668Sshin { 179817435Spst tos = IPTOS_THROUGHPUT; 1799100612Syar if (setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0) 1800100609Syar syslog(LOG_WARNING, "pdata setsockopt (IP_TOS): %m"); 180156668Sshin } 18021592Srgrimes#endif 18031592Srgrimes reply(150, "Opening %s mode data connection for '%s'%s.", 18041592Srgrimes type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); 18051592Srgrimes return (fdopen(pdata, mode)); 180686628Syarpdata_err: 180786628Syar reply(425, "Can't open data connection."); 180886628Syar (void) close(pdata); 180986628Syar pdata = -1; 181086628Syar return (NULL); 18111592Srgrimes } 18121592Srgrimes if (data >= 0) { 18131592Srgrimes reply(125, "Using existing data connection for '%s'%s.", 18141592Srgrimes name, sizebuf); 18151592Srgrimes usedefault = 1; 18161592Srgrimes return (fdopen(data, mode)); 18171592Srgrimes } 18181592Srgrimes if (usedefault) 18191592Srgrimes data_dest = his_addr; 18201592Srgrimes usedefault = 1; 18211592Srgrimes file = getdatasock(mode); 18221592Srgrimes if (file == NULL) { 182356668Sshin char hostbuf[BUFSIZ], portbuf[BUFSIZ]; 182456668Sshin getnameinfo((struct sockaddr *)&data_source, 182556668Sshin data_source.su_len, hostbuf, sizeof(hostbuf) - 1, 182656668Sshin portbuf, sizeof(portbuf), 182799255Sume NI_NUMERICHOST|NI_NUMERICSERV); 182856668Sshin reply(425, "Can't create data socket (%s,%s): %s.", 182956668Sshin hostbuf, portbuf, strerror(errno)); 18301592Srgrimes return (NULL); 18311592Srgrimes } 18321592Srgrimes data = fileno(file); 18331592Srgrimes while (connect(data, (struct sockaddr *)&data_dest, 183456668Sshin data_dest.su_len) < 0) { 18351592Srgrimes if (errno == EADDRINUSE && retry < swaitmax) { 18361592Srgrimes sleep((unsigned) swaitint); 18371592Srgrimes retry += swaitint; 18381592Srgrimes continue; 18391592Srgrimes } 18401592Srgrimes perror_reply(425, "Can't build data connection"); 18411592Srgrimes (void) fclose(file); 18421592Srgrimes data = -1; 18431592Srgrimes return (NULL); 18441592Srgrimes } 18451592Srgrimes reply(150, "Opening %s mode data connection for '%s'%s.", 18461592Srgrimes type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); 18471592Srgrimes return (file); 18481592Srgrimes} 18491592Srgrimes 18501592Srgrimes/* 18511592Srgrimes * Tranfer the contents of "instr" to "outstr" peer using the appropriate 18528240Swollman * encapsulation of the data subject to Mode, Structure, and Type. 18531592Srgrimes * 18541592Srgrimes * NB: Form isn't handled. 18551592Srgrimes */ 185689935Syarstatic int 185790148Simpsend_data(FILE *instr, FILE *outstr, off_t blksize, off_t filesize, int isreg) 18581592Srgrimes{ 185970205Sdan int c, filefd, netfd; 186070205Sdan char *buf; 186170205Sdan off_t cnt; 18621592Srgrimes 18631592Srgrimes transflag++; 18641592Srgrimes switch (type) { 18651592Srgrimes 18661592Srgrimes case TYPE_A: 18671592Srgrimes while ((c = getc(instr)) != EOF) { 186889935Syar if (recvurg) 186989935Syar goto got_oob; 18701592Srgrimes byte_count++; 18711592Srgrimes if (c == '\n') { 18721592Srgrimes if (ferror(outstr)) 18731592Srgrimes goto data_err; 18741592Srgrimes (void) putc('\r', outstr); 18751592Srgrimes } 18761592Srgrimes (void) putc(c, outstr); 18771592Srgrimes } 187889935Syar if (recvurg) 187989935Syar goto got_oob; 18801592Srgrimes fflush(outstr); 18811592Srgrimes transflag = 0; 18821592Srgrimes if (ferror(instr)) 18831592Srgrimes goto file_err; 18841592Srgrimes if (ferror(outstr)) 18851592Srgrimes goto data_err; 18861592Srgrimes reply(226, "Transfer complete."); 188789935Syar return (0); 18881592Srgrimes 18891592Srgrimes case TYPE_I: 18901592Srgrimes case TYPE_L: 18918240Swollman /* 18928240Swollman * isreg is only set if we are not doing restart and we 18938240Swollman * are sending a regular file 18948240Swollman */ 18958240Swollman netfd = fileno(outstr); 18968870Srgrimes filefd = fileno(instr); 18978240Swollman 189870205Sdan if (isreg) { 189970205Sdan 190070205Sdan off_t offset; 190170205Sdan int err; 190270205Sdan 190370205Sdan err = cnt = offset = 0; 190470205Sdan 190590604Smaxim while (err != -1 && filesize > 0) { 190690604Smaxim err = sendfile(filefd, netfd, offset, 0, 190770205Sdan (struct sf_hdtr *) NULL, &cnt, 0); 190899212Smaxim /* 190999212Smaxim * Calculate byte_count before OOB processing. 191099212Smaxim * It can be used in myoob() later. 191199212Smaxim */ 191299212Smaxim byte_count += cnt; 191389935Syar if (recvurg) 191489935Syar goto got_oob; 191570205Sdan offset += cnt; 191690604Smaxim filesize -= cnt; 19178240Swollman 191870205Sdan if (err == -1) { 191970205Sdan if (!cnt) 192070205Sdan goto oldway; 192170205Sdan 192270205Sdan goto data_err; 192370205Sdan } 192470205Sdan } 192570205Sdan 192699318Sdan transflag = 0; 19278240Swollman reply(226, "Transfer complete."); 192889935Syar return (0); 19298240Swollman } 19308240Swollman 19318240Swollmanoldway: 19321592Srgrimes if ((buf = malloc((u_int)blksize)) == NULL) { 19331592Srgrimes transflag = 0; 19341592Srgrimes perror_reply(451, "Local resource failure: malloc"); 193589935Syar return (-1); 19361592Srgrimes } 19378870Srgrimes 19381592Srgrimes while ((cnt = read(filefd, buf, (u_int)blksize)) > 0 && 19391592Srgrimes write(netfd, buf, cnt) == cnt) 19401592Srgrimes byte_count += cnt; 19411592Srgrimes transflag = 0; 19421592Srgrimes (void)free(buf); 19431592Srgrimes if (cnt != 0) { 19441592Srgrimes if (cnt < 0) 19451592Srgrimes goto file_err; 19461592Srgrimes goto data_err; 19471592Srgrimes } 19481592Srgrimes reply(226, "Transfer complete."); 194989935Syar return (0); 19501592Srgrimes default: 19511592Srgrimes transflag = 0; 19521592Srgrimes reply(550, "Unimplemented TYPE %d in send_data", type); 195389935Syar return (-1); 19541592Srgrimes } 19551592Srgrimes 19561592Srgrimesdata_err: 19571592Srgrimes transflag = 0; 19581592Srgrimes perror_reply(426, "Data connection"); 195989935Syar return (-1); 19601592Srgrimes 19611592Srgrimesfile_err: 19621592Srgrimes transflag = 0; 19631592Srgrimes perror_reply(551, "Error on input file"); 196489935Syar return (-1); 196589935Syar 196689935Syargot_oob: 196789935Syar myoob(); 196889935Syar recvurg = 0; 196989935Syar transflag = 0; 197089935Syar return (-1); 19711592Srgrimes} 19721592Srgrimes 19731592Srgrimes/* 19741592Srgrimes * Transfer data from peer to "outstr" using the appropriate encapulation of 19751592Srgrimes * the data subject to Mode, Structure, and Type. 19761592Srgrimes * 19771592Srgrimes * N.B.: Form isn't handled. 19781592Srgrimes */ 19791592Srgrimesstatic int 198090148Simpreceive_data(FILE *instr, FILE *outstr) 19811592Srgrimes{ 19821592Srgrimes int c; 198317433Spst int cnt, bare_lfs; 19841592Srgrimes char buf[BUFSIZ]; 19851592Srgrimes 19861592Srgrimes transflag++; 198717433Spst bare_lfs = 0; 198817433Spst 19891592Srgrimes switch (type) { 19901592Srgrimes 19911592Srgrimes case TYPE_I: 19921592Srgrimes case TYPE_L: 19931592Srgrimes while ((cnt = read(fileno(instr), buf, sizeof(buf))) > 0) { 199489935Syar if (recvurg) 199589935Syar goto got_oob; 19961592Srgrimes if (write(fileno(outstr), buf, cnt) != cnt) 19971592Srgrimes goto file_err; 19981592Srgrimes byte_count += cnt; 19991592Srgrimes } 200089935Syar if (recvurg) 200189935Syar goto got_oob; 20021592Srgrimes if (cnt < 0) 20031592Srgrimes goto data_err; 20041592Srgrimes transflag = 0; 20051592Srgrimes return (0); 20061592Srgrimes 20071592Srgrimes case TYPE_E: 20081592Srgrimes reply(553, "TYPE E not implemented."); 20091592Srgrimes transflag = 0; 20101592Srgrimes return (-1); 20111592Srgrimes 20121592Srgrimes case TYPE_A: 20131592Srgrimes while ((c = getc(instr)) != EOF) { 201489935Syar if (recvurg) 201589935Syar goto got_oob; 20161592Srgrimes byte_count++; 20171592Srgrimes if (c == '\n') 20181592Srgrimes bare_lfs++; 20191592Srgrimes while (c == '\r') { 20201592Srgrimes if (ferror(outstr)) 20211592Srgrimes goto data_err; 20221592Srgrimes if ((c = getc(instr)) != '\n') { 20231592Srgrimes (void) putc ('\r', outstr); 20241592Srgrimes if (c == '\0' || c == EOF) 20251592Srgrimes goto contin2; 20261592Srgrimes } 20271592Srgrimes } 20281592Srgrimes (void) putc(c, outstr); 20291592Srgrimes contin2: ; 20301592Srgrimes } 203189935Syar if (recvurg) 203289935Syar goto got_oob; 20331592Srgrimes fflush(outstr); 20341592Srgrimes if (ferror(instr)) 20351592Srgrimes goto data_err; 20361592Srgrimes if (ferror(outstr)) 20371592Srgrimes goto file_err; 20381592Srgrimes transflag = 0; 20391592Srgrimes if (bare_lfs) { 20401592Srgrimes lreply(226, 20411592Srgrimes "WARNING! %d bare linefeeds received in ASCII mode", 20421592Srgrimes bare_lfs); 20431592Srgrimes (void)printf(" File may not have transferred correctly.\r\n"); 20441592Srgrimes } 20451592Srgrimes return (0); 20461592Srgrimes default: 20471592Srgrimes reply(550, "Unimplemented TYPE %d in receive_data", type); 20481592Srgrimes transflag = 0; 20491592Srgrimes return (-1); 20501592Srgrimes } 20511592Srgrimes 20521592Srgrimesdata_err: 20531592Srgrimes transflag = 0; 20541592Srgrimes perror_reply(426, "Data Connection"); 20551592Srgrimes return (-1); 20561592Srgrimes 20571592Srgrimesfile_err: 20581592Srgrimes transflag = 0; 20591592Srgrimes perror_reply(452, "Error writing file"); 20601592Srgrimes return (-1); 206189935Syar 206289935Syargot_oob: 206389935Syar myoob(); 206489935Syar recvurg = 0; 206589935Syar transflag = 0; 206689935Syar return (-1); 20671592Srgrimes} 20681592Srgrimes 20691592Srgrimesvoid 207090148Simpstatfilecmd(char *filename) 20711592Srgrimes{ 20721592Srgrimes FILE *fin; 20731592Srgrimes int c; 20741592Srgrimes char line[LINE_MAX]; 20751592Srgrimes 207625165Sdavidn (void)snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename); 20771592Srgrimes fin = ftpd_popen(line, "r"); 20781592Srgrimes lreply(211, "status of %s:", filename); 20791592Srgrimes while ((c = getc(fin)) != EOF) { 20801592Srgrimes if (c == '\n') { 20811592Srgrimes if (ferror(stdout)){ 20821592Srgrimes perror_reply(421, "control connection"); 20831592Srgrimes (void) ftpd_pclose(fin); 20841592Srgrimes dologout(1); 20851592Srgrimes /* NOTREACHED */ 20861592Srgrimes } 20871592Srgrimes if (ferror(fin)) { 20881592Srgrimes perror_reply(551, filename); 20891592Srgrimes (void) ftpd_pclose(fin); 20901592Srgrimes return; 20911592Srgrimes } 20921592Srgrimes (void) putc('\r', stdout); 20931592Srgrimes } 20941592Srgrimes (void) putc(c, stdout); 20951592Srgrimes } 20961592Srgrimes (void) ftpd_pclose(fin); 20971592Srgrimes reply(211, "End of Status"); 20981592Srgrimes} 20991592Srgrimes 21001592Srgrimesvoid 210190148Simpstatcmd(void) 21021592Srgrimes{ 210356668Sshin union sockunion *su; 21041592Srgrimes u_char *a, *p; 210599255Sume char hname[NI_MAXHOST]; 210656668Sshin int ispassive; 21071592Srgrimes 21081592Srgrimes lreply(211, "%s FTP server status:", hostname, version); 21091592Srgrimes printf(" %s\r\n", version); 21101592Srgrimes printf(" Connected to %s", remotehost); 211156668Sshin if (!getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len, 211299255Sume hname, sizeof(hname) - 1, NULL, 0, NI_NUMERICHOST)) { 211356668Sshin if (strcmp(hname, remotehost) != 0) 211456668Sshin printf(" (%s)", hname); 211556668Sshin } 21161592Srgrimes printf("\r\n"); 21171592Srgrimes if (logged_in) { 21181592Srgrimes if (guest) 21191592Srgrimes printf(" Logged in anonymously\r\n"); 21201592Srgrimes else 21211592Srgrimes printf(" Logged in as %s\r\n", pw->pw_name); 21221592Srgrimes } else if (askpasswd) 21231592Srgrimes printf(" Waiting for password\r\n"); 21241592Srgrimes else 21251592Srgrimes printf(" Waiting for user name\r\n"); 21261592Srgrimes printf(" TYPE: %s", typenames[type]); 21271592Srgrimes if (type == TYPE_A || type == TYPE_E) 21281592Srgrimes printf(", FORM: %s", formnames[form]); 21291592Srgrimes if (type == TYPE_L) 21301592Srgrimes#if NBBY == 8 21311592Srgrimes printf(" %d", NBBY); 21321592Srgrimes#else 21331592Srgrimes printf(" %d", bytesize); /* need definition! */ 21341592Srgrimes#endif 21351592Srgrimes printf("; STRUcture: %s; transfer MODE: %s\r\n", 21361592Srgrimes strunames[stru], modenames[mode]); 21371592Srgrimes if (data != -1) 21381592Srgrimes printf(" Data connection open\r\n"); 21391592Srgrimes else if (pdata != -1) { 214056668Sshin ispassive = 1; 214156668Sshin su = &pasv_addr; 21421592Srgrimes goto printaddr; 21431592Srgrimes } else if (usedefault == 0) { 214456668Sshin ispassive = 0; 214556668Sshin su = &data_dest; 21461592Srgrimesprintaddr: 21471592Srgrimes#define UC(b) (((int) b) & 0xff) 214856668Sshin if (epsvall) { 214956668Sshin printf(" EPSV only mode (EPSV ALL)\r\n"); 215056668Sshin goto epsvonly; 215156668Sshin } 215256668Sshin 215356668Sshin /* PORT/PASV */ 215456668Sshin if (su->su_family == AF_INET) { 215556668Sshin a = (u_char *) &su->su_sin.sin_addr; 215656668Sshin p = (u_char *) &su->su_sin.sin_port; 215756668Sshin printf(" %s (%d,%d,%d,%d,%d,%d)\r\n", 215856668Sshin ispassive ? "PASV" : "PORT", 215956668Sshin UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), 216056668Sshin UC(p[0]), UC(p[1])); 216156668Sshin } 216256668Sshin 216356668Sshin /* LPRT/LPSV */ 216456668Sshin { 216556668Sshin int alen, af, i; 216656668Sshin 216756668Sshin switch (su->su_family) { 216856668Sshin case AF_INET: 216956668Sshin a = (u_char *) &su->su_sin.sin_addr; 217056668Sshin p = (u_char *) &su->su_sin.sin_port; 217156668Sshin alen = sizeof(su->su_sin.sin_addr); 217256668Sshin af = 4; 217356668Sshin break; 217456668Sshin case AF_INET6: 217556668Sshin a = (u_char *) &su->su_sin6.sin6_addr; 217656668Sshin p = (u_char *) &su->su_sin6.sin6_port; 217756668Sshin alen = sizeof(su->su_sin6.sin6_addr); 217856668Sshin af = 6; 217956668Sshin break; 218056668Sshin default: 218156668Sshin af = 0; 218256668Sshin break; 218356668Sshin } 218456668Sshin if (af) { 218556668Sshin printf(" %s (%d,%d,", ispassive ? "LPSV" : "LPRT", 218656668Sshin af, alen); 218756668Sshin for (i = 0; i < alen; i++) 218856668Sshin printf("%d,", UC(a[i])); 218956668Sshin printf("%d,%d,%d)\r\n", 2, UC(p[0]), UC(p[1])); 219056668Sshin } 219156668Sshin } 219256668Sshin 219356668Sshinepsvonly:; 219456668Sshin /* EPRT/EPSV */ 219556668Sshin { 219656668Sshin int af; 219756668Sshin 219856668Sshin switch (su->su_family) { 219956668Sshin case AF_INET: 220056668Sshin af = 1; 220156668Sshin break; 220256668Sshin case AF_INET6: 220356668Sshin af = 2; 220456668Sshin break; 220556668Sshin default: 220656668Sshin af = 0; 220756668Sshin break; 220856668Sshin } 220956668Sshin if (af) { 221099255Sume union sockunion tmp; 221199255Sume 221299255Sume tmp = *su; 221399255Sume if (tmp.su_family == AF_INET6) 221499255Sume tmp.su_sin6.sin6_scope_id = 0; 221599255Sume if (!getnameinfo((struct sockaddr *)&tmp, tmp.su_len, 221656668Sshin hname, sizeof(hname) - 1, NULL, 0, 221756668Sshin NI_NUMERICHOST)) { 221856668Sshin printf(" %s |%d|%s|%d|\r\n", 221956668Sshin ispassive ? "EPSV" : "EPRT", 222099255Sume af, hname, htons(tmp.su_port)); 222156668Sshin } 222256668Sshin } 222356668Sshin } 22241592Srgrimes#undef UC 22251592Srgrimes } else 22261592Srgrimes printf(" No data connection\r\n"); 22271592Srgrimes reply(211, "End of status"); 22281592Srgrimes} 22291592Srgrimes 22301592Srgrimesvoid 223190148Simpfatalerror(char *s) 22321592Srgrimes{ 22331592Srgrimes 22341592Srgrimes reply(451, "Error in server: %s\n", s); 22351592Srgrimes reply(221, "Closing connection due to server error."); 22361592Srgrimes dologout(0); 22371592Srgrimes /* NOTREACHED */ 22381592Srgrimes} 22391592Srgrimes 22401592Srgrimesvoid 22411592Srgrimesreply(int n, const char *fmt, ...) 22421592Srgrimes{ 22431592Srgrimes va_list ap; 224490148Simp 22451592Srgrimes va_start(ap, fmt); 22461592Srgrimes (void)printf("%d ", n); 22471592Srgrimes (void)vprintf(fmt, ap); 22481592Srgrimes (void)printf("\r\n"); 22491592Srgrimes (void)fflush(stdout); 225076096Smarkm if (ftpdebug) { 22511592Srgrimes syslog(LOG_DEBUG, "<--- %d ", n); 22521592Srgrimes vsyslog(LOG_DEBUG, fmt, ap); 22531592Srgrimes } 22541592Srgrimes} 22551592Srgrimes 22561592Srgrimesvoid 22571592Srgrimeslreply(int n, const char *fmt, ...) 22581592Srgrimes{ 22591592Srgrimes va_list ap; 226090148Simp 22611592Srgrimes va_start(ap, fmt); 22621592Srgrimes (void)printf("%d- ", n); 22631592Srgrimes (void)vprintf(fmt, ap); 22641592Srgrimes (void)printf("\r\n"); 22651592Srgrimes (void)fflush(stdout); 226676096Smarkm if (ftpdebug) { 22671592Srgrimes syslog(LOG_DEBUG, "<--- %d- ", n); 22681592Srgrimes vsyslog(LOG_DEBUG, fmt, ap); 22691592Srgrimes } 22701592Srgrimes} 22711592Srgrimes 22721592Srgrimesstatic void 227390148Simpack(char *s) 22741592Srgrimes{ 22751592Srgrimes 22761592Srgrimes reply(250, "%s command successful.", s); 22771592Srgrimes} 22781592Srgrimes 22791592Srgrimesvoid 228090148Simpnack(char *s) 22811592Srgrimes{ 22821592Srgrimes 22831592Srgrimes reply(502, "%s command not implemented.", s); 22841592Srgrimes} 22851592Srgrimes 22861592Srgrimes/* ARGSUSED */ 22871592Srgrimesvoid 228890148Simpyyerror(char *s) 22891592Srgrimes{ 22901592Srgrimes char *cp; 22911592Srgrimes 229217478Smarkm if ((cp = strchr(cbuf,'\n'))) 22931592Srgrimes *cp = '\0'; 22941592Srgrimes reply(500, "'%s': command not understood.", cbuf); 22951592Srgrimes} 22961592Srgrimes 22971592Srgrimesvoid 229890148Simpdelete(char *name) 22991592Srgrimes{ 23001592Srgrimes struct stat st; 23011592Srgrimes 23021592Srgrimes LOGCMD("delete", name); 2303100439Syar if (lstat(name, &st) < 0) { 23041592Srgrimes perror_reply(550, name); 23051592Srgrimes return; 23061592Srgrimes } 23071592Srgrimes if ((st.st_mode&S_IFMT) == S_IFDIR) { 23081592Srgrimes if (rmdir(name) < 0) { 23091592Srgrimes perror_reply(550, name); 23101592Srgrimes return; 23111592Srgrimes } 23121592Srgrimes goto done; 23131592Srgrimes } 23141592Srgrimes if (unlink(name) < 0) { 23151592Srgrimes perror_reply(550, name); 23161592Srgrimes return; 23171592Srgrimes } 23181592Srgrimesdone: 23191592Srgrimes ack("DELE"); 23201592Srgrimes} 23211592Srgrimes 23221592Srgrimesvoid 232390148Simpcwd(char *path) 23241592Srgrimes{ 23251592Srgrimes 23261592Srgrimes if (chdir(path) < 0) 23271592Srgrimes perror_reply(550, path); 23281592Srgrimes else 23291592Srgrimes ack("CWD"); 23301592Srgrimes} 23311592Srgrimes 23321592Srgrimesvoid 233390148Simpmakedir(char *name) 23341592Srgrimes{ 2335100878Syar char *s; 23361592Srgrimes 23371592Srgrimes LOGCMD("mkdir", name); 233899195Smdodd if (guest && noguestmkd) 233999195Smdodd reply(550, "%s: permission denied", name); 234099195Smdodd else if (mkdir(name, 0777) < 0) 23411592Srgrimes perror_reply(550, name); 2342100878Syar else { 2343100878Syar if ((s = doublequote(name)) == NULL) 2344100878Syar fatalerror("Ran out of memory."); 2345100878Syar reply(257, "\"%s\" directory created.", s); 2346100878Syar free(s); 2347100878Syar } 23481592Srgrimes} 23491592Srgrimes 23501592Srgrimesvoid 235190148Simpremovedir(char *name) 23521592Srgrimes{ 23531592Srgrimes 23541592Srgrimes LOGCMD("rmdir", name); 23551592Srgrimes if (rmdir(name) < 0) 23561592Srgrimes perror_reply(550, name); 23571592Srgrimes else 23581592Srgrimes ack("RMD"); 23591592Srgrimes} 23601592Srgrimes 23611592Srgrimesvoid 236290148Simppwd(void) 23631592Srgrimes{ 2364100486Syar char *s, path[MAXPATHLEN + 1]; 23651592Srgrimes 23661592Srgrimes if (getwd(path) == (char *)NULL) 23671592Srgrimes reply(550, "%s.", path); 2368100486Syar else { 2369100486Syar if ((s = doublequote(path)) == NULL) 2370100486Syar fatalerror("Ran out of memory."); 2371100486Syar reply(257, "\"%s\" is current directory.", s); 2372100486Syar free(s); 2373100486Syar } 23741592Srgrimes} 23751592Srgrimes 23761592Srgrimeschar * 237790148Simprenamefrom(char *name) 23781592Srgrimes{ 23791592Srgrimes struct stat st; 23801592Srgrimes 2381100439Syar if (lstat(name, &st) < 0) { 23821592Srgrimes perror_reply(550, name); 23831592Srgrimes return ((char *)0); 23841592Srgrimes } 23851592Srgrimes reply(350, "File exists, ready for destination name"); 23861592Srgrimes return (name); 23871592Srgrimes} 23881592Srgrimes 23891592Srgrimesvoid 239090148Simprenamecmd(char *from, char *to) 23911592Srgrimes{ 239217433Spst struct stat st; 23931592Srgrimes 23941592Srgrimes LOGCMD2("rename", from, to); 239517433Spst 239617433Spst if (guest && (stat(to, &st) == 0)) { 239717433Spst reply(550, "%s: permission denied", to); 239817433Spst return; 239917433Spst } 240017433Spst 24011592Srgrimes if (rename(from, to) < 0) 24021592Srgrimes perror_reply(550, "rename"); 24031592Srgrimes else 24041592Srgrimes ack("RNTO"); 24051592Srgrimes} 24061592Srgrimes 24071592Srgrimesstatic void 240890148Simpdolog(struct sockaddr *who) 24091592Srgrimes{ 241056668Sshin int error; 24111592Srgrimes 241256668Sshin realhostname_sa(remotehost, sizeof(remotehost) - 1, who, who->sa_len); 241356668Sshin 24141592Srgrimes#ifdef SETPROCTITLE 241525283Sdavidn#ifdef VIRTUAL_HOSTING 241625283Sdavidn if (thishost != firsthost) 241725283Sdavidn snprintf(proctitle, sizeof(proctitle), "%s: connected (to %s)", 241825283Sdavidn remotehost, hostname); 241925283Sdavidn else 242025283Sdavidn#endif 242125283Sdavidn snprintf(proctitle, sizeof(proctitle), "%s: connected", 242225283Sdavidn remotehost); 242313139Speter setproctitle("%s", proctitle); 24241592Srgrimes#endif /* SETPROCTITLE */ 24251592Srgrimes 242625283Sdavidn if (logging) { 242725283Sdavidn#ifdef VIRTUAL_HOSTING 242825283Sdavidn if (thishost != firsthost) 242925283Sdavidn syslog(LOG_INFO, "connection from %s (to %s)", 243025283Sdavidn remotehost, hostname); 243125283Sdavidn else 243225283Sdavidn#endif 243356668Sshin { 243456668Sshin char who_name[MAXHOSTNAMELEN]; 243556668Sshin 243656668Sshin error = getnameinfo(who, who->sa_len, 243756668Sshin who_name, sizeof(who_name) - 1, 243899255Sume NULL, 0, NI_NUMERICHOST); 243933782Seivind syslog(LOG_INFO, "connection from %s (%s)", remotehost, 244056668Sshin error == 0 ? who_name : ""); 244156668Sshin } 244225283Sdavidn } 24431592Srgrimes} 24441592Srgrimes 24451592Srgrimes/* 24461592Srgrimes * Record logout in wtmp file 24471592Srgrimes * and exit with supplied status. 24481592Srgrimes */ 24491592Srgrimesvoid 245090148Simpdologout(int status) 24511592Srgrimes{ 245222057Sdg /* 245322057Sdg * Prevent reception of SIGURG from resulting in a resumption 245422057Sdg * back to the main program loop. 245522058Sdg */ 245622057Sdg transflag = 0; 24571592Srgrimes 24581592Srgrimes if (logged_in) { 24591592Srgrimes (void) seteuid((uid_t)0); 246089920Sume ftpd_logwtmp(ttyline, "", NULL); 24611592Srgrimes } 24621592Srgrimes /* beware of flushing buffers after a SIGPIPE */ 24631592Srgrimes _exit(status); 24641592Srgrimes} 24651592Srgrimes 24661592Srgrimesstatic void 246790148Simpsigurg(int signo) 24681592Srgrimes{ 246989935Syar 247089935Syar recvurg = 1; 247189935Syar} 247289935Syar 247389935Syarstatic void 247490148Simpmyoob(void) 247589935Syar{ 24761592Srgrimes char *cp; 24771592Srgrimes 24781592Srgrimes /* only process if transfer occurring */ 24791592Srgrimes if (!transflag) 24801592Srgrimes return; 24811592Srgrimes cp = tmpline; 24821592Srgrimes if (getline(cp, 7, stdin) == NULL) { 24831592Srgrimes reply(221, "You could at least say goodbye."); 24841592Srgrimes dologout(0); 24851592Srgrimes } 24861592Srgrimes upper(cp); 24871592Srgrimes if (strcmp(cp, "ABOR\r\n") == 0) { 24881592Srgrimes tmpline[0] = '\0'; 24891592Srgrimes reply(426, "Transfer aborted. Data connection closed."); 24901592Srgrimes reply(226, "Abort successful"); 24911592Srgrimes } 24921592Srgrimes if (strcmp(cp, "STAT\r\n") == 0) { 249351192Smharo tmpline[0] = '\0'; 24941592Srgrimes if (file_size != (off_t) -1) 24951592Srgrimes reply(213, "Status: %qd of %qd bytes transferred", 24961592Srgrimes byte_count, file_size); 24971592Srgrimes else 24981592Srgrimes reply(213, "Status: %qd bytes transferred", byte_count); 24991592Srgrimes } 25001592Srgrimes} 25011592Srgrimes 25021592Srgrimes/* 25031592Srgrimes * Note: a response of 425 is not mentioned as a possible response to 25041592Srgrimes * the PASV command in RFC959. However, it has been blessed as 25051592Srgrimes * a legitimate response by Jon Postel in a telephone conversation 25061592Srgrimes * with Rick Adams on 25 Jan 89. 25071592Srgrimes */ 25081592Srgrimesvoid 250990148Simppassive(void) 25101592Srgrimes{ 2511100615Syar int len, on; 25121592Srgrimes char *p, *a; 25131592Srgrimes 251417433Spst if (pdata >= 0) /* close old port if one set */ 251517433Spst close(pdata); 251617433Spst 251756668Sshin pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0); 25181592Srgrimes if (pdata < 0) { 25191592Srgrimes perror_reply(425, "Can't open passive connection"); 25201592Srgrimes return; 25211592Srgrimes } 2522100615Syar on = 1; 2523100615Syar if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) 2524100615Syar syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m"); 25259933Spst 252617433Spst (void) seteuid((uid_t)0); 252717433Spst 252819903Spst#ifdef IP_PORTRANGE 252956668Sshin if (ctrl_addr.su_family == AF_INET) { 2530100615Syar on = restricted_data_ports ? IP_PORTRANGE_HIGH 2531100615Syar : IP_PORTRANGE_DEFAULT; 253219903Spst 253319903Spst if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE, 2534100612Syar &on, sizeof(on)) < 0) 253519903Spst goto pasv_error; 25361592Srgrimes } 253719903Spst#endif 253860929Snsayer#ifdef IPV6_PORTRANGE 253960929Snsayer if (ctrl_addr.su_family == AF_INET6) { 2540100615Syar on = restricted_data_ports ? IPV6_PORTRANGE_HIGH 2541100615Syar : IPV6_PORTRANGE_DEFAULT; 25429933Spst 254360929Snsayer if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE, 2544100612Syar &on, sizeof(on)) < 0) 254560929Snsayer goto pasv_error; 254660929Snsayer } 254760929Snsayer#endif 254860929Snsayer 254916033Speter pasv_addr = ctrl_addr; 255056668Sshin pasv_addr.su_port = 0; 255156668Sshin if (bind(pdata, (struct sockaddr *)&pasv_addr, pasv_addr.su_len) < 0) 255216033Speter goto pasv_error; 255317433Spst 255416033Speter (void) seteuid((uid_t)pw->pw_uid); 255516033Speter 25561592Srgrimes len = sizeof(pasv_addr); 25571592Srgrimes if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0) 25581592Srgrimes goto pasv_error; 25591592Srgrimes if (listen(pdata, 1) < 0) 25601592Srgrimes goto pasv_error; 256156668Sshin if (pasv_addr.su_family == AF_INET) 256256668Sshin a = (char *) &pasv_addr.su_sin.sin_addr; 256356668Sshin else if (pasv_addr.su_family == AF_INET6 && 256456668Sshin IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) 256556668Sshin a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12]; 256656668Sshin else 256756668Sshin goto pasv_error; 256856668Sshin 256956668Sshin p = (char *) &pasv_addr.su_port; 25701592Srgrimes 25711592Srgrimes#define UC(b) (((int) b) & 0xff) 25721592Srgrimes 25731592Srgrimes reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]), 25741592Srgrimes UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1])); 25751592Srgrimes return; 25761592Srgrimes 25771592Srgrimespasv_error: 257817433Spst (void) seteuid((uid_t)pw->pw_uid); 25791592Srgrimes (void) close(pdata); 25801592Srgrimes pdata = -1; 25811592Srgrimes perror_reply(425, "Can't open passive connection"); 25821592Srgrimes return; 25831592Srgrimes} 25841592Srgrimes 25851592Srgrimes/* 258656668Sshin * Long Passive defined in RFC 1639. 258756668Sshin * 228 Entering Long Passive Mode 258856668Sshin * (af, hal, h1, h2, h3,..., pal, p1, p2...) 258956668Sshin */ 259056668Sshin 259156668Sshinvoid 259290148Simplong_passive(char *cmd, int pf) 259356668Sshin{ 2594100615Syar int len, on; 259556668Sshin char *p, *a; 259656668Sshin 259756668Sshin if (pdata >= 0) /* close old port if one set */ 259856668Sshin close(pdata); 259956668Sshin 260056668Sshin if (pf != PF_UNSPEC) { 260156668Sshin if (ctrl_addr.su_family != pf) { 260256668Sshin switch (ctrl_addr.su_family) { 260356668Sshin case AF_INET: 260456668Sshin pf = 1; 260556668Sshin break; 260656668Sshin case AF_INET6: 260756668Sshin pf = 2; 260856668Sshin break; 260956668Sshin default: 261056668Sshin pf = 0; 261156668Sshin break; 261256668Sshin } 261356668Sshin /* 261456668Sshin * XXX 261556668Sshin * only EPRT/EPSV ready clients will understand this 261656668Sshin */ 261756668Sshin if (strcmp(cmd, "EPSV") == 0 && pf) { 261856668Sshin reply(522, "Network protocol mismatch, " 261956668Sshin "use (%d)", pf); 262056668Sshin } else 262156668Sshin reply(501, "Network protocol mismatch"); /*XXX*/ 262256668Sshin 262356668Sshin return; 262456668Sshin } 262556668Sshin } 262656668Sshin 262756668Sshin pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0); 262856668Sshin if (pdata < 0) { 262956668Sshin perror_reply(425, "Can't open passive connection"); 263056668Sshin return; 263156668Sshin } 2632100615Syar on = 1; 2633100615Syar if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) 2634100615Syar syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m"); 263556668Sshin 263656668Sshin (void) seteuid((uid_t)0); 263756668Sshin 263856668Sshin pasv_addr = ctrl_addr; 263956668Sshin pasv_addr.su_port = 0; 264056668Sshin len = pasv_addr.su_len; 264156668Sshin 264260929Snsayer#ifdef IP_PORTRANGE 264360929Snsayer if (ctrl_addr.su_family == AF_INET) { 2644100615Syar on = restricted_data_ports ? IP_PORTRANGE_HIGH 2645100615Syar : IP_PORTRANGE_DEFAULT; 264660929Snsayer 264760929Snsayer if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE, 2648100612Syar &on, sizeof(on)) < 0) 264960929Snsayer goto pasv_error; 265060929Snsayer } 265160929Snsayer#endif 265260929Snsayer#ifdef IPV6_PORTRANGE 265360929Snsayer if (ctrl_addr.su_family == AF_INET6) { 2654100615Syar on = restricted_data_ports ? IPV6_PORTRANGE_HIGH 2655100615Syar : IPV6_PORTRANGE_DEFAULT; 265660929Snsayer 265760929Snsayer if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE, 2658100612Syar &on, sizeof(on)) < 0) 265960929Snsayer goto pasv_error; 266060929Snsayer } 266160929Snsayer#endif 266260929Snsayer 266356668Sshin if (bind(pdata, (struct sockaddr *)&pasv_addr, len) < 0) 266456668Sshin goto pasv_error; 266556668Sshin 266656668Sshin (void) seteuid((uid_t)pw->pw_uid); 266756668Sshin 266856668Sshin if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0) 266956668Sshin goto pasv_error; 267056668Sshin if (listen(pdata, 1) < 0) 267156668Sshin goto pasv_error; 267256668Sshin 267356668Sshin#define UC(b) (((int) b) & 0xff) 267456668Sshin 267556668Sshin if (strcmp(cmd, "LPSV") == 0) { 267656668Sshin p = (char *)&pasv_addr.su_port; 267756668Sshin switch (pasv_addr.su_family) { 267856668Sshin case AF_INET: 267956668Sshin a = (char *) &pasv_addr.su_sin.sin_addr; 268056668Sshin v4_reply: 268156668Sshin reply(228, 268256668Sshin"Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)", 268356668Sshin 4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), 268456668Sshin 2, UC(p[0]), UC(p[1])); 268556668Sshin return; 268656668Sshin case AF_INET6: 268756668Sshin if (IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) { 268856668Sshin a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12]; 268956668Sshin goto v4_reply; 269056668Sshin } 269156668Sshin a = (char *) &pasv_addr.su_sin6.sin6_addr; 269256668Sshin reply(228, 269356668Sshin"Entering Long Passive Mode " 269456668Sshin"(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)", 269556668Sshin 6, 16, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), 269656668Sshin UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]), 269756668Sshin UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]), 269856668Sshin UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]), 269956668Sshin 2, UC(p[0]), UC(p[1])); 270056668Sshin return; 270156668Sshin } 270256668Sshin } else if (strcmp(cmd, "EPSV") == 0) { 270356668Sshin switch (pasv_addr.su_family) { 270456668Sshin case AF_INET: 270556668Sshin case AF_INET6: 270656668Sshin reply(229, "Entering Extended Passive Mode (|||%d|)", 270756668Sshin ntohs(pasv_addr.su_port)); 270856668Sshin return; 270956668Sshin } 271056668Sshin } else { 271156668Sshin /* more proper error code? */ 271256668Sshin } 271356668Sshin 271456668Sshinpasv_error: 271556668Sshin (void) seteuid((uid_t)pw->pw_uid); 271656668Sshin (void) close(pdata); 271756668Sshin pdata = -1; 271856668Sshin perror_reply(425, "Can't open passive connection"); 271956668Sshin return; 272056668Sshin} 272156668Sshin 272256668Sshin/* 2723101537Syar * Generate unique name for file with basename "local" 2724101537Syar * and open the file in order to avoid possible races. 2725101537Syar * Try "local" first, then "local.1", "local.2" etc, up to "local.99". 2726101537Syar * Return descriptor to the file, set "name" to its name. 2727101537Syar * 27281592Srgrimes * Generates failure reply on error. 27291592Srgrimes */ 2730101537Syarstatic int 2731101537Syarguniquefd(char *local, char **name) 27321592Srgrimes{ 27331592Srgrimes static char new[MAXPATHLEN]; 27341592Srgrimes struct stat st; 2735101537Syar char *cp; 27361592Srgrimes int count; 2737101537Syar int fd; 27381592Srgrimes 27391592Srgrimes cp = strrchr(local, '/'); 27401592Srgrimes if (cp) 27411592Srgrimes *cp = '\0'; 27421592Srgrimes if (stat(cp ? local : ".", &st) < 0) { 27431592Srgrimes perror_reply(553, cp ? local : "."); 2744101537Syar return (-1); 27451592Srgrimes } 2746101537Syar if (cp) { 2747101537Syar /* 2748101537Syar * Let not overwrite dirname with counter suffix. 2749101537Syar * -4 is for /nn\0 2750101537Syar * In this extreme case dot won't be put in front of suffix. 2751101537Syar */ 2752101537Syar if (strlen(local) > sizeof(new) - 4) { 2753101537Syar reply(553, "Pathname too long"); 2754101537Syar return (-1); 2755101537Syar } 27561592Srgrimes *cp = '/'; 2757101537Syar } 275831973Simp /* -4 is for the .nn<null> we put on the end below */ 275931973Simp (void) snprintf(new, sizeof(new) - 4, "%s", local); 27601592Srgrimes cp = new + strlen(new); 2761101537Syar /* 2762101537Syar * Don't generate dotfile unless requested explicitly. 2763101537Syar * This covers the case when basename gets truncated off 2764101537Syar * by buffer size. 2765101537Syar */ 2766101537Syar if (cp > new && cp[-1] != '/') 2767101537Syar *cp++ = '.'; 2768101537Syar for (count = 0; count < 100; count++) { 2769101537Syar /* At count 0 try unmodified name */ 2770101537Syar if (count) 2771101537Syar (void)sprintf(cp, "%d", count); 2772101537Syar if ((fd = open(count ? new : local, 2773101537Syar O_RDWR | O_CREAT | O_EXCL, 0666)) >= 0) { 2774101537Syar *name = count ? new : local; 2775101537Syar return (fd); 2776101537Syar } 27771592Srgrimes } 27781592Srgrimes reply(452, "Unique file name cannot be created."); 2779101537Syar return (-1); 27801592Srgrimes} 27811592Srgrimes 27821592Srgrimes/* 27831592Srgrimes * Format and send reply containing system error number. 27841592Srgrimes */ 27851592Srgrimesvoid 278690148Simpperror_reply(int code, char *string) 27871592Srgrimes{ 27881592Srgrimes 27891592Srgrimes reply(code, "%s: %s.", string, strerror(errno)); 27901592Srgrimes} 27911592Srgrimes 27921592Srgrimesstatic char *onefile[] = { 27931592Srgrimes "", 27941592Srgrimes 0 27951592Srgrimes}; 27961592Srgrimes 27971592Srgrimesvoid 279890148Simpsend_file_list(char *whichf) 27991592Srgrimes{ 28001592Srgrimes struct stat st; 28011592Srgrimes DIR *dirp = NULL; 28021592Srgrimes struct dirent *dir; 28031592Srgrimes FILE *dout = NULL; 28041592Srgrimes char **dirlist, *dirname; 28051592Srgrimes int simple = 0; 28061592Srgrimes int freeglob = 0; 28071592Srgrimes glob_t gl; 28081592Srgrimes 28091592Srgrimes if (strpbrk(whichf, "~{[*?") != NULL) { 2810100222Smikeh int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE; 28111592Srgrimes 28121592Srgrimes memset(&gl, 0, sizeof(gl)); 281374470Sjlemon gl.gl_matchc = MAXGLOBARGS; 281480525Smikeh flags |= GLOB_LIMIT; 28151592Srgrimes freeglob = 1; 28161592Srgrimes if (glob(whichf, flags, 0, &gl)) { 28171592Srgrimes reply(550, "not found"); 28181592Srgrimes goto out; 28191592Srgrimes } else if (gl.gl_pathc == 0) { 28201592Srgrimes errno = ENOENT; 28211592Srgrimes perror_reply(550, whichf); 28221592Srgrimes goto out; 28231592Srgrimes } 28241592Srgrimes dirlist = gl.gl_pathv; 28251592Srgrimes } else { 28261592Srgrimes onefile[0] = whichf; 28271592Srgrimes dirlist = onefile; 28281592Srgrimes simple = 1; 28291592Srgrimes } 28301592Srgrimes 283117478Smarkm while ((dirname = *dirlist++)) { 28321592Srgrimes if (stat(dirname, &st) < 0) { 28331592Srgrimes /* 28341592Srgrimes * If user typed "ls -l", etc, and the client 28351592Srgrimes * used NLST, do what the user meant. 28361592Srgrimes */ 28371592Srgrimes if (dirname[0] == '-' && *dirlist == NULL && 28381592Srgrimes transflag == 0) { 283925165Sdavidn retrieve(_PATH_LS " %s", dirname); 28401592Srgrimes goto out; 28411592Srgrimes } 28421592Srgrimes perror_reply(550, whichf); 28431592Srgrimes if (dout != NULL) { 28441592Srgrimes (void) fclose(dout); 28451592Srgrimes transflag = 0; 28461592Srgrimes data = -1; 28471592Srgrimes pdata = -1; 28481592Srgrimes } 28491592Srgrimes goto out; 28501592Srgrimes } 28511592Srgrimes 28521592Srgrimes if (S_ISREG(st.st_mode)) { 28531592Srgrimes if (dout == NULL) { 28541592Srgrimes dout = dataconn("file list", (off_t)-1, "w"); 28551592Srgrimes if (dout == NULL) 28561592Srgrimes goto out; 28571592Srgrimes transflag++; 28581592Srgrimes } 28591592Srgrimes fprintf(dout, "%s%s\n", dirname, 28601592Srgrimes type == TYPE_A ? "\r" : ""); 28611592Srgrimes byte_count += strlen(dirname) + 1; 28621592Srgrimes continue; 28631592Srgrimes } else if (!S_ISDIR(st.st_mode)) 28641592Srgrimes continue; 28651592Srgrimes 28661592Srgrimes if ((dirp = opendir(dirname)) == NULL) 28671592Srgrimes continue; 28681592Srgrimes 28691592Srgrimes while ((dir = readdir(dirp)) != NULL) { 28701592Srgrimes char nbuf[MAXPATHLEN]; 28711592Srgrimes 287289935Syar if (recvurg) { 287389935Syar myoob(); 287489935Syar recvurg = 0; 287589935Syar transflag = 0; 287689935Syar goto out; 287789935Syar } 287889935Syar 28791592Srgrimes if (dir->d_name[0] == '.' && dir->d_namlen == 1) 28801592Srgrimes continue; 28811592Srgrimes if (dir->d_name[0] == '.' && dir->d_name[1] == '.' && 28821592Srgrimes dir->d_namlen == 2) 28831592Srgrimes continue; 28841592Srgrimes 288599213Smaxim snprintf(nbuf, sizeof(nbuf), 288631973Simp "%s/%s", dirname, dir->d_name); 28871592Srgrimes 28881592Srgrimes /* 28891592Srgrimes * We have to do a stat to insure it's 28901592Srgrimes * not a directory or special file. 28911592Srgrimes */ 28921592Srgrimes if (simple || (stat(nbuf, &st) == 0 && 28931592Srgrimes S_ISREG(st.st_mode))) { 28941592Srgrimes if (dout == NULL) { 28951592Srgrimes dout = dataconn("file list", (off_t)-1, 28961592Srgrimes "w"); 28971592Srgrimes if (dout == NULL) 28981592Srgrimes goto out; 28991592Srgrimes transflag++; 29001592Srgrimes } 29011592Srgrimes if (nbuf[0] == '.' && nbuf[1] == '/') 29021592Srgrimes fprintf(dout, "%s%s\n", &nbuf[2], 29031592Srgrimes type == TYPE_A ? "\r" : ""); 29041592Srgrimes else 29051592Srgrimes fprintf(dout, "%s%s\n", nbuf, 29061592Srgrimes type == TYPE_A ? "\r" : ""); 29071592Srgrimes byte_count += strlen(nbuf) + 1; 29081592Srgrimes } 29091592Srgrimes } 29101592Srgrimes (void) closedir(dirp); 29111592Srgrimes } 29121592Srgrimes 29131592Srgrimes if (dout == NULL) 29141592Srgrimes reply(550, "No files found."); 29151592Srgrimes else if (ferror(dout) != 0) 29161592Srgrimes perror_reply(550, "Data connection"); 29171592Srgrimes else 29181592Srgrimes reply(226, "Transfer complete."); 29191592Srgrimes 29201592Srgrimes transflag = 0; 29211592Srgrimes if (dout != NULL) 29221592Srgrimes (void) fclose(dout); 29231592Srgrimes data = -1; 29241592Srgrimes pdata = -1; 29251592Srgrimesout: 29261592Srgrimes if (freeglob) { 29271592Srgrimes freeglob = 0; 29281592Srgrimes globfree(&gl); 29291592Srgrimes } 29301592Srgrimes} 29311592Srgrimes 293215196Sdgvoid 293390148Simpreapchild(int signo) 293415196Sdg{ 293515196Sdg while (wait3(NULL, WNOHANG, NULL) > 0); 293615196Sdg} 293715196Sdg 293813139Speter#ifdef OLD_SETPROCTITLE 29391592Srgrimes/* 29401592Srgrimes * Clobber argv so ps will show what we're doing. (Stolen from sendmail.) 29411592Srgrimes * Warning, since this is usually started from inetd.conf, it often doesn't 29421592Srgrimes * have much of an environment or arglist to overwrite. 29431592Srgrimes */ 29441592Srgrimesvoid 29451592Srgrimessetproctitle(const char *fmt, ...) 29461592Srgrimes{ 29471592Srgrimes int i; 29481592Srgrimes va_list ap; 29491592Srgrimes char *p, *bp, ch; 29501592Srgrimes char buf[LINE_MAX]; 29511592Srgrimes 29521592Srgrimes va_start(ap, fmt); 29531592Srgrimes (void)vsnprintf(buf, sizeof(buf), fmt, ap); 29541592Srgrimes 29551592Srgrimes /* make ps print our process name */ 29561592Srgrimes p = Argv[0]; 29571592Srgrimes *p++ = '-'; 29581592Srgrimes 29591592Srgrimes i = strlen(buf); 29601592Srgrimes if (i > LastArgv - p - 2) { 29611592Srgrimes i = LastArgv - p - 2; 29621592Srgrimes buf[i] = '\0'; 29631592Srgrimes } 29641592Srgrimes bp = buf; 29651592Srgrimes while (ch = *bp++) 29661592Srgrimes if (ch != '\n' && ch != '\r') 29671592Srgrimes *p++ = ch; 29681592Srgrimes while (p < LastArgv) 29691592Srgrimes *p++ = ' '; 29701592Srgrimes} 297113139Speter#endif /* OLD_SETPROCTITLE */ 29726740Sguido 297317433Spststatic void 297490148Simplogxfer(char *name, off_t size, time_t start) 29756740Sguido{ 29766740Sguido char buf[1024]; 29776740Sguido char path[MAXPATHLEN + 1]; 297836612Sjb time_t now; 29796740Sguido 29806740Sguido if (statfd >= 0 && getwd(path) != NULL) { 29816740Sguido time(&now); 298282792Sache snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s/%s!%qd!%ld\n", 29836740Sguido ctime(&now)+4, ident, remotehost, 298482792Sache path, name, (long long)size, 298582792Sache (long)(now - start + (now == start))); 29866740Sguido write(statfd, buf, strlen(buf)); 29876740Sguido } 29886740Sguido} 2989100486Syar 2990100486Syarstatic char * 2991100486Syardoublequote(char *s) 2992100486Syar{ 2993100486Syar int n; 2994100486Syar char *p, *s2; 2995100486Syar 2996100486Syar for (p = s, n = 0; *p; p++) 2997100486Syar if (*p == '"') 2998100486Syar n++; 2999100486Syar 3000100486Syar if ((s2 = malloc(p - s + n + 1)) == NULL) 3001100486Syar return (NULL); 3002100486Syar 3003100486Syar for (p = s2; *s; s++, p++) { 3004100486Syar if ((*p = *s) == '"') 3005100486Syar *(++p) = '"'; 3006100486Syar } 3007100486Syar *p = '\0'; 3008100486Syar 3009100486Syar return (s2); 3010100486Syar} 3011