110275Ssam #ifndef lint 2*13595Ssam static char sccsid[] = "@(#)ftpd.c 4.24 (Berkeley) 07/01/83"; 310275Ssam #endif 410275Ssam 510275Ssam /* 610275Ssam * FTP server. 710275Ssam */ 810303Ssam #include <sys/param.h> 910275Ssam #include <sys/stat.h> 1010275Ssam #include <sys/ioctl.h> 1110275Ssam #include <sys/socket.h> 1213247Ssam #include <sys/file.h> 13*13595Ssam #include <sys/wait.h> 1410275Ssam 1510275Ssam #include <netinet/in.h> 1610275Ssam 1713034Ssam #include <arpa/ftp.h> 1813211Sroot #include <arpa/inet.h> 1913034Ssam 2010275Ssam #include <stdio.h> 2110275Ssam #include <signal.h> 2210275Ssam #include <pwd.h> 2310275Ssam #include <setjmp.h> 2410275Ssam #include <netdb.h> 2510423Ssam #include <errno.h> 2610275Ssam 2710695Ssam /* 2810695Ssam * File containing login names 2910695Ssam * NOT to be used on this machine. 3010695Ssam * Commonly used to disallow uucp. 3110695Ssam */ 3210695Ssam #define FTPUSERS "/etc/ftpusers" 3310695Ssam 3410275Ssam extern int errno; 3510275Ssam extern char *sys_errlist[]; 3610275Ssam extern char *crypt(); 3710275Ssam extern char version[]; 3810275Ssam extern char *home; /* pointer to home directory for glob */ 3910275Ssam extern FILE *popen(), *fopen(); 4010275Ssam extern int pclose(), fclose(); 4110275Ssam 4210275Ssam struct sockaddr_in ctrl_addr; 4310275Ssam struct sockaddr_in data_source; 4410275Ssam struct sockaddr_in data_dest; 4510275Ssam struct sockaddr_in his_addr; 4610275Ssam 4710275Ssam struct hostent *hp; 4810275Ssam 4910275Ssam int data; 5010275Ssam jmp_buf errcatch; 5110275Ssam int logged_in; 5210275Ssam struct passwd *pw; 5310275Ssam int debug; 5411653Ssam int timeout; 5511757Ssam int logging; 5610275Ssam int guest; 5710275Ssam int type; 5810275Ssam int form; 5910275Ssam int stru; /* avoid C keyword */ 6010275Ssam int mode; 6110321Ssam int usedefault = 1; /* for data transfers */ 6210275Ssam char hostname[32]; 6313247Ssam char remotehost[32]; 6410321Ssam struct servent *sp; 6510275Ssam 6611653Ssam /* 6711653Ssam * Timeout intervals for retrying connections 6811653Ssam * to hosts that don't accept PORT cmds. This 6911653Ssam * is a kludge, but given the problems with TCP... 7011653Ssam */ 7111653Ssam #define SWAITMAX 90 /* wait at most 90 seconds */ 7211653Ssam #define SWAITINT 5 /* interval between retries */ 7311653Ssam 7411653Ssam int swaitmax = SWAITMAX; 7511653Ssam int swaitint = SWAITINT; 7611653Ssam 7710275Ssam int lostconn(); 7810419Ssam int reapchild(); 7910275Ssam FILE *getdatasock(), *dataconn(); 8010275Ssam 8110275Ssam main(argc, argv) 8210275Ssam int argc; 8310275Ssam char *argv[]; 8410275Ssam { 8510275Ssam int ctrl, s, options = 0; 8610275Ssam char *cp; 8710275Ssam 8810275Ssam sp = getservbyname("ftp", "tcp"); 8910275Ssam if (sp == 0) { 9011220Ssam fprintf(stderr, "ftpd: ftp/tcp: unknown service\n"); 9110275Ssam exit(1); 9210275Ssam } 9310275Ssam ctrl_addr.sin_port = sp->s_port; 9410275Ssam data_source.sin_port = htons(ntohs(sp->s_port) - 1); 9510275Ssam signal(SIGPIPE, lostconn); 9610275Ssam debug = 0; 9710275Ssam argc--, argv++; 9810275Ssam while (argc > 0 && *argv[0] == '-') { 9910275Ssam for (cp = &argv[0][1]; *cp; cp++) switch (*cp) { 10010275Ssam 10111653Ssam case 'v': 10211653Ssam debug = 1; 10311653Ssam break; 10411653Ssam 10510275Ssam case 'd': 10610275Ssam debug = 1; 10710275Ssam options |= SO_DEBUG; 10810275Ssam break; 10910275Ssam 11011757Ssam case 'l': 11111757Ssam logging = 1; 11211757Ssam break; 11311757Ssam 11411653Ssam case 't': 11511653Ssam timeout = atoi(++cp); 11611653Ssam goto nextopt; 11711653Ssam break; 11811653Ssam 11910275Ssam default: 12011653Ssam fprintf(stderr, "Unknown flag -%c ignored.\n", *cp); 12110275Ssam break; 12210275Ssam } 12311653Ssam nextopt: 12410275Ssam argc--, argv++; 12510275Ssam } 12610275Ssam #ifndef DEBUG 12710275Ssam if (fork()) 12810275Ssam exit(0); 12910275Ssam for (s = 0; s < 10; s++) 13011653Ssam if (!logging || (s != 2)) 13111653Ssam (void) close(s); 13213247Ssam (void) open("/", O_RDONLY); 13310275Ssam (void) dup2(0, 1); 13411653Ssam if (!logging) 13511653Ssam (void) dup2(0, 2); 13613247Ssam { int tt = open("/dev/tty", O_RDWR); 13710275Ssam if (tt > 0) { 13810275Ssam ioctl(tt, TIOCNOTTY, 0); 13910275Ssam close(tt); 14010275Ssam } 14110275Ssam } 14210275Ssam #endif 14313247Ssam while ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 14410275Ssam perror("ftpd: socket"); 14510275Ssam sleep(5); 14610275Ssam } 14710419Ssam if (options & SO_DEBUG) 14810419Ssam if (setsockopt(s, SOL_SOCKET, SO_DEBUG, 0, 0) < 0) 14910419Ssam perror("ftpd: setsockopt (SO_DEBUG)"); 15010419Ssam if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 0, 0) < 0) 15110419Ssam perror("ftpd: setsockopt (SO_KEEPALIVE)"); 15210275Ssam while (bind(s, &ctrl_addr, sizeof (ctrl_addr), 0) < 0) { 15310275Ssam perror("ftpd: bind"); 15410275Ssam sleep(5); 15510275Ssam } 15613034Ssam signal(SIGCHLD, reapchild); 15710303Ssam listen(s, 10); 15810275Ssam for (;;) { 15910275Ssam int hisaddrlen = sizeof (his_addr); 16010275Ssam 16110275Ssam ctrl = accept(s, &his_addr, &hisaddrlen, 0); 16210275Ssam if (ctrl < 0) { 16310419Ssam if (errno == EINTR) 16410419Ssam continue; 16510275Ssam perror("ftpd: accept"); 16610275Ssam continue; 16710275Ssam } 16810275Ssam if (fork() == 0) { 16911220Ssam signal (SIGCHLD, SIG_IGN); 17013247Ssam dolog(&his_addr); 17110275Ssam close(s); 17210275Ssam dup2(ctrl, 0), close(ctrl), dup2(0, 1); 17310275Ssam /* do telnet option negotiation here */ 17410303Ssam /* 17510303Ssam * Set up default state 17610303Ssam */ 17710275Ssam logged_in = 0; 17810275Ssam data = -1; 17910303Ssam type = TYPE_A; 18010303Ssam form = FORM_N; 18110303Ssam stru = STRU_F; 18210303Ssam mode = MODE_S; 18313152Ssam (void) getsockname(0, &ctrl_addr, sizeof (ctrl_addr)); 18410275Ssam gethostname(hostname, sizeof (hostname)); 18510275Ssam reply(220, "%s FTP server (%s) ready.", 18610275Ssam hostname, version); 18710275Ssam for (;;) { 18810275Ssam setjmp(errcatch); 18910275Ssam yyparse(); 19010275Ssam } 19110275Ssam } 19210275Ssam close(ctrl); 19310275Ssam } 19410275Ssam } 19510275Ssam 19610419Ssam reapchild() 19710419Ssam { 19810419Ssam union wait status; 19910419Ssam 20010419Ssam while (wait3(&status, WNOHANG, 0) > 0) 20110419Ssam ; 20210419Ssam } 20310419Ssam 20410275Ssam lostconn() 20510275Ssam { 20610275Ssam 20710275Ssam fatal("Connection closed."); 20810275Ssam } 20910275Ssam 21010275Ssam pass(passwd) 21110275Ssam char *passwd; 21210275Ssam { 21310303Ssam char *xpasswd, *savestr(); 21410303Ssam static struct passwd save; 21510275Ssam 21610275Ssam if (logged_in || pw == NULL) { 21710275Ssam reply(503, "Login with USER first."); 21810275Ssam return; 21910275Ssam } 22010275Ssam if (!guest) { /* "ftp" is only account allowed no password */ 22110275Ssam xpasswd = crypt(passwd, pw->pw_passwd); 22210275Ssam if (strcmp(xpasswd, pw->pw_passwd) != 0) { 22310275Ssam reply(530, "Login incorrect."); 22410275Ssam pw = NULL; 22510275Ssam return; 22610275Ssam } 22710275Ssam } 22810303Ssam setegid(pw->pw_gid); 22910275Ssam initgroups(pw->pw_name, pw->pw_gid); 23010275Ssam if (chdir(pw->pw_dir)) { 23110275Ssam reply(550, "User %s: can't change directory to $s.", 23210275Ssam pw->pw_name, pw->pw_dir); 23310303Ssam goto bad; 23410275Ssam } 23510303Ssam if (guest && chroot(pw->pw_dir) < 0) { 23610275Ssam reply(550, "Can't set guest privileges."); 23710303Ssam goto bad; 23810275Ssam } 23910275Ssam if (!guest) 24010275Ssam reply(230, "User %s logged in.", pw->pw_name); 24110275Ssam else 24210275Ssam reply(230, "Guest login ok, access restrictions apply."); 24310275Ssam logged_in = 1; 24413247Ssam dologin(pw); 24510303Ssam seteuid(pw->pw_uid); 24610303Ssam /* 24710303Ssam * Save everything so globbing doesn't 24810303Ssam * clobber the fields. 24910303Ssam */ 25010303Ssam save = *pw; 25110303Ssam save.pw_name = savestr(pw->pw_name); 25210303Ssam save.pw_passwd = savestr(pw->pw_passwd); 25310303Ssam save.pw_comment = savestr(pw->pw_comment); 25410303Ssam save.pw_gecos = savestr(pw->pw_gecos, &save.pw_gecos); 25510303Ssam save.pw_dir = savestr(pw->pw_dir); 25610303Ssam save.pw_shell = savestr(pw->pw_shell); 25710303Ssam pw = &save; 25810303Ssam home = pw->pw_dir; /* home dir for globbing */ 25910303Ssam return; 26010303Ssam bad: 26110303Ssam seteuid(0); 26210303Ssam pw = NULL; 26310275Ssam } 26410275Ssam 26510303Ssam char * 26610303Ssam savestr(s) 26710303Ssam char *s; 26810303Ssam { 26910303Ssam char *malloc(); 27010303Ssam char *new = malloc(strlen(s) + 1); 27110303Ssam 27210303Ssam if (new != NULL) 27310303Ssam strcpy(new, s); 27411347Ssam return (new); 27510303Ssam } 27610303Ssam 27710275Ssam retrieve(cmd, name) 27810275Ssam char *cmd, *name; 27910275Ssam { 28010275Ssam FILE *fin, *dout; 28110275Ssam struct stat st; 28210275Ssam int (*closefunc)(); 28310275Ssam 28410275Ssam if (cmd == 0) { 28510317Ssam #ifdef notdef 28610317Ssam /* no remote command execution -- it's a security hole */ 28711653Ssam if (*name == '|') 28810275Ssam fin = popen(name + 1, "r"), closefunc = pclose; 28910275Ssam else 29010317Ssam #endif 29110275Ssam fin = fopen(name, "r"), closefunc = fclose; 29210275Ssam } else { 29310275Ssam char line[BUFSIZ]; 29410275Ssam 29510422Ssam sprintf(line, cmd, name), name = line; 29610275Ssam fin = popen(line, "r"), closefunc = pclose; 29710275Ssam } 29810275Ssam if (fin == NULL) { 29913152Ssam if (errno != 0) 30013152Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 30110275Ssam return; 30210275Ssam } 30310275Ssam st.st_size = 0; 30410275Ssam if (cmd == 0 && 30510275Ssam (stat(name, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) { 30610275Ssam reply(550, "%s: not a plain file.", name); 30710275Ssam goto done; 30810275Ssam } 30910275Ssam dout = dataconn(name, st.st_size, "w"); 31010275Ssam if (dout == NULL) 31110275Ssam goto done; 31210303Ssam if (send_data(fin, dout) || ferror(dout)) 31310275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 31410275Ssam else 31510275Ssam reply(226, "Transfer complete."); 31610303Ssam fclose(dout), data = -1; 31710275Ssam done: 31810275Ssam (*closefunc)(fin); 31910275Ssam } 32010275Ssam 32110275Ssam store(name, mode) 32210275Ssam char *name, *mode; 32310275Ssam { 32410275Ssam FILE *fout, *din; 32510303Ssam int (*closefunc)(), dochown = 0; 32610275Ssam 32710317Ssam #ifdef notdef 32810317Ssam /* no remote command execution -- it's a security hole */ 32911653Ssam if (name[0] == '|') 33010275Ssam fout = popen(&name[1], "w"), closefunc = pclose; 33110317Ssam else 33210317Ssam #endif 33310317Ssam { 33410303Ssam struct stat st; 33510303Ssam 33610303Ssam if (stat(name, &st) < 0) 33710303Ssam dochown++; 33810275Ssam fout = fopen(name, mode), closefunc = fclose; 33910303Ssam } 34010275Ssam if (fout == NULL) { 34110275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 34210275Ssam return; 34310275Ssam } 34411653Ssam din = dataconn(name, (off_t)-1, "r"); 34510275Ssam if (din == NULL) 34610275Ssam goto done; 34710303Ssam if (receive_data(din, fout) || ferror(fout)) 34810275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 34910275Ssam else 35010275Ssam reply(226, "Transfer complete."); 35110275Ssam fclose(din), data = -1; 35210275Ssam done: 35310303Ssam if (dochown) 35410303Ssam (void) chown(name, pw->pw_uid, -1); 35510275Ssam (*closefunc)(fout); 35610275Ssam } 35710275Ssam 35810275Ssam FILE * 35910275Ssam getdatasock(mode) 36010275Ssam char *mode; 36110275Ssam { 36213247Ssam int s; 36310275Ssam 36410275Ssam if (data >= 0) 36510275Ssam return (fdopen(data, mode)); 36613247Ssam s = socket(AF_INET, SOCK_STREAM, 0); 36710602Ssam if (s < 0) 36810275Ssam return (NULL); 36910275Ssam seteuid(0); 37010602Ssam if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0) 37110602Ssam goto bad; 37213152Ssam /* anchor socket to avoid multi-homing problems */ 37313152Ssam data_source.sin_family = AF_INET; 37413152Ssam data_source.sin_addr = ctrl_addr.sin_addr; 37510602Ssam if (bind(s, &data_source, sizeof (data_source), 0) < 0) 37610602Ssam goto bad; 37710311Ssam seteuid(pw->pw_uid); 37810275Ssam return (fdopen(s, mode)); 37910602Ssam bad: 38010602Ssam seteuid(pw->pw_uid); 38110602Ssam close(s); 38210602Ssam return (NULL); 38310275Ssam } 38410275Ssam 38510275Ssam FILE * 38610275Ssam dataconn(name, size, mode) 38710275Ssam char *name; 38811653Ssam off_t size; 38910275Ssam char *mode; 39010275Ssam { 39110275Ssam char sizebuf[32]; 39210275Ssam FILE *file; 39311653Ssam int retry = 0; 39410275Ssam 39510275Ssam if (size >= 0) 39611653Ssam sprintf (sizebuf, " (%ld bytes)", size); 39710275Ssam else 39810275Ssam (void) strcpy(sizebuf, ""); 39910275Ssam if (data >= 0) { 40010275Ssam reply(125, "Using existing data connection for %s%s.", 40110275Ssam name, sizebuf); 40210321Ssam usedefault = 1; 40310275Ssam return (fdopen(data, mode)); 40410275Ssam } 40510566Ssam if (usedefault) 40610422Ssam data_dest = his_addr; 40710422Ssam usedefault = 1; 40810275Ssam file = getdatasock(mode); 40910275Ssam if (file == NULL) { 41010275Ssam reply(425, "Can't create data socket (%s,%d): %s.", 41113247Ssam inet_ntoa(data_source.sin_addr), 41210275Ssam ntohs(data_source.sin_port), 41310275Ssam sys_errlist[errno]); 41410275Ssam return (NULL); 41510275Ssam } 41610602Ssam reply(150, "Opening data connection for %s (%s,%d)%s.", 41713247Ssam name, inet_ntoa(data_dest.sin_addr.s_addr), 41810602Ssam ntohs(data_dest.sin_port), sizebuf); 41910275Ssam data = fileno(file); 42011653Ssam while (connect(data, &data_dest, sizeof (data_dest), 0) < 0) { 42111653Ssam if (errno == EADDRINUSE && retry < swaitmax) { 42211653Ssam sleep(swaitint); 42311653Ssam retry += swaitint; 42411653Ssam continue; 42511653Ssam } 42610275Ssam reply(425, "Can't build data connection: %s.", 42710275Ssam sys_errlist[errno]); 42810275Ssam (void) fclose(file); 42910275Ssam data = -1; 43010275Ssam return (NULL); 43110275Ssam } 43210275Ssam return (file); 43310275Ssam } 43410275Ssam 43510275Ssam /* 43610275Ssam * Tranfer the contents of "instr" to 43710275Ssam * "outstr" peer using the appropriate 43810275Ssam * encapulation of the date subject 43910275Ssam * to Mode, Structure, and Type. 44010275Ssam * 44110275Ssam * NB: Form isn't handled. 44210275Ssam */ 44310275Ssam send_data(instr, outstr) 44410275Ssam FILE *instr, *outstr; 44510275Ssam { 44610275Ssam register int c; 44710275Ssam int netfd, filefd, cnt; 44810275Ssam char buf[BUFSIZ]; 44910275Ssam 45010275Ssam switch (type) { 45110275Ssam 45210275Ssam case TYPE_A: 45310275Ssam while ((c = getc(instr)) != EOF) { 45411220Ssam if (c == '\n') { 45511220Ssam if (ferror (outstr)) 45611220Ssam return (1); 45710275Ssam putc('\r', outstr); 45811220Ssam } 45911220Ssam putc(c, outstr); 46011220Ssam if (c == '\r') 46111220Ssam putc ('\0', outstr); 46210275Ssam } 46311220Ssam if (ferror (instr) || ferror (outstr)) 46411220Ssam return (1); 46510275Ssam return (0); 46610275Ssam 46710275Ssam case TYPE_I: 46810275Ssam case TYPE_L: 46910275Ssam netfd = fileno(outstr); 47010275Ssam filefd = fileno(instr); 47110275Ssam 47210303Ssam while ((cnt = read(filefd, buf, sizeof (buf))) > 0) 47310275Ssam if (write(netfd, buf, cnt) < 0) 47410275Ssam return (1); 47510275Ssam return (cnt < 0); 47610275Ssam } 47710275Ssam reply(504,"Unimplemented TYPE %d in send_data", type); 47810275Ssam return (1); 47910275Ssam } 48010275Ssam 48110275Ssam /* 48210275Ssam * Transfer data from peer to 48310275Ssam * "outstr" using the appropriate 48410275Ssam * encapulation of the data subject 48510275Ssam * to Mode, Structure, and Type. 48610275Ssam * 48710275Ssam * N.B.: Form isn't handled. 48810275Ssam */ 48910275Ssam receive_data(instr, outstr) 49010275Ssam FILE *instr, *outstr; 49110275Ssam { 49210275Ssam register int c; 49311220Ssam int cnt; 49410275Ssam char buf[BUFSIZ]; 49510275Ssam 49610275Ssam 49710275Ssam switch (type) { 49810275Ssam 49910275Ssam case TYPE_I: 50010275Ssam case TYPE_L: 50110616Ssam while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0) 50210616Ssam if (write(fileno(outstr), buf, cnt) < 0) 50310275Ssam return (1); 50410275Ssam return (cnt < 0); 50510275Ssam 50610275Ssam case TYPE_E: 50710275Ssam reply(504, "TYPE E not implemented."); 50810275Ssam return (1); 50910275Ssam 51010275Ssam case TYPE_A: 51110275Ssam while ((c = getc(instr)) != EOF) { 51210275Ssam if (c == '\r') { 51311220Ssam if (ferror (outstr)) 51411220Ssam return (1); 51511220Ssam if ((c = getc(instr)) != '\n') 51611220Ssam putc ('\r', outstr); 51711220Ssam if (c == '\0') 51811220Ssam continue; 51910275Ssam } 52011220Ssam putc (c, outstr); 52110275Ssam } 52211220Ssam if (ferror (instr) || ferror (outstr)) 52311220Ssam return (1); 52410275Ssam return (0); 52510275Ssam } 52610275Ssam fatal("Unknown type in receive_data."); 52710275Ssam /*NOTREACHED*/ 52810275Ssam } 52910275Ssam 53010275Ssam fatal(s) 53110275Ssam char *s; 53210275Ssam { 53310275Ssam reply(451, "Error in server: %s\n", s); 53410275Ssam reply(221, "Closing connection due to server error."); 53513247Ssam dologout(0); 53610275Ssam } 53710275Ssam 53810275Ssam reply(n, s, args) 53910275Ssam int n; 54010275Ssam char *s; 54110275Ssam { 54210275Ssam 54310275Ssam printf("%d ", n); 54410275Ssam _doprnt(s, &args, stdout); 54510275Ssam printf("\r\n"); 54610275Ssam fflush(stdout); 54710275Ssam if (debug) { 54810275Ssam fprintf(stderr, "<--- %d ", n); 54910275Ssam _doprnt(s, &args, stderr); 55010275Ssam fprintf(stderr, "\n"); 55110275Ssam fflush(stderr); 55210275Ssam } 55310275Ssam } 55410275Ssam 55510275Ssam lreply(n, s, args) 55610275Ssam int n; 55710275Ssam char *s; 55810275Ssam { 55910275Ssam printf("%d-", n); 56010275Ssam _doprnt(s, &args, stdout); 56110275Ssam printf("\r\n"); 56210275Ssam fflush(stdout); 56310275Ssam if (debug) { 56410275Ssam fprintf(stderr, "<--- %d-", n); 56510275Ssam _doprnt(s, &args, stderr); 56610275Ssam fprintf(stderr, "\n"); 56710275Ssam } 56810275Ssam } 56910275Ssam 57010275Ssam replystr(s) 57110275Ssam char *s; 57210275Ssam { 57310275Ssam printf("%s\r\n", s); 57410275Ssam fflush(stdout); 57510275Ssam if (debug) 57610275Ssam fprintf(stderr, "<--- %s\n", s); 57710275Ssam } 57810275Ssam 57910275Ssam ack(s) 58010275Ssam char *s; 58110275Ssam { 58210275Ssam reply(200, "%s command okay.", s); 58310275Ssam } 58410275Ssam 58510275Ssam nack(s) 58610275Ssam char *s; 58710275Ssam { 58810275Ssam reply(502, "%s command not implemented.", s); 58910275Ssam } 59010275Ssam 59110275Ssam yyerror() 59210275Ssam { 59310275Ssam reply(500, "Command not understood."); 59410275Ssam } 59510275Ssam 59610275Ssam delete(name) 59710275Ssam char *name; 59810275Ssam { 59910275Ssam struct stat st; 60010275Ssam 60110275Ssam if (stat(name, &st) < 0) { 60210275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 60310275Ssam return; 60410275Ssam } 60510275Ssam if ((st.st_mode&S_IFMT) == S_IFDIR) { 60610275Ssam if (rmdir(name) < 0) { 60710275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 60810275Ssam return; 60910275Ssam } 61010275Ssam goto done; 61110275Ssam } 61210275Ssam if (unlink(name) < 0) { 61310275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 61410275Ssam return; 61510275Ssam } 61610275Ssam done: 61710275Ssam ack("DELE"); 61810275Ssam } 61910275Ssam 62010275Ssam cwd(path) 62110275Ssam char *path; 62210275Ssam { 62310275Ssam 62410275Ssam if (chdir(path) < 0) { 62510275Ssam reply(550, "%s: %s.", path, sys_errlist[errno]); 62610275Ssam return; 62710275Ssam } 62810275Ssam ack("CWD"); 62910275Ssam } 63010275Ssam 63110303Ssam makedir(name) 63210275Ssam char *name; 63310275Ssam { 63410303Ssam struct stat st; 63510303Ssam int dochown = stat(name, &st) < 0; 63610275Ssam 63710275Ssam if (mkdir(name, 0777) < 0) { 63810275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 63910275Ssam return; 64010275Ssam } 64110303Ssam if (dochown) 64210303Ssam (void) chown(name, pw->pw_uid, -1); 64310275Ssam ack("MKDIR"); 64410275Ssam } 64510275Ssam 64610303Ssam removedir(name) 64710275Ssam char *name; 64810275Ssam { 64910275Ssam 65010275Ssam if (rmdir(name) < 0) { 65110275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 65210275Ssam return; 65310275Ssam } 65410275Ssam ack("RMDIR"); 65510275Ssam } 65610275Ssam 65710303Ssam pwd() 65810275Ssam { 65910303Ssam char path[MAXPATHLEN + 1]; 66010275Ssam 66110275Ssam if (getwd(path) == NULL) { 66210275Ssam reply(451, "%s.", path); 66310275Ssam return; 66410275Ssam } 66510275Ssam reply(251, "\"%s\" is current directory.", path); 66610275Ssam } 66710275Ssam 66810275Ssam char * 66910275Ssam renamefrom(name) 67010275Ssam char *name; 67110275Ssam { 67210275Ssam struct stat st; 67310275Ssam 67410275Ssam if (stat(name, &st) < 0) { 67510275Ssam reply(550, "%s: %s.", name, sys_errlist[errno]); 67610275Ssam return ((char *)0); 67710275Ssam } 67810303Ssam reply(350, "File exists, ready for destination name"); 67910275Ssam return (name); 68010275Ssam } 68110275Ssam 68210275Ssam renamecmd(from, to) 68310275Ssam char *from, *to; 68410275Ssam { 68510275Ssam 68610275Ssam if (rename(from, to) < 0) { 68710275Ssam reply(550, "rename: %s.", sys_errlist[errno]); 68810275Ssam return; 68910275Ssam } 69010275Ssam ack("RNTO"); 69110275Ssam } 69210275Ssam 69310275Ssam dolog(sin) 69410275Ssam struct sockaddr_in *sin; 69510275Ssam { 69610275Ssam struct hostent *hp = gethostbyaddr(&sin->sin_addr, 69710275Ssam sizeof (struct in_addr), AF_INET); 69810275Ssam time_t t; 69910275Ssam 70013247Ssam if (hp) { 70113247Ssam strncpy(remotehost, hp->h_name, sizeof (remotehost)); 70213247Ssam endhostent(); 70313247Ssam } else 70413247Ssam strncpy(remotehost, inet_ntoa(sin->sin_addr), 70513247Ssam sizeof (remotehost)); 70613247Ssam if (!logging) 70713247Ssam return; 70810275Ssam t = time(0); 70911757Ssam fprintf(stderr,"FTPD: connection from %s at %s", remotehost, ctime(&t)); 71010275Ssam fflush(stderr); 71110275Ssam } 71210695Ssam 71313247Ssam #include <utmp.h> 71413247Ssam 71513247Ssam #define SCPYN(a, b) strncpy(a, b, sizeof (a)) 71613247Ssam struct utmp utmp; 71713247Ssam 71810695Ssam /* 71913247Ssam * Record login in wtmp file. 72013247Ssam */ 72113247Ssam dologin(pw) 72213247Ssam struct passwd *pw; 72313247Ssam { 72413247Ssam int wtmp; 72513247Ssam char line[32]; 72613247Ssam 72713247Ssam wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND); 72813247Ssam if (wtmp >= 0) { 72913247Ssam /* hack, but must be unique and no tty line */ 73013247Ssam sprintf(line, "ftp%d", getpid()); 73113247Ssam SCPYN(utmp.ut_line, line); 73213247Ssam SCPYN(utmp.ut_name, pw->pw_name); 73313247Ssam SCPYN(utmp.ut_host, remotehost); 73413247Ssam utmp.ut_time = time(0); 73513247Ssam (void) write(wtmp, (char *)&utmp, sizeof (utmp)); 73613247Ssam (void) close(wtmp); 73713247Ssam } 73813247Ssam } 73913247Ssam 74013247Ssam /* 74113247Ssam * Record logout in wtmp file 74213247Ssam * and exit with supplied status. 74313247Ssam */ 74413247Ssam dologout(status) 74513247Ssam int status; 74613247Ssam { 74713247Ssam int wtmp; 74813247Ssam 74913247Ssam if (!logged_in) 75013247Ssam return; 75113247Ssam seteuid(0); 75213247Ssam wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND); 75313247Ssam if (wtmp >= 0) { 75413247Ssam SCPYN(utmp.ut_name, ""); 75513247Ssam SCPYN(utmp.ut_host, ""); 75613247Ssam utmp.ut_time = time(0); 75713247Ssam (void) write(wtmp, (char *)&utmp, sizeof (utmp)); 75813247Ssam (void) close(wtmp); 75913247Ssam } 76013247Ssam exit(status); 76113247Ssam } 76213247Ssam 76313247Ssam /* 76410695Ssam * Special version of popen which avoids 76510695Ssam * call to shell. This insures noone may 76610695Ssam * create a pipe to a hidden program as a side 76710695Ssam * effect of a list or dir command. 76810695Ssam */ 76910695Ssam #define tst(a,b) (*mode == 'r'? (b) : (a)) 77010695Ssam #define RDR 0 77110695Ssam #define WTR 1 77210695Ssam static int popen_pid[5]; 77310695Ssam 77410695Ssam static char * 77510695Ssam nextarg(cpp) 77610695Ssam char *cpp; 77710695Ssam { 77810695Ssam register char *cp = cpp; 77910695Ssam 78010695Ssam if (cp == 0) 78110695Ssam return (cp); 78210695Ssam while (*cp && *cp != ' ' && *cp != '\t') 78310695Ssam cp++; 78410695Ssam if (*cp == ' ' || *cp == '\t') { 78510695Ssam *cp++ = '\0'; 78610695Ssam while (*cp == ' ' || *cp == '\t') 78710695Ssam cp++; 78810695Ssam } 78910695Ssam if (cp == cpp) 79010695Ssam return ((char *)0); 79110695Ssam return (cp); 79210695Ssam } 79310695Ssam 79410695Ssam FILE * 79510695Ssam popen(cmd, mode) 79610695Ssam char *cmd, *mode; 79710695Ssam { 79813211Sroot int p[2], ac, gac; 79910695Ssam register myside, hisside, pid; 80013211Sroot char *av[20], *gav[512]; 80110695Ssam register char *cp; 80210695Ssam 80310695Ssam if (pipe(p) < 0) 80410695Ssam return (NULL); 80510695Ssam cp = cmd, ac = 0; 80613211Sroot /* break up string into pieces */ 80710695Ssam do { 80810695Ssam av[ac++] = cp; 80910695Ssam cp = nextarg(cp); 81013211Sroot } while (cp && *cp && ac < 20); 81110695Ssam av[ac] = (char *)0; 81213211Sroot gav[0] = av[0]; 81313211Sroot /* glob each piece */ 81413211Sroot for (gac = ac = 1; av[ac] != NULL; ac++) { 81513211Sroot char **pop; 81613211Sroot extern char **glob(); 81713211Sroot 81813211Sroot pop = glob(av[ac]); 81913211Sroot if (pop) { 82013211Sroot av[ac] = (char *)pop; /* save to free later */ 82113211Sroot while (*pop && gac < 512) 82213211Sroot gav[gac++] = *pop++; 82313211Sroot } 82411757Ssam } 82513211Sroot gav[gac] = (char *)0; 82610695Ssam myside = tst(p[WTR], p[RDR]); 82710695Ssam hisside = tst(p[RDR], p[WTR]); 82810695Ssam if ((pid = fork()) == 0) { 82910695Ssam /* myside and hisside reverse roles in child */ 83010695Ssam close(myside); 83110695Ssam dup2(hisside, tst(0, 1)); 83210695Ssam close(hisside); 83313211Sroot execv(gav[0], gav); 83410695Ssam _exit(1); 83510695Ssam } 83613211Sroot for (ac = 1; av[ac] != NULL; ac++) 83713211Sroot blkfree((char **)av[ac]); 83810695Ssam if (pid == -1) 83910695Ssam return (NULL); 84010695Ssam popen_pid[myside] = pid; 84110695Ssam close(hisside); 84210695Ssam return (fdopen(myside, mode)); 84310695Ssam } 84410695Ssam 84510695Ssam pclose(ptr) 84610695Ssam FILE *ptr; 84710695Ssam { 84810695Ssam register f, r, (*hstat)(), (*istat)(), (*qstat)(); 84910695Ssam int status; 85010695Ssam 85110695Ssam f = fileno(ptr); 85210695Ssam fclose(ptr); 85310695Ssam istat = signal(SIGINT, SIG_IGN); 85410695Ssam qstat = signal(SIGQUIT, SIG_IGN); 85510695Ssam hstat = signal(SIGHUP, SIG_IGN); 85610695Ssam while ((r = wait(&status)) != popen_pid[f] && r != -1) 85710695Ssam ; 85810695Ssam if (r == -1) 85910695Ssam status = -1; 86010695Ssam signal(SIGINT, istat); 86110695Ssam signal(SIGQUIT, qstat); 86210695Ssam signal(SIGHUP, hstat); 86310695Ssam return (status); 86410695Ssam } 86510695Ssam 86610695Ssam /* 86710695Ssam * Check user requesting login priviledges. 86810695Ssam * Disallow anyone mentioned in the file FTPUSERS 86910695Ssam * to allow people such as uucp to be avoided. 87010695Ssam */ 87110695Ssam checkuser(name) 87210695Ssam register char *name; 87310695Ssam { 87410695Ssam char line[BUFSIZ], *index(); 87510695Ssam FILE *fd; 87610695Ssam int found = 0; 87710695Ssam 87810695Ssam fd = fopen(FTPUSERS, "r"); 87910695Ssam if (fd == NULL) 88010695Ssam return (1); 88110695Ssam while (fgets(line, sizeof (line), fd) != NULL) { 88210695Ssam register char *cp = index(line, '\n'); 88310695Ssam 88410695Ssam if (cp) 88510695Ssam *cp = '\0'; 88610695Ssam if (strcmp(line, name) == 0) { 88710695Ssam found++; 88810695Ssam break; 88910695Ssam } 89010695Ssam } 89110695Ssam fclose(fd); 89210695Ssam return (!found); 89310695Ssam } 894