122499Sdist /* 226044Sminshall * Copyright (c) 1985 Regents of the University of California. 3*33738Sbostic * All rights reserved. 4*33738Sbostic * 5*33738Sbostic * Redistribution and use in source and binary forms are permitted 6*33738Sbostic * provided that this notice is preserved and that due credit is given 7*33738Sbostic * to the University of California at Berkeley. The name of the University 8*33738Sbostic * may not be used to endorse or promote products derived from this 9*33738Sbostic * software without specific prior written permission. This software 10*33738Sbostic * is provided ``as is'' without express or implied warranty. 1122499Sdist */ 1222499Sdist 1310275Ssam #ifndef lint 1422499Sdist char copyright[] = 1526044Sminshall "@(#) Copyright (c) 1985 Regents of the University of California.\n\ 1622499Sdist All rights reserved.\n"; 17*33738Sbostic #endif /* not lint */ 1810275Ssam 1922499Sdist #ifndef lint 20*33738Sbostic static char sccsid[] = "@(#)ftpd.c 5.11 (Berkeley) 03/14/88"; 21*33738Sbostic #endif /* not lint */ 2222499Sdist 2310275Ssam /* 2410275Ssam * FTP server. 2510275Ssam */ 2610303Ssam #include <sys/param.h> 2710275Ssam #include <sys/stat.h> 2810275Ssam #include <sys/ioctl.h> 2910275Ssam #include <sys/socket.h> 3013247Ssam #include <sys/file.h> 3113595Ssam #include <sys/wait.h> 3210275Ssam 3310275Ssam #include <netinet/in.h> 3410275Ssam 3513034Ssam #include <arpa/ftp.h> 3613211Sroot #include <arpa/inet.h> 3726044Sminshall #include <arpa/telnet.h> 3813034Ssam 3910275Ssam #include <stdio.h> 4010275Ssam #include <signal.h> 4110275Ssam #include <pwd.h> 4210275Ssam #include <setjmp.h> 4310275Ssam #include <netdb.h> 4410423Ssam #include <errno.h> 4526044Sminshall #include <strings.h> 4626493Sminshall #include <syslog.h> 4710275Ssam 4810695Ssam /* 4910695Ssam * File containing login names 5010695Ssam * NOT to be used on this machine. 5110695Ssam * Commonly used to disallow uucp. 5210695Ssam */ 5310695Ssam #define FTPUSERS "/etc/ftpusers" 5410695Ssam 5510275Ssam extern int errno; 5610275Ssam extern char *sys_errlist[]; 5710275Ssam extern char *crypt(); 5810275Ssam extern char version[]; 5910275Ssam extern char *home; /* pointer to home directory for glob */ 6026044Sminshall extern FILE *popen(), *fopen(), *freopen(); 6126493Sminshall extern int pclose(), fclose(); 6226044Sminshall extern char *getline(); 6326044Sminshall extern char cbuf[]; 6410275Ssam 6510275Ssam struct sockaddr_in ctrl_addr; 6610275Ssam struct sockaddr_in data_source; 6710275Ssam struct sockaddr_in data_dest; 6810275Ssam struct sockaddr_in his_addr; 6910275Ssam 7010275Ssam int data; 7126044Sminshall jmp_buf errcatch, urgcatch; 7210275Ssam int logged_in; 7310275Ssam struct passwd *pw; 7410275Ssam int debug; 7526493Sminshall int timeout = 900; /* timeout after 15 minutes of inactivity */ 7611757Ssam int logging; 7710275Ssam int guest; 7816033Sralph int wtmp; 7910275Ssam int type; 8010275Ssam int form; 8110275Ssam int stru; /* avoid C keyword */ 8210275Ssam int mode; 8310321Ssam int usedefault = 1; /* for data transfers */ 8426044Sminshall int pdata; /* for passive mode */ 8526044Sminshall int unique; 8626044Sminshall int transflag; 8726044Sminshall char tmpline[7]; 8810275Ssam char hostname[32]; 8913247Ssam char remotehost[32]; 9010275Ssam 9111653Ssam /* 9211653Ssam * Timeout intervals for retrying connections 9311653Ssam * to hosts that don't accept PORT cmds. This 9411653Ssam * is a kludge, but given the problems with TCP... 9511653Ssam */ 9611653Ssam #define SWAITMAX 90 /* wait at most 90 seconds */ 9711653Ssam #define SWAITINT 5 /* interval between retries */ 9811653Ssam 9911653Ssam int swaitmax = SWAITMAX; 10011653Ssam int swaitint = SWAITINT; 10111653Ssam 10210275Ssam int lostconn(); 10326044Sminshall int myoob(); 10410275Ssam FILE *getdatasock(), *dataconn(); 10510275Ssam 10610275Ssam main(argc, argv) 10710275Ssam int argc; 10810275Ssam char *argv[]; 10910275Ssam { 11027750Sminshall int addrlen, on = 1; 11126044Sminshall long pgid; 11210275Ssam char *cp; 11310275Ssam 11416339Skarels addrlen = sizeof (his_addr); 11516339Skarels if (getpeername(0, &his_addr, &addrlen) < 0) { 11626493Sminshall syslog(LOG_ERR, "getpeername (%s): %m",argv[0]); 11710275Ssam exit(1); 11810275Ssam } 11916339Skarels addrlen = sizeof (ctrl_addr); 12026493Sminshall if (getsockname(0, (char *) &ctrl_addr, &addrlen) < 0) { 12126493Sminshall syslog(LOG_ERR, "getsockname (%s): %m",argv[0]); 12216339Skarels exit(1); 12316339Skarels } 12416339Skarels data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1); 12510275Ssam debug = 0; 12626493Sminshall openlog("ftpd", LOG_PID, LOG_DAEMON); 12710275Ssam argc--, argv++; 12810275Ssam while (argc > 0 && *argv[0] == '-') { 12910275Ssam for (cp = &argv[0][1]; *cp; cp++) switch (*cp) { 13010275Ssam 13111653Ssam case 'v': 13211653Ssam debug = 1; 13311653Ssam break; 13411653Ssam 13510275Ssam case 'd': 13610275Ssam debug = 1; 13710275Ssam break; 13810275Ssam 13911757Ssam case 'l': 14011757Ssam logging = 1; 14111757Ssam break; 14211757Ssam 14311653Ssam case 't': 14411653Ssam timeout = atoi(++cp); 14511653Ssam goto nextopt; 14611653Ssam break; 14711653Ssam 14810275Ssam default: 14916339Skarels fprintf(stderr, "ftpd: Unknown flag -%c ignored.\n", 15016339Skarels *cp); 15110275Ssam break; 15210275Ssam } 15311653Ssam nextopt: 15410275Ssam argc--, argv++; 15510275Ssam } 15630944Scsvsj (void) freopen("/dev/null", "w", stderr); 15726493Sminshall (void) signal(SIGPIPE, lostconn); 15826493Sminshall (void) signal(SIGCHLD, SIG_IGN); 15926044Sminshall if (signal(SIGURG, myoob) < 0) { 16026493Sminshall syslog(LOG_ERR, "signal: %m"); 16126044Sminshall } 16227750Sminshall /* handle urgent data inline */ 16327750Sminshall #ifdef SO_OOBINLINE 16427750Sminshall if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0) { 16527750Sminshall syslog(LOG_ERR, "setsockopt: %m"); 16627750Sminshall } 16727750Sminshall #endif SO_OOBINLINE 16826044Sminshall pgid = getpid(); 16927750Sminshall if (ioctl(fileno(stdin), SIOCSPGRP, (char *) &pgid) < 0) { 17026493Sminshall syslog(LOG_ERR, "ioctl: %m"); 17126044Sminshall } 17216760Slepreau dolog(&his_addr); 17316339Skarels /* do telnet option negotiation here */ 17416339Skarels /* 17516339Skarels * Set up default state 17616339Skarels */ 17716339Skarels logged_in = 0; 17816339Skarels data = -1; 17916339Skarels type = TYPE_A; 18016339Skarels form = FORM_N; 18116339Skarels stru = STRU_F; 18216339Skarels mode = MODE_S; 18326044Sminshall tmpline[0] = '\0'; 18426493Sminshall (void) gethostname(hostname, sizeof (hostname)); 18516339Skarels reply(220, "%s FTP server (%s) ready.", 18616339Skarels hostname, version); 18710275Ssam for (;;) { 18826493Sminshall (void) setjmp(errcatch); 18926493Sminshall (void) yyparse(); 19010275Ssam } 19110275Ssam } 19210419Ssam 19310275Ssam lostconn() 19410275Ssam { 19510275Ssam 19614089Ssam if (debug) 19726493Sminshall syslog(LOG_DEBUG, "lost connection"); 19814089Ssam dologout(-1); 19910275Ssam } 20010275Ssam 20110275Ssam pass(passwd) 20210275Ssam char *passwd; 20310275Ssam { 20410303Ssam char *xpasswd, *savestr(); 20510303Ssam static struct passwd save; 20610275Ssam 20710275Ssam if (logged_in || pw == NULL) { 20810275Ssam reply(503, "Login with USER first."); 20910275Ssam return; 21010275Ssam } 21110275Ssam if (!guest) { /* "ftp" is only account allowed no password */ 21210275Ssam xpasswd = crypt(passwd, pw->pw_passwd); 21316760Slepreau /* The strcmp does not catch null passwords! */ 21416760Slepreau if (*pw->pw_passwd == '\0' || strcmp(xpasswd, pw->pw_passwd)) { 21510275Ssam reply(530, "Login incorrect."); 21610275Ssam pw = NULL; 21710275Ssam return; 21810275Ssam } 21910275Ssam } 22010303Ssam setegid(pw->pw_gid); 22110275Ssam initgroups(pw->pw_name, pw->pw_gid); 22210275Ssam if (chdir(pw->pw_dir)) { 22327106Smckusick reply(530, "User %s: can't change directory to %s.", 22410275Ssam pw->pw_name, pw->pw_dir); 22510303Ssam goto bad; 22610275Ssam } 22716033Sralph 22816760Slepreau /* grab wtmp before chroot */ 22916760Slepreau wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND); 23010303Ssam if (guest && chroot(pw->pw_dir) < 0) { 23110275Ssam reply(550, "Can't set guest privileges."); 23216760Slepreau if (wtmp >= 0) { 23316760Slepreau (void) close(wtmp); 23416760Slepreau wtmp = -1; 23516760Slepreau } 23610303Ssam goto bad; 23710275Ssam } 23810275Ssam if (!guest) 23910275Ssam reply(230, "User %s logged in.", pw->pw_name); 24010275Ssam else 24110275Ssam reply(230, "Guest login ok, access restrictions apply."); 24210275Ssam logged_in = 1; 24313247Ssam dologin(pw); 24410303Ssam seteuid(pw->pw_uid); 24510303Ssam /* 24610303Ssam * Save everything so globbing doesn't 24710303Ssam * clobber the fields. 24810303Ssam */ 24910303Ssam save = *pw; 25010303Ssam save.pw_name = savestr(pw->pw_name); 25110303Ssam save.pw_passwd = savestr(pw->pw_passwd); 25210303Ssam save.pw_comment = savestr(pw->pw_comment); 25326493Sminshall save.pw_gecos = savestr(pw->pw_gecos); 25410303Ssam save.pw_dir = savestr(pw->pw_dir); 25510303Ssam save.pw_shell = savestr(pw->pw_shell); 25610303Ssam pw = &save; 25710303Ssam home = pw->pw_dir; /* home dir for globbing */ 25810303Ssam return; 25910303Ssam bad: 26010303Ssam seteuid(0); 26110303Ssam pw = NULL; 26210275Ssam } 26310275Ssam 26410303Ssam char * 26510303Ssam savestr(s) 26610303Ssam char *s; 26710303Ssam { 26810303Ssam char *malloc(); 26926493Sminshall char *new = malloc((unsigned) strlen(s) + 1); 27010303Ssam 27110303Ssam if (new != NULL) 27226493Sminshall (void) strcpy(new, s); 27311347Ssam return (new); 27410303Ssam } 27510303Ssam 27610275Ssam retrieve(cmd, name) 27710275Ssam char *cmd, *name; 27810275Ssam { 27910275Ssam FILE *fin, *dout; 28010275Ssam struct stat st; 28126044Sminshall int (*closefunc)(), tmp; 28210275Ssam 28310275Ssam if (cmd == 0) { 28410317Ssam #ifdef notdef 28510317Ssam /* no remote command execution -- it's a security hole */ 28611653Ssam if (*name == '|') 28710275Ssam fin = popen(name + 1, "r"), closefunc = pclose; 28810275Ssam else 28910317Ssam #endif 29010275Ssam fin = fopen(name, "r"), closefunc = fclose; 29110275Ssam } else { 29210275Ssam char line[BUFSIZ]; 29310275Ssam 29426493Sminshall (void) sprintf(line, cmd, name), name = line; 29510275Ssam fin = popen(line, "r"), closefunc = pclose; 29610275Ssam } 29710275Ssam if (fin == NULL) { 29813152Ssam if (errno != 0) 29913152Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 30010275Ssam return; 30110275Ssam } 30210275Ssam st.st_size = 0; 30310275Ssam if (cmd == 0 && 30410275Ssam (stat(name, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) { 30510275Ssam reply(550, "%s: not a plain file.", name); 30610275Ssam goto done; 30710275Ssam } 30810275Ssam dout = dataconn(name, st.st_size, "w"); 30910275Ssam if (dout == NULL) 31010275Ssam goto done; 31126044Sminshall if ((tmp = send_data(fin, dout)) > 0 || ferror(dout) > 0) { 31210275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 31326044Sminshall } 31426044Sminshall else if (tmp == 0) { 31510275Ssam reply(226, "Transfer complete."); 31626044Sminshall } 31726493Sminshall (void) fclose(dout); 31826044Sminshall data = -1; 31926044Sminshall pdata = -1; 32010275Ssam done: 32110275Ssam (*closefunc)(fin); 32210275Ssam } 32310275Ssam 32410275Ssam store(name, mode) 32510275Ssam char *name, *mode; 32610275Ssam { 32710275Ssam FILE *fout, *din; 32826044Sminshall int (*closefunc)(), dochown = 0, tmp; 32926044Sminshall char *gunique(), *local; 33010275Ssam 33110317Ssam #ifdef notdef 33210317Ssam /* no remote command execution -- it's a security hole */ 33311653Ssam if (name[0] == '|') 33410275Ssam fout = popen(&name[1], "w"), closefunc = pclose; 33510317Ssam else 33610317Ssam #endif 33710317Ssam { 33810303Ssam struct stat st; 33910303Ssam 34026044Sminshall local = name; 34126044Sminshall if (stat(name, &st) < 0) { 34210303Ssam dochown++; 34326044Sminshall } 34426044Sminshall else if (unique) { 34526044Sminshall if ((local = gunique(name)) == NULL) { 34626044Sminshall return; 34726044Sminshall } 34826044Sminshall dochown++; 34926044Sminshall } 35026044Sminshall fout = fopen(local, mode), closefunc = fclose; 35110303Ssam } 35210275Ssam if (fout == NULL) { 35327106Smckusick reply(553, "%s: %s.", local, sys_errlist[errno]); 35410275Ssam return; 35510275Ssam } 35626044Sminshall din = dataconn(local, (off_t)-1, "r"); 35710275Ssam if (din == NULL) 35810275Ssam goto done; 35926044Sminshall if ((tmp = receive_data(din, fout)) > 0 || ferror(fout) > 0) { 36027106Smckusick reply(552, "%s: %s.", local, sys_errlist[errno]); 36126044Sminshall } 36226044Sminshall else if (tmp == 0 && !unique) { 36310275Ssam reply(226, "Transfer complete."); 36426044Sminshall } 36526044Sminshall else if (tmp == 0 && unique) { 36626044Sminshall reply(226, "Transfer complete (unique file name:%s).", local); 36726044Sminshall } 36826493Sminshall (void) fclose(din); 36926044Sminshall data = -1; 37026044Sminshall pdata = -1; 37110275Ssam done: 37210303Ssam if (dochown) 37326044Sminshall (void) chown(local, pw->pw_uid, -1); 37410275Ssam (*closefunc)(fout); 37510275Ssam } 37610275Ssam 37710275Ssam FILE * 37810275Ssam getdatasock(mode) 37910275Ssam char *mode; 38010275Ssam { 38117157Ssam int s, on = 1; 38210275Ssam 38310275Ssam if (data >= 0) 38410275Ssam return (fdopen(data, mode)); 38513247Ssam s = socket(AF_INET, SOCK_STREAM, 0); 38610602Ssam if (s < 0) 38710275Ssam return (NULL); 38810275Ssam seteuid(0); 38926493Sminshall if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof (on)) < 0) 39010602Ssam goto bad; 39113152Ssam /* anchor socket to avoid multi-homing problems */ 39213152Ssam data_source.sin_family = AF_INET; 39313152Ssam data_source.sin_addr = ctrl_addr.sin_addr; 39426493Sminshall if (bind(s, &data_source, sizeof (data_source)) < 0) 39510602Ssam goto bad; 39610311Ssam seteuid(pw->pw_uid); 39710275Ssam return (fdopen(s, mode)); 39810602Ssam bad: 39910602Ssam seteuid(pw->pw_uid); 40026493Sminshall (void) close(s); 40110602Ssam return (NULL); 40210275Ssam } 40310275Ssam 40410275Ssam FILE * 40510275Ssam dataconn(name, size, mode) 40610275Ssam char *name; 40711653Ssam off_t size; 40810275Ssam char *mode; 40910275Ssam { 41010275Ssam char sizebuf[32]; 41110275Ssam FILE *file; 41211653Ssam int retry = 0; 41310275Ssam 41410275Ssam if (size >= 0) 41526493Sminshall (void) sprintf (sizebuf, " (%ld bytes)", size); 41610275Ssam else 41710275Ssam (void) strcpy(sizebuf, ""); 41826044Sminshall if (pdata > 0) { 41926044Sminshall struct sockaddr_in from; 42026044Sminshall int s, fromlen = sizeof(from); 42126044Sminshall 42226493Sminshall s = accept(pdata, &from, &fromlen); 42326044Sminshall if (s < 0) { 42426044Sminshall reply(425, "Can't open data connection."); 42526044Sminshall (void) close(pdata); 42626044Sminshall pdata = -1; 42726044Sminshall return(NULL); 42826044Sminshall } 42926044Sminshall (void) close(pdata); 43026044Sminshall pdata = s; 43126044Sminshall reply(150, "Openning data connection for %s (%s,%d)%s.", 43226493Sminshall name, inet_ntoa(from.sin_addr), 43326044Sminshall ntohs(from.sin_port), sizebuf); 43426044Sminshall return(fdopen(pdata, mode)); 43526044Sminshall } 43610275Ssam if (data >= 0) { 43710275Ssam reply(125, "Using existing data connection for %s%s.", 43810275Ssam name, sizebuf); 43910321Ssam usedefault = 1; 44010275Ssam return (fdopen(data, mode)); 44110275Ssam } 44210566Ssam if (usedefault) 44310422Ssam data_dest = his_addr; 44410422Ssam usedefault = 1; 44510275Ssam file = getdatasock(mode); 44610275Ssam if (file == NULL) { 44710275Ssam reply(425, "Can't create data socket (%s,%d): %s.", 44813247Ssam inet_ntoa(data_source.sin_addr), 44910275Ssam ntohs(data_source.sin_port), 45010275Ssam sys_errlist[errno]); 45110275Ssam return (NULL); 45210275Ssam } 45310275Ssam data = fileno(file); 45426044Sminshall while (connect(data, &data_dest, sizeof (data_dest)) < 0) { 45511653Ssam if (errno == EADDRINUSE && retry < swaitmax) { 45626493Sminshall sleep((unsigned) swaitint); 45711653Ssam retry += swaitint; 45811653Ssam continue; 45911653Ssam } 46010275Ssam reply(425, "Can't build data connection: %s.", 46110275Ssam sys_errlist[errno]); 46210275Ssam (void) fclose(file); 46310275Ssam data = -1; 46410275Ssam return (NULL); 46510275Ssam } 46627750Sminshall reply(150, "Opening data connection for %s (%s,%d)%s.", 46727750Sminshall name, inet_ntoa(data_dest.sin_addr), 46827750Sminshall ntohs(data_dest.sin_port), sizebuf); 46910275Ssam return (file); 47010275Ssam } 47110275Ssam 47210275Ssam /* 47310275Ssam * Tranfer the contents of "instr" to 47410275Ssam * "outstr" peer using the appropriate 47510275Ssam * encapulation of the date subject 47610275Ssam * to Mode, Structure, and Type. 47710275Ssam * 47810275Ssam * NB: Form isn't handled. 47910275Ssam */ 48010275Ssam send_data(instr, outstr) 48110275Ssam FILE *instr, *outstr; 48210275Ssam { 48310275Ssam register int c; 48410275Ssam int netfd, filefd, cnt; 48510275Ssam char buf[BUFSIZ]; 48610275Ssam 48726044Sminshall transflag++; 48826044Sminshall if (setjmp(urgcatch)) { 48926044Sminshall transflag = 0; 49026044Sminshall return(-1); 49126044Sminshall } 49210275Ssam switch (type) { 49310275Ssam 49410275Ssam case TYPE_A: 49510275Ssam while ((c = getc(instr)) != EOF) { 49611220Ssam if (c == '\n') { 49726044Sminshall if (ferror (outstr)) { 49826044Sminshall transflag = 0; 49911220Ssam return (1); 50026044Sminshall } 50127750Sminshall (void) putc('\r', outstr); 50211220Ssam } 50327750Sminshall (void) putc(c, outstr); 50426044Sminshall /* if (c == '\r') */ 50526044Sminshall /* putc ('\0', outstr); */ 50610275Ssam } 50726044Sminshall transflag = 0; 50826044Sminshall if (ferror (instr) || ferror (outstr)) { 50911220Ssam return (1); 51026044Sminshall } 51110275Ssam return (0); 51210275Ssam 51310275Ssam case TYPE_I: 51410275Ssam case TYPE_L: 51510275Ssam netfd = fileno(outstr); 51610275Ssam filefd = fileno(instr); 51710275Ssam 51826044Sminshall while ((cnt = read(filefd, buf, sizeof (buf))) > 0) { 51926044Sminshall if (write(netfd, buf, cnt) < 0) { 52026044Sminshall transflag = 0; 52110275Ssam return (1); 52226044Sminshall } 52326044Sminshall } 52426044Sminshall transflag = 0; 52510275Ssam return (cnt < 0); 52610275Ssam } 52727106Smckusick reply(550, "Unimplemented TYPE %d in send_data", type); 52826044Sminshall transflag = 0; 52927106Smckusick return (-1); 53010275Ssam } 53110275Ssam 53210275Ssam /* 53310275Ssam * Transfer data from peer to 53410275Ssam * "outstr" using the appropriate 53510275Ssam * encapulation of the data subject 53610275Ssam * to Mode, Structure, and Type. 53710275Ssam * 53810275Ssam * N.B.: Form isn't handled. 53910275Ssam */ 54010275Ssam receive_data(instr, outstr) 54110275Ssam FILE *instr, *outstr; 54210275Ssam { 54310275Ssam register int c; 54411220Ssam int cnt; 54510275Ssam char buf[BUFSIZ]; 54610275Ssam 54710275Ssam 54826044Sminshall transflag++; 54926044Sminshall if (setjmp(urgcatch)) { 55026044Sminshall transflag = 0; 55126044Sminshall return(-1); 55226044Sminshall } 55310275Ssam switch (type) { 55410275Ssam 55510275Ssam case TYPE_I: 55610275Ssam case TYPE_L: 55726044Sminshall while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0) { 55826044Sminshall if (write(fileno(outstr), buf, cnt) < 0) { 55926044Sminshall transflag = 0; 56010275Ssam return (1); 56126044Sminshall } 56226044Sminshall } 56326044Sminshall transflag = 0; 56410275Ssam return (cnt < 0); 56510275Ssam 56610275Ssam case TYPE_E: 56727106Smckusick reply(553, "TYPE E not implemented."); 56826044Sminshall transflag = 0; 56927106Smckusick return (-1); 57010275Ssam 57110275Ssam case TYPE_A: 57210275Ssam while ((c = getc(instr)) != EOF) { 57327750Sminshall while (c == '\r') { 57426044Sminshall if (ferror (outstr)) { 57526044Sminshall transflag = 0; 57611220Ssam return (1); 57726044Sminshall } 57811220Ssam if ((c = getc(instr)) != '\n') 57927750Sminshall (void) putc ('\r', outstr); 58026044Sminshall /* if (c == '\0') */ 58126044Sminshall /* continue; */ 58210275Ssam } 58327750Sminshall (void) putc (c, outstr); 58410275Ssam } 58526044Sminshall transflag = 0; 58611220Ssam if (ferror (instr) || ferror (outstr)) 58711220Ssam return (1); 58810275Ssam return (0); 58910275Ssam } 59026044Sminshall transflag = 0; 59110275Ssam fatal("Unknown type in receive_data."); 59210275Ssam /*NOTREACHED*/ 59310275Ssam } 59410275Ssam 59510275Ssam fatal(s) 59610275Ssam char *s; 59710275Ssam { 59810275Ssam reply(451, "Error in server: %s\n", s); 59910275Ssam reply(221, "Closing connection due to server error."); 60013247Ssam dologout(0); 60110275Ssam } 60210275Ssam 60332110Smckusick reply(n, s, p0, p1, p2, p3, p4) 60410275Ssam int n; 60510275Ssam char *s; 60610275Ssam { 60710275Ssam 60810275Ssam printf("%d ", n); 60932110Smckusick printf(s, p0, p1, p2, p3, p4); 61010275Ssam printf("\r\n"); 61126493Sminshall (void) fflush(stdout); 61210275Ssam if (debug) { 61326493Sminshall syslog(LOG_DEBUG, "<--- %d ", n); 61432110Smckusick syslog(LOG_DEBUG, s, p0, p1, p2, p3, p4); 61510275Ssam } 61610275Ssam } 61710275Ssam 61832110Smckusick lreply(n, s, p0, p1, p2, p3, p4) 61910275Ssam int n; 62010275Ssam char *s; 62110275Ssam { 62210275Ssam printf("%d-", n); 62332110Smckusick printf(s, p0, p1, p2, p3, p4); 62410275Ssam printf("\r\n"); 62526493Sminshall (void) fflush(stdout); 62610275Ssam if (debug) { 62726493Sminshall syslog(LOG_DEBUG, "<--- %d- ", n); 62832110Smckusick syslog(LOG_DEBUG, s, p0, p1, p2, p3, p4); 62910275Ssam } 63010275Ssam } 63110275Ssam 63210275Ssam ack(s) 63310275Ssam char *s; 63410275Ssam { 63527106Smckusick reply(250, "%s command successful.", s); 63610275Ssam } 63710275Ssam 63810275Ssam nack(s) 63910275Ssam char *s; 64010275Ssam { 64110275Ssam reply(502, "%s command not implemented.", s); 64210275Ssam } 64310275Ssam 64426493Sminshall yyerror(s) 64526493Sminshall char *s; 64610275Ssam { 64726044Sminshall char *cp; 64826044Sminshall 64926044Sminshall cp = index(cbuf,'\n'); 65026044Sminshall *cp = '\0'; 65126044Sminshall reply(500, "'%s': command not understood.",cbuf); 65210275Ssam } 65310275Ssam 65410275Ssam delete(name) 65510275Ssam char *name; 65610275Ssam { 65710275Ssam struct stat st; 65810275Ssam 65910275Ssam if (stat(name, &st) < 0) { 66010275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 66110275Ssam return; 66210275Ssam } 66310275Ssam if ((st.st_mode&S_IFMT) == S_IFDIR) { 66410275Ssam if (rmdir(name) < 0) { 66510275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 66610275Ssam return; 66710275Ssam } 66810275Ssam goto done; 66910275Ssam } 67010275Ssam if (unlink(name) < 0) { 67110275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 67210275Ssam return; 67310275Ssam } 67410275Ssam done: 67510275Ssam ack("DELE"); 67610275Ssam } 67710275Ssam 67810275Ssam cwd(path) 67910275Ssam char *path; 68010275Ssam { 68110275Ssam 68210275Ssam if (chdir(path) < 0) { 68310275Ssam reply(550, "%s: %s.", path, sys_errlist[errno]); 68410275Ssam return; 68510275Ssam } 68610275Ssam ack("CWD"); 68710275Ssam } 68810275Ssam 68910303Ssam makedir(name) 69010275Ssam char *name; 69110275Ssam { 69210303Ssam struct stat st; 69310303Ssam int dochown = stat(name, &st) < 0; 69410275Ssam 69510275Ssam if (mkdir(name, 0777) < 0) { 69610275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 69710275Ssam return; 69810275Ssam } 69910303Ssam if (dochown) 70010303Ssam (void) chown(name, pw->pw_uid, -1); 70127106Smckusick reply(257, "MKD command successful."); 70210275Ssam } 70310275Ssam 70410303Ssam removedir(name) 70510275Ssam char *name; 70610275Ssam { 70710275Ssam 70810275Ssam if (rmdir(name) < 0) { 70910275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 71010275Ssam return; 71110275Ssam } 71227106Smckusick ack("RMD"); 71310275Ssam } 71410275Ssam 71510303Ssam pwd() 71610275Ssam { 71710303Ssam char path[MAXPATHLEN + 1]; 71810275Ssam 71910275Ssam if (getwd(path) == NULL) { 72027106Smckusick reply(550, "%s.", path); 72110275Ssam return; 72210275Ssam } 72327106Smckusick reply(257, "\"%s\" is current directory.", path); 72410275Ssam } 72510275Ssam 72610275Ssam char * 72710275Ssam renamefrom(name) 72810275Ssam char *name; 72910275Ssam { 73010275Ssam struct stat st; 73110275Ssam 73210275Ssam if (stat(name, &st) < 0) { 73310275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 73410275Ssam return ((char *)0); 73510275Ssam } 73610303Ssam reply(350, "File exists, ready for destination name"); 73710275Ssam return (name); 73810275Ssam } 73910275Ssam 74010275Ssam renamecmd(from, to) 74110275Ssam char *from, *to; 74210275Ssam { 74310275Ssam 74410275Ssam if (rename(from, to) < 0) { 74510275Ssam reply(550, "rename: %s.", sys_errlist[errno]); 74610275Ssam return; 74710275Ssam } 74810275Ssam ack("RNTO"); 74910275Ssam } 75010275Ssam 75110275Ssam dolog(sin) 75210275Ssam struct sockaddr_in *sin; 75310275Ssam { 75410275Ssam struct hostent *hp = gethostbyaddr(&sin->sin_addr, 75510275Ssam sizeof (struct in_addr), AF_INET); 75610275Ssam time_t t; 75726493Sminshall extern char *ctime(); 75810275Ssam 75913247Ssam if (hp) { 76026493Sminshall (void) strncpy(remotehost, hp->h_name, sizeof (remotehost)); 76113247Ssam endhostent(); 76213247Ssam } else 76326493Sminshall (void) strncpy(remotehost, inet_ntoa(sin->sin_addr), 76413247Ssam sizeof (remotehost)); 76513247Ssam if (!logging) 76613247Ssam return; 76726493Sminshall t = time((time_t *) 0); 76826493Sminshall syslog(LOG_INFO,"FTPD: connection from %s at %s", remotehost, ctime(&t)); 76910275Ssam } 77010695Ssam 77113247Ssam #include <utmp.h> 77213247Ssam 77326493Sminshall #define SCPYN(a, b) (void) strncpy(a, b, sizeof (a)) 77413247Ssam struct utmp utmp; 77513247Ssam 77610695Ssam /* 77713247Ssam * Record login in wtmp file. 77813247Ssam */ 77913247Ssam dologin(pw) 78013247Ssam struct passwd *pw; 78113247Ssam { 78213247Ssam char line[32]; 78313247Ssam 78413247Ssam if (wtmp >= 0) { 78513247Ssam /* hack, but must be unique and no tty line */ 78626493Sminshall (void) sprintf(line, "ftp%d", getpid()); 78713247Ssam SCPYN(utmp.ut_line, line); 78813247Ssam SCPYN(utmp.ut_name, pw->pw_name); 78913247Ssam SCPYN(utmp.ut_host, remotehost); 79026493Sminshall utmp.ut_time = (long) time((time_t *) 0); 79113247Ssam (void) write(wtmp, (char *)&utmp, sizeof (utmp)); 79216760Slepreau if (!guest) { /* anon must hang on */ 79316760Slepreau (void) close(wtmp); 79416760Slepreau wtmp = -1; 79516760Slepreau } 79613247Ssam } 79713247Ssam } 79813247Ssam 79913247Ssam /* 80013247Ssam * Record logout in wtmp file 80113247Ssam * and exit with supplied status. 80213247Ssam */ 80313247Ssam dologout(status) 80413247Ssam int status; 80513247Ssam { 80616339Skarels 80717580Ssam if (logged_in) { 80817580Ssam (void) seteuid(0); 80917580Ssam if (wtmp < 0) 81017580Ssam wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND); 81117580Ssam if (wtmp >= 0) { 81217580Ssam SCPYN(utmp.ut_name, ""); 81317580Ssam SCPYN(utmp.ut_host, ""); 81426493Sminshall utmp.ut_time = (long) time((time_t *) 0); 81517580Ssam (void) write(wtmp, (char *)&utmp, sizeof (utmp)); 81617580Ssam (void) close(wtmp); 81717580Ssam } 81813247Ssam } 81914436Ssam /* beware of flushing buffers after a SIGPIPE */ 82014436Ssam _exit(status); 82113247Ssam } 82213247Ssam 82313247Ssam /* 82410695Ssam * Special version of popen which avoids 82510695Ssam * call to shell. This insures noone may 82610695Ssam * create a pipe to a hidden program as a side 82710695Ssam * effect of a list or dir command. 82810695Ssam */ 82910695Ssam #define tst(a,b) (*mode == 'r'? (b) : (a)) 83010695Ssam #define RDR 0 83110695Ssam #define WTR 1 83210695Ssam static int popen_pid[5]; 83310695Ssam 83410695Ssam static char * 83510695Ssam nextarg(cpp) 83610695Ssam char *cpp; 83710695Ssam { 83810695Ssam register char *cp = cpp; 83910695Ssam 84010695Ssam if (cp == 0) 84110695Ssam return (cp); 84210695Ssam while (*cp && *cp != ' ' && *cp != '\t') 84310695Ssam cp++; 84410695Ssam if (*cp == ' ' || *cp == '\t') { 84510695Ssam *cp++ = '\0'; 84610695Ssam while (*cp == ' ' || *cp == '\t') 84710695Ssam cp++; 84810695Ssam } 84910695Ssam if (cp == cpp) 85010695Ssam return ((char *)0); 85110695Ssam return (cp); 85210695Ssam } 85310695Ssam 85410695Ssam FILE * 85510695Ssam popen(cmd, mode) 85610695Ssam char *cmd, *mode; 85710695Ssam { 85813211Sroot int p[2], ac, gac; 85910695Ssam register myside, hisside, pid; 86013211Sroot char *av[20], *gav[512]; 86110695Ssam register char *cp; 86210695Ssam 86310695Ssam if (pipe(p) < 0) 86410695Ssam return (NULL); 86510695Ssam cp = cmd, ac = 0; 86613211Sroot /* break up string into pieces */ 86710695Ssam do { 86810695Ssam av[ac++] = cp; 86910695Ssam cp = nextarg(cp); 87013211Sroot } while (cp && *cp && ac < 20); 87110695Ssam av[ac] = (char *)0; 87213211Sroot gav[0] = av[0]; 87313211Sroot /* glob each piece */ 87413211Sroot for (gac = ac = 1; av[ac] != NULL; ac++) { 87513211Sroot char **pop; 87622024Ssam extern char **glob(), **copyblk(); 87713211Sroot 87813211Sroot pop = glob(av[ac]); 87922024Ssam if (pop == (char **)NULL) { /* globbing failed */ 88022024Ssam char *vv[2]; 88122024Ssam 88222024Ssam vv[0] = av[ac]; 88322024Ssam vv[1] = 0; 88422024Ssam pop = copyblk(vv); 88513211Sroot } 88622024Ssam av[ac] = (char *)pop; /* save to free later */ 88722024Ssam while (*pop && gac < 512) 88822024Ssam gav[gac++] = *pop++; 88911757Ssam } 89013211Sroot gav[gac] = (char *)0; 89110695Ssam myside = tst(p[WTR], p[RDR]); 89210695Ssam hisside = tst(p[RDR], p[WTR]); 89310695Ssam if ((pid = fork()) == 0) { 89410695Ssam /* myside and hisside reverse roles in child */ 89526493Sminshall (void) close(myside); 89626493Sminshall (void) dup2(hisside, tst(0, 1)); 89726493Sminshall (void) close(hisside); 89813211Sroot execv(gav[0], gav); 89910695Ssam _exit(1); 90010695Ssam } 90113211Sroot for (ac = 1; av[ac] != NULL; ac++) 90213211Sroot blkfree((char **)av[ac]); 90310695Ssam if (pid == -1) 90410695Ssam return (NULL); 90510695Ssam popen_pid[myside] = pid; 90626493Sminshall (void) close(hisside); 90710695Ssam return (fdopen(myside, mode)); 90810695Ssam } 90910695Ssam 91010695Ssam pclose(ptr) 91110695Ssam FILE *ptr; 91210695Ssam { 91310695Ssam register f, r, (*hstat)(), (*istat)(), (*qstat)(); 91410695Ssam int status; 91510695Ssam 91610695Ssam f = fileno(ptr); 91726493Sminshall (void) fclose(ptr); 91810695Ssam istat = signal(SIGINT, SIG_IGN); 91910695Ssam qstat = signal(SIGQUIT, SIG_IGN); 92010695Ssam hstat = signal(SIGHUP, SIG_IGN); 92110695Ssam while ((r = wait(&status)) != popen_pid[f] && r != -1) 92210695Ssam ; 92310695Ssam if (r == -1) 92410695Ssam status = -1; 92526493Sminshall (void) signal(SIGINT, istat); 92626493Sminshall (void) signal(SIGQUIT, qstat); 92726493Sminshall (void) signal(SIGHUP, hstat); 92810695Ssam return (status); 92910695Ssam } 93010695Ssam 93110695Ssam /* 93210695Ssam * Check user requesting login priviledges. 93328864Smckusick * Disallow anyone who does not have a standard 93428864Smckusick * shell returned by getusershell() (/etc/shells). 93510695Ssam * Disallow anyone mentioned in the file FTPUSERS 93610695Ssam * to allow people such as uucp to be avoided. 93710695Ssam */ 93810695Ssam checkuser(name) 93910695Ssam register char *name; 94010695Ssam { 94128864Smckusick register char *cp; 94228864Smckusick char line[BUFSIZ], *index(), *getusershell(); 94310695Ssam FILE *fd; 94428864Smckusick struct passwd *pw; 94510695Ssam int found = 0; 94610695Ssam 94728864Smckusick pw = getpwnam(name); 94828864Smckusick if (pw == NULL) 94928864Smckusick return (0); 95030102Sbostic if (pw ->pw_shell == NULL || pw->pw_shell[0] == NULL) 95130102Sbostic pw->pw_shell = "/bin/sh"; 95228864Smckusick while ((cp = getusershell()) != NULL) 95328864Smckusick if (strcmp(cp, pw->pw_shell) == 0) 95428864Smckusick break; 95528864Smckusick endusershell(); 95628864Smckusick if (cp == NULL) 95728864Smckusick return (0); 95810695Ssam fd = fopen(FTPUSERS, "r"); 95910695Ssam if (fd == NULL) 96010695Ssam return (1); 96110695Ssam while (fgets(line, sizeof (line), fd) != NULL) { 96228864Smckusick cp = index(line, '\n'); 96310695Ssam if (cp) 96410695Ssam *cp = '\0'; 96510695Ssam if (strcmp(line, name) == 0) { 96610695Ssam found++; 96710695Ssam break; 96810695Ssam } 96910695Ssam } 97026493Sminshall (void) fclose(fd); 97110695Ssam return (!found); 97210695Ssam } 97326044Sminshall 97426044Sminshall myoob() 97526044Sminshall { 97627750Sminshall char *cp; 97726044Sminshall 97827750Sminshall /* only process if transfer occurring */ 97926044Sminshall if (!transflag) { 98026044Sminshall return; 98126044Sminshall } 98227750Sminshall cp = tmpline; 98327750Sminshall if (getline(cp, 7, stdin) == NULL) { 98427750Sminshall reply(221, "You could at least say goodby."); 98527750Sminshall dologout(0); 98626044Sminshall } 98726044Sminshall upper(cp); 98826227Ssam if (strcmp(cp, "ABOR\r\n")) 98926044Sminshall return; 99026044Sminshall tmpline[0] = '\0'; 99126044Sminshall reply(426,"Transfer aborted. Data connection closed."); 99226044Sminshall reply(226,"Abort successful"); 99326044Sminshall longjmp(urgcatch, 1); 99426044Sminshall } 99526044Sminshall 99627106Smckusick /* 99727106Smckusick * Note: The 530 reply codes could be 4xx codes, except nothing is 99827106Smckusick * given in the state tables except 421 which implies an exit. (RFC959) 99927106Smckusick */ 100026044Sminshall passive() 100126044Sminshall { 100226044Sminshall int len; 100326044Sminshall struct sockaddr_in tmp; 100426044Sminshall register char *p, *a; 100526044Sminshall 100626044Sminshall pdata = socket(AF_INET, SOCK_STREAM, 0); 100726044Sminshall if (pdata < 0) { 100827106Smckusick reply(530, "Can't open passive connection"); 100926044Sminshall return; 101026044Sminshall } 101126044Sminshall tmp = ctrl_addr; 101226044Sminshall tmp.sin_port = 0; 101326044Sminshall seteuid(0); 101426493Sminshall if (bind(pdata, (struct sockaddr *) &tmp, sizeof(tmp)) < 0) { 101526044Sminshall seteuid(pw->pw_uid); 101626044Sminshall (void) close(pdata); 101726044Sminshall pdata = -1; 101827106Smckusick reply(530, "Can't open passive connection"); 101926044Sminshall return; 102026044Sminshall } 102126044Sminshall seteuid(pw->pw_uid); 102226044Sminshall len = sizeof(tmp); 102326044Sminshall if (getsockname(pdata, (char *) &tmp, &len) < 0) { 102426044Sminshall (void) close(pdata); 102526044Sminshall pdata = -1; 102627106Smckusick reply(530, "Can't open passive connection"); 102726044Sminshall return; 102826044Sminshall } 102926044Sminshall if (listen(pdata, 1) < 0) { 103026044Sminshall (void) close(pdata); 103126044Sminshall pdata = -1; 103227106Smckusick reply(530, "Can't open passive connection"); 103326044Sminshall return; 103426044Sminshall } 103526044Sminshall a = (char *) &tmp.sin_addr; 103626044Sminshall p = (char *) &tmp.sin_port; 103726044Sminshall 103826044Sminshall #define UC(b) (((int) b) & 0xff) 103926044Sminshall 104026044Sminshall reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]), 104126044Sminshall UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1])); 104226044Sminshall } 104326044Sminshall 104426044Sminshall char * 104526044Sminshall gunique(local) 104626044Sminshall char *local; 104726044Sminshall { 104826044Sminshall static char new[MAXPATHLEN]; 104926044Sminshall char *cp = rindex(local, '/'); 105026044Sminshall int d, count=0; 105126044Sminshall char ext = '1'; 105226044Sminshall 105326044Sminshall if (cp) { 105426044Sminshall *cp = '\0'; 105526044Sminshall } 105626044Sminshall d = access(cp ? local : ".", 2); 105726044Sminshall if (cp) { 105826044Sminshall *cp = '/'; 105926044Sminshall } 106026044Sminshall if (d < 0) { 106126493Sminshall syslog(LOG_ERR, "%s: %m", local); 106226044Sminshall return((char *) 0); 106326044Sminshall } 106426044Sminshall (void) strcpy(new, local); 106526044Sminshall cp = new + strlen(new); 106626044Sminshall *cp++ = '.'; 106726044Sminshall while (!d) { 106826044Sminshall if (++count == 100) { 106927106Smckusick reply(452, "Unique file name not cannot be created."); 107026044Sminshall return((char *) 0); 107126044Sminshall } 107226044Sminshall *cp++ = ext; 107326044Sminshall *cp = '\0'; 107426044Sminshall if (ext == '9') { 107526044Sminshall ext = '0'; 107626044Sminshall } 107726044Sminshall else { 107826044Sminshall ext++; 107926044Sminshall } 108026044Sminshall if ((d = access(new, 0)) < 0) { 108126044Sminshall break; 108226044Sminshall } 108326044Sminshall if (ext != '0') { 108426044Sminshall cp--; 108526044Sminshall } 108626044Sminshall else if (*(cp - 2) == '.') { 108726044Sminshall *(cp - 1) = '1'; 108826044Sminshall } 108926044Sminshall else { 109026044Sminshall *(cp - 2) = *(cp - 2) + 1; 109126044Sminshall cp--; 109226044Sminshall } 109326044Sminshall } 109426044Sminshall return(new); 109526044Sminshall } 1096