121183Sdist /* 221183Sdist * Copyright (c) 1983 Regents of the University of California. 333822Sbostic * All rights reserved. 433822Sbostic * 542673Sbostic * %sccs.include.redist.c% 621183Sdist */ 721183Sdist 813609Ssam #ifndef lint 921183Sdist char copyright[] = 1021183Sdist "@(#) Copyright (c) 1983 Regents of the University of California.\n\ 1121183Sdist All rights reserved.\n"; 1233822Sbostic #endif /* not lint */ 137772Ssam 1421183Sdist #ifndef lint 15*46687Sbostic static char sccsid[] = "@(#)tftpd.c 5.13 (Berkeley) 02/26/91"; 1633822Sbostic #endif /* not lint */ 1721183Sdist 187772Ssam /* 197772Ssam * Trivial file transfer protocol server. 2026108Sminshall * 2126108Sminshall * This version includes many modifications by Jim Guyton <guyton@rand-unix> 227772Ssam */ 2326108Sminshall 247772Ssam #include <sys/types.h> 259220Ssam #include <sys/ioctl.h> 2613609Ssam #include <sys/stat.h> 27*46687Sbostic #include <signal.h> 28*46687Sbostic #include <fcntl.h> 299220Ssam 30*46687Sbostic #include <sys/socket.h> 319220Ssam #include <netinet/in.h> 3212217Ssam #include <arpa/tftp.h> 33*46687Sbostic #include <netdb.h> 3412217Ssam 3542413Sbostic #include <setjmp.h> 36*46687Sbostic #include <syslog.h> 377772Ssam #include <stdio.h> 387772Ssam #include <errno.h> 397772Ssam #include <ctype.h> 4042413Sbostic #include <string.h> 41*46687Sbostic #include <stdlib.h> 429220Ssam 4313020Ssam #define TIMEOUT 5 4413020Ssam 457772Ssam extern int errno; 468385Ssam struct sockaddr_in sin = { AF_INET }; 4716372Skarels int peer; 4813020Ssam int rexmtval = TIMEOUT; 4913020Ssam int maxtimeout = 5*TIMEOUT; 5026108Sminshall 5126108Sminshall #define PKTSIZE SEGSIZE+4 5226108Sminshall char buf[PKTSIZE]; 5326108Sminshall char ackbuf[PKTSIZE]; 5416372Skarels struct sockaddr_in from; 5516372Skarels int fromlen; 567772Ssam 5735783Stef #define MAXARG 4 5835783Stef char *dirs[MAXARG+1]; 5935783Stef 6035783Stef main(ac, av) 6135783Stef char **av; 627772Ssam { 637772Ssam register struct tftphdr *tp; 6435783Stef register int n = 0; 6528070Sminshall int on = 1; 667772Ssam 6735783Stef ac--; av++; 6835783Stef while (ac-- > 0 && n < MAXARG) 6935783Stef dirs[n++] = *av++; 7024852Seric openlog("tftpd", LOG_PID, LOG_DAEMON); 7128070Sminshall if (ioctl(0, FIONBIO, &on) < 0) { 7228070Sminshall syslog(LOG_ERR, "ioctl(FIONBIO): %m\n"); 7328070Sminshall exit(1); 7428070Sminshall } 7516372Skarels fromlen = sizeof (from); 7616372Skarels n = recvfrom(0, buf, sizeof (buf), 0, 77*46687Sbostic (struct sockaddr *)&from, &fromlen); 7816372Skarels if (n < 0) { 7928070Sminshall syslog(LOG_ERR, "recvfrom: %m\n"); 808385Ssam exit(1); 818385Ssam } 8228070Sminshall /* 8328070Sminshall * Now that we have read the message out of the UDP 8428070Sminshall * socket, we fork and exit. Thus, inetd will go back 8528070Sminshall * to listening to the tftp port, and the next request 8628070Sminshall * to come in will start up a new instance of tftpd. 8728070Sminshall * 8828070Sminshall * We do this so that inetd can run tftpd in "wait" mode. 8928070Sminshall * The problem with tftpd running in "nowait" mode is that 9028070Sminshall * inetd may get one or more successful "selects" on the 9128070Sminshall * tftp port before we do our receive, so more than one 9228070Sminshall * instance of tftpd may be started up. Worse, if tftpd 9328070Sminshall * break before doing the above "recvfrom", inetd would 9428070Sminshall * spawn endless instances, clogging the system. 9528070Sminshall */ 9628070Sminshall { 9728070Sminshall int pid; 9828070Sminshall int i, j; 9928070Sminshall 10028070Sminshall for (i = 1; i < 20; i++) { 10128070Sminshall pid = fork(); 10228070Sminshall if (pid < 0) { 10328070Sminshall sleep(i); 10428070Sminshall /* 10528070Sminshall * flush out to most recently sent request. 10628070Sminshall * 10728070Sminshall * This may drop some request, but those 10828070Sminshall * will be resent by the clients when 10928070Sminshall * they timeout. The positive effect of 11028070Sminshall * this flush is to (try to) prevent more 11128070Sminshall * than one tftpd being started up to service 11228070Sminshall * a single request from a single client. 11328070Sminshall */ 11428070Sminshall j = sizeof from; 11528070Sminshall i = recvfrom(0, buf, sizeof (buf), 0, 116*46687Sbostic (struct sockaddr *)&from, &j); 11728070Sminshall if (i > 0) { 11828070Sminshall n = i; 11928070Sminshall fromlen = j; 12028070Sminshall } 12128070Sminshall } else { 12228070Sminshall break; 12328070Sminshall } 12428070Sminshall } 12528070Sminshall if (pid < 0) { 12628070Sminshall syslog(LOG_ERR, "fork: %m\n"); 12728070Sminshall exit(1); 12828070Sminshall } else if (pid != 0) { 12928070Sminshall exit(0); 13028070Sminshall } 13128070Sminshall } 13216372Skarels from.sin_family = AF_INET; 13316372Skarels alarm(0); 13416372Skarels close(0); 13516372Skarels close(1); 13616372Skarels peer = socket(AF_INET, SOCK_DGRAM, 0); 13716372Skarels if (peer < 0) { 13828070Sminshall syslog(LOG_ERR, "socket: %m\n"); 13916372Skarels exit(1); 1407772Ssam } 141*46687Sbostic if (bind(peer, (struct sockaddr *)&sin, sizeof (sin)) < 0) { 14228070Sminshall syslog(LOG_ERR, "bind: %m\n"); 14316372Skarels exit(1); 14416372Skarels } 145*46687Sbostic if (connect(peer, (struct sockaddr *)&from, sizeof(from)) < 0) { 14628070Sminshall syslog(LOG_ERR, "connect: %m\n"); 14716372Skarels exit(1); 1487772Ssam } 14916372Skarels tp = (struct tftphdr *)buf; 15016372Skarels tp->th_opcode = ntohs(tp->th_opcode); 15116372Skarels if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 15216372Skarels tftp(tp, n); 15316372Skarels exit(1); 1547772Ssam } 1557772Ssam 1567772Ssam int validate_access(); 1577772Ssam int sendfile(), recvfile(); 1587772Ssam 1597772Ssam struct formats { 1607772Ssam char *f_mode; 1617772Ssam int (*f_validate)(); 1627772Ssam int (*f_send)(); 1637772Ssam int (*f_recv)(); 16426108Sminshall int f_convert; 1657772Ssam } formats[] = { 16626108Sminshall { "netascii", validate_access, sendfile, recvfile, 1 }, 16726108Sminshall { "octet", validate_access, sendfile, recvfile, 0 }, 1687772Ssam #ifdef notdef 16926108Sminshall { "mail", validate_user, sendmail, recvmail, 1 }, 1707772Ssam #endif 1717772Ssam { 0 } 1727772Ssam }; 1737772Ssam 1747772Ssam /* 1757772Ssam * Handle initial connection protocol. 1767772Ssam */ 17716372Skarels tftp(tp, size) 1787772Ssam struct tftphdr *tp; 1797772Ssam int size; 1807772Ssam { 1817772Ssam register char *cp; 1827772Ssam int first = 1, ecode; 1837772Ssam register struct formats *pf; 1847772Ssam char *filename, *mode; 1857772Ssam 1867772Ssam filename = cp = tp->th_stuff; 1877772Ssam again: 1887772Ssam while (cp < buf + size) { 1897772Ssam if (*cp == '\0') 1907772Ssam break; 1917772Ssam cp++; 1927772Ssam } 1937772Ssam if (*cp != '\0') { 1947772Ssam nak(EBADOP); 1957772Ssam exit(1); 1967772Ssam } 1977772Ssam if (first) { 1987772Ssam mode = ++cp; 1997772Ssam first = 0; 2007772Ssam goto again; 2017772Ssam } 2027772Ssam for (cp = mode; *cp; cp++) 2037772Ssam if (isupper(*cp)) 2047772Ssam *cp = tolower(*cp); 2057772Ssam for (pf = formats; pf->f_mode; pf++) 2067772Ssam if (strcmp(pf->f_mode, mode) == 0) 2077772Ssam break; 2087772Ssam if (pf->f_mode == 0) { 2097772Ssam nak(EBADOP); 2107772Ssam exit(1); 2117772Ssam } 21216372Skarels ecode = (*pf->f_validate)(filename, tp->th_opcode); 2137772Ssam if (ecode) { 2147772Ssam nak(ecode); 2157772Ssam exit(1); 2167772Ssam } 2177772Ssam if (tp->th_opcode == WRQ) 2187772Ssam (*pf->f_recv)(pf); 2197772Ssam else 2207772Ssam (*pf->f_send)(pf); 2217772Ssam exit(0); 2227772Ssam } 2237772Ssam 22416372Skarels 22526108Sminshall FILE *file; 22626108Sminshall 2277772Ssam /* 2287772Ssam * Validate file access. Since we 2297772Ssam * have no uid or gid, for now require 2307772Ssam * file to exist and be publicly 2317772Ssam * readable/writable. 23235783Stef * If we were invoked with arguments 23335783Stef * from inetd then the file must also be 23435783Stef * in one of the given directory prefixes. 2357772Ssam * Note also, full path name must be 2367772Ssam * given as we have no login directory. 2377772Ssam */ 23826108Sminshall validate_access(filename, mode) 23926108Sminshall char *filename; 2407772Ssam int mode; 2417772Ssam { 2427772Ssam struct stat stbuf; 24326108Sminshall int fd; 24441146Stef char *cp, **dirp; 2457772Ssam 24626108Sminshall if (*filename != '/') 2477772Ssam return (EACCESS); 24841146Stef /* 24941146Stef * prevent tricksters from getting around the directory restrictions 25041146Stef */ 25141146Stef for (cp = filename + 1; *cp; cp++) 25241146Stef if(*cp == '.' && strncmp(cp-1, "/../", 4) == 0) 25341146Stef return(EACCESS); 25441146Stef for (dirp = dirs; *dirp; dirp++) 25535783Stef if (strncmp(filename, *dirp, strlen(*dirp)) == 0) 25635783Stef break; 25735783Stef if (*dirp==0 && dirp!=dirs) 25835783Stef return (EACCESS); 25926108Sminshall if (stat(filename, &stbuf) < 0) 2607772Ssam return (errno == ENOENT ? ENOTFOUND : EACCESS); 2617772Ssam if (mode == RRQ) { 2627772Ssam if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) 2637772Ssam return (EACCESS); 2647772Ssam } else { 2657772Ssam if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) 2667772Ssam return (EACCESS); 2677772Ssam } 26826108Sminshall fd = open(filename, mode == RRQ ? 0 : 1); 2697772Ssam if (fd < 0) 2707772Ssam return (errno + 100); 27126108Sminshall file = fdopen(fd, (mode == RRQ)? "r":"w"); 27226108Sminshall if (file == NULL) { 27326108Sminshall return errno+100; 27426108Sminshall } 2757772Ssam return (0); 2767772Ssam } 2777772Ssam 27813020Ssam int timeout; 27913020Ssam jmp_buf timeoutbuf; 2807772Ssam 281*46687Sbostic void 2827772Ssam timer() 2837772Ssam { 28413020Ssam 28513020Ssam timeout += rexmtval; 28613020Ssam if (timeout >= maxtimeout) 2877772Ssam exit(1); 28813020Ssam longjmp(timeoutbuf, 1); 2897772Ssam } 2907772Ssam 2917772Ssam /* 2927772Ssam * Send the requested file. 2937772Ssam */ 2947772Ssam sendfile(pf) 29526108Sminshall struct formats *pf; 2967772Ssam { 29726108Sminshall struct tftphdr *dp, *r_init(); 29826108Sminshall register struct tftphdr *ap; /* ack packet */ 2997772Ssam register int block = 1, size, n; 3007772Ssam 30113020Ssam signal(SIGALRM, timer); 30226108Sminshall dp = r_init(); 30326108Sminshall ap = (struct tftphdr *)ackbuf; 3047772Ssam do { 30526108Sminshall size = readit(file, &dp, pf->f_convert); 3067772Ssam if (size < 0) { 3077772Ssam nak(errno + 100); 30826108Sminshall goto abort; 3097772Ssam } 31026108Sminshall dp->th_opcode = htons((u_short)DATA); 31126108Sminshall dp->th_block = htons((u_short)block); 3127772Ssam timeout = 0; 31313020Ssam (void) setjmp(timeoutbuf); 31426108Sminshall 31526109Sminshall send_data: 31626108Sminshall if (send(peer, dp, size + 4, 0) != size + 4) { 31728070Sminshall syslog(LOG_ERR, "tftpd: write: %m\n"); 31826108Sminshall goto abort; 3197772Ssam } 32026108Sminshall read_ahead(file, pf->f_convert); 32126109Sminshall for ( ; ; ) { 32226108Sminshall alarm(rexmtval); /* read the ack */ 32326108Sminshall n = recv(peer, ackbuf, sizeof (ackbuf), 0); 3247772Ssam alarm(0); 32513020Ssam if (n < 0) { 32628070Sminshall syslog(LOG_ERR, "tftpd: read: %m\n"); 32726108Sminshall goto abort; 32813020Ssam } 32926108Sminshall ap->th_opcode = ntohs((u_short)ap->th_opcode); 33026108Sminshall ap->th_block = ntohs((u_short)ap->th_block); 33126108Sminshall 33226108Sminshall if (ap->th_opcode == ERROR) 33326108Sminshall goto abort; 33426109Sminshall 33526109Sminshall if (ap->th_opcode == ACK) { 33626109Sminshall if (ap->th_block == block) { 33726109Sminshall break; 33826109Sminshall } 33926115Sminshall /* Re-synchronize with the other side */ 34026115Sminshall (void) synchnet(peer); 34126109Sminshall if (ap->th_block == (block -1)) { 34226109Sminshall goto send_data; 34326109Sminshall } 34426109Sminshall } 34526108Sminshall 34626109Sminshall } 3477772Ssam block++; 3487772Ssam } while (size == SEGSIZE); 34926108Sminshall abort: 35026108Sminshall (void) fclose(file); 3517772Ssam } 3527772Ssam 353*46687Sbostic void 35426108Sminshall justquit() 35526108Sminshall { 35626108Sminshall exit(0); 35726108Sminshall } 35826108Sminshall 35926108Sminshall 3607772Ssam /* 3617772Ssam * Receive a file. 3627772Ssam */ 3637772Ssam recvfile(pf) 36426108Sminshall struct formats *pf; 3657772Ssam { 36626108Sminshall struct tftphdr *dp, *w_init(); 36726108Sminshall register struct tftphdr *ap; /* ack buffer */ 3687772Ssam register int block = 0, n, size; 3697772Ssam 37013020Ssam signal(SIGALRM, timer); 37126108Sminshall dp = w_init(); 37226108Sminshall ap = (struct tftphdr *)ackbuf; 3737772Ssam do { 3747772Ssam timeout = 0; 37526108Sminshall ap->th_opcode = htons((u_short)ACK); 37626108Sminshall ap->th_block = htons((u_short)block); 3777772Ssam block++; 37813020Ssam (void) setjmp(timeoutbuf); 37926108Sminshall send_ack: 38026108Sminshall if (send(peer, ackbuf, 4, 0) != 4) { 38128070Sminshall syslog(LOG_ERR, "tftpd: write: %m\n"); 38213020Ssam goto abort; 3837772Ssam } 38426108Sminshall write_behind(file, pf->f_convert); 38526108Sminshall for ( ; ; ) { 38613020Ssam alarm(rexmtval); 38726108Sminshall n = recv(peer, dp, PKTSIZE, 0); 3887772Ssam alarm(0); 38926108Sminshall if (n < 0) { /* really? */ 39028070Sminshall syslog(LOG_ERR, "tftpd: read: %m\n"); 39113020Ssam goto abort; 39213020Ssam } 39326108Sminshall dp->th_opcode = ntohs((u_short)dp->th_opcode); 39426108Sminshall dp->th_block = ntohs((u_short)dp->th_block); 39526108Sminshall if (dp->th_opcode == ERROR) 39613020Ssam goto abort; 39726108Sminshall if (dp->th_opcode == DATA) { 39826108Sminshall if (dp->th_block == block) { 39926108Sminshall break; /* normal */ 40026108Sminshall } 40126115Sminshall /* Re-synchronize with the other side */ 40226115Sminshall (void) synchnet(peer); 40326108Sminshall if (dp->th_block == (block-1)) 40426108Sminshall goto send_ack; /* rexmit */ 40526108Sminshall } 40626108Sminshall } 40726108Sminshall /* size = write(file, dp->th_data, n - 4); */ 40826108Sminshall size = writeit(file, &dp, n - 4, pf->f_convert); 40926108Sminshall if (size != (n-4)) { /* ahem */ 41026108Sminshall if (size < 0) nak(errno + 100); 41126108Sminshall else nak(ENOSPACE); 41213020Ssam goto abort; 4137772Ssam } 4147772Ssam } while (size == SEGSIZE); 41526108Sminshall write_behind(file, pf->f_convert); 41626108Sminshall (void) fclose(file); /* close data file */ 41726108Sminshall 41826108Sminshall ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 41926108Sminshall ap->th_block = htons((u_short)(block)); 42026108Sminshall (void) send(peer, ackbuf, 4, 0); 42126108Sminshall 42226108Sminshall signal(SIGALRM, justquit); /* just quit on timeout */ 42326108Sminshall alarm(rexmtval); 42426108Sminshall n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 42526108Sminshall alarm(0); 42626108Sminshall if (n >= 4 && /* if read some data */ 42726108Sminshall dp->th_opcode == DATA && /* and got a data block */ 42826108Sminshall block == dp->th_block) { /* then my last ack was lost */ 42926108Sminshall (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 43026108Sminshall } 43113020Ssam abort: 43226108Sminshall return; 4337772Ssam } 4347772Ssam 4357772Ssam struct errmsg { 4367772Ssam int e_code; 4377772Ssam char *e_msg; 4387772Ssam } errmsgs[] = { 4397772Ssam { EUNDEF, "Undefined error code" }, 4407772Ssam { ENOTFOUND, "File not found" }, 4417772Ssam { EACCESS, "Access violation" }, 4427772Ssam { ENOSPACE, "Disk full or allocation exceeded" }, 4437772Ssam { EBADOP, "Illegal TFTP operation" }, 4447772Ssam { EBADID, "Unknown transfer ID" }, 4457772Ssam { EEXISTS, "File already exists" }, 4467772Ssam { ENOUSER, "No such user" }, 4477772Ssam { -1, 0 } 4487772Ssam }; 4497772Ssam 4507772Ssam /* 4517772Ssam * Send a nak packet (error message). 4527772Ssam * Error code passed in is one of the 4537772Ssam * standard TFTP codes, or a UNIX errno 4547772Ssam * offset by 100. 4557772Ssam */ 4567772Ssam nak(error) 4577772Ssam int error; 4587772Ssam { 4597772Ssam register struct tftphdr *tp; 4607772Ssam int length; 4617772Ssam register struct errmsg *pe; 4627772Ssam 4637772Ssam tp = (struct tftphdr *)buf; 4647772Ssam tp->th_opcode = htons((u_short)ERROR); 4657772Ssam tp->th_code = htons((u_short)error); 4667772Ssam for (pe = errmsgs; pe->e_code >= 0; pe++) 4677772Ssam if (pe->e_code == error) 4687772Ssam break; 46926108Sminshall if (pe->e_code < 0) { 47042413Sbostic pe->e_msg = strerror(error - 100); 47126108Sminshall tp->th_code = EUNDEF; /* set 'undef' errorcode */ 47226108Sminshall } 4737772Ssam strcpy(tp->th_msg, pe->e_msg); 4747772Ssam length = strlen(pe->e_msg); 4757772Ssam tp->th_msg[length] = '\0'; 4767772Ssam length += 5; 47716372Skarels if (send(peer, buf, length, 0) != length) 47828070Sminshall syslog(LOG_ERR, "nak: %m\n"); 4797772Ssam } 480