121183Sdist /* 221183Sdist * Copyright (c) 1983 Regents of the University of California. 333822Sbostic * All rights reserved. 433822Sbostic * 5*42673Sbostic * %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*42673Sbostic static char sccsid[] = "@(#)tftpd.c 5.12 (Berkeley) 06/01/90"; 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> 257772Ssam #include <sys/socket.h> 269220Ssam #include <sys/ioctl.h> 2713609Ssam #include <sys/wait.h> 2813609Ssam #include <sys/stat.h> 2942413Sbostic #include <sys/signal.h> 309220Ssam 319220Ssam #include <netinet/in.h> 329220Ssam 3312217Ssam #include <arpa/tftp.h> 3412217Ssam 3542413Sbostic #include <netdb.h> 3642413Sbostic #include <setjmp.h> 377772Ssam #include <stdio.h> 387772Ssam #include <errno.h> 397772Ssam #include <ctype.h> 4017188Sralph #include <syslog.h> 4142413Sbostic #include <string.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, 7716372Skarels (caddr_t)&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, 11628070Sminshall (caddr_t)&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 } 14116372Skarels if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) { 14228070Sminshall syslog(LOG_ERR, "bind: %m\n"); 14316372Skarels exit(1); 14416372Skarels } 14516372Skarels if (connect(peer, (caddr_t)&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 2817772Ssam timer() 2827772Ssam { 28313020Ssam 28413020Ssam timeout += rexmtval; 28513020Ssam if (timeout >= maxtimeout) 2867772Ssam exit(1); 28713020Ssam longjmp(timeoutbuf, 1); 2887772Ssam } 2897772Ssam 2907772Ssam /* 2917772Ssam * Send the requested file. 2927772Ssam */ 2937772Ssam sendfile(pf) 29426108Sminshall struct formats *pf; 2957772Ssam { 29626108Sminshall struct tftphdr *dp, *r_init(); 29726108Sminshall register struct tftphdr *ap; /* ack packet */ 2987772Ssam register int block = 1, size, n; 2997772Ssam 30013020Ssam signal(SIGALRM, timer); 30126108Sminshall dp = r_init(); 30226108Sminshall ap = (struct tftphdr *)ackbuf; 3037772Ssam do { 30426108Sminshall size = readit(file, &dp, pf->f_convert); 3057772Ssam if (size < 0) { 3067772Ssam nak(errno + 100); 30726108Sminshall goto abort; 3087772Ssam } 30926108Sminshall dp->th_opcode = htons((u_short)DATA); 31026108Sminshall dp->th_block = htons((u_short)block); 3117772Ssam timeout = 0; 31213020Ssam (void) setjmp(timeoutbuf); 31326108Sminshall 31426109Sminshall send_data: 31526108Sminshall if (send(peer, dp, size + 4, 0) != size + 4) { 31628070Sminshall syslog(LOG_ERR, "tftpd: write: %m\n"); 31726108Sminshall goto abort; 3187772Ssam } 31926108Sminshall read_ahead(file, pf->f_convert); 32026109Sminshall for ( ; ; ) { 32126108Sminshall alarm(rexmtval); /* read the ack */ 32226108Sminshall n = recv(peer, ackbuf, sizeof (ackbuf), 0); 3237772Ssam alarm(0); 32413020Ssam if (n < 0) { 32528070Sminshall syslog(LOG_ERR, "tftpd: read: %m\n"); 32626108Sminshall goto abort; 32713020Ssam } 32826108Sminshall ap->th_opcode = ntohs((u_short)ap->th_opcode); 32926108Sminshall ap->th_block = ntohs((u_short)ap->th_block); 33026108Sminshall 33126108Sminshall if (ap->th_opcode == ERROR) 33226108Sminshall goto abort; 33326109Sminshall 33426109Sminshall if (ap->th_opcode == ACK) { 33526109Sminshall if (ap->th_block == block) { 33626109Sminshall break; 33726109Sminshall } 33826115Sminshall /* Re-synchronize with the other side */ 33926115Sminshall (void) synchnet(peer); 34026109Sminshall if (ap->th_block == (block -1)) { 34126109Sminshall goto send_data; 34226109Sminshall } 34326109Sminshall } 34426108Sminshall 34526109Sminshall } 3467772Ssam block++; 3477772Ssam } while (size == SEGSIZE); 34826108Sminshall abort: 34926108Sminshall (void) fclose(file); 3507772Ssam } 3517772Ssam 35226108Sminshall justquit() 35326108Sminshall { 35426108Sminshall exit(0); 35526108Sminshall } 35626108Sminshall 35726108Sminshall 3587772Ssam /* 3597772Ssam * Receive a file. 3607772Ssam */ 3617772Ssam recvfile(pf) 36226108Sminshall struct formats *pf; 3637772Ssam { 36426108Sminshall struct tftphdr *dp, *w_init(); 36526108Sminshall register struct tftphdr *ap; /* ack buffer */ 3667772Ssam register int block = 0, n, size; 3677772Ssam 36813020Ssam signal(SIGALRM, timer); 36926108Sminshall dp = w_init(); 37026108Sminshall ap = (struct tftphdr *)ackbuf; 3717772Ssam do { 3727772Ssam timeout = 0; 37326108Sminshall ap->th_opcode = htons((u_short)ACK); 37426108Sminshall ap->th_block = htons((u_short)block); 3757772Ssam block++; 37613020Ssam (void) setjmp(timeoutbuf); 37726108Sminshall send_ack: 37826108Sminshall if (send(peer, ackbuf, 4, 0) != 4) { 37928070Sminshall syslog(LOG_ERR, "tftpd: write: %m\n"); 38013020Ssam goto abort; 3817772Ssam } 38226108Sminshall write_behind(file, pf->f_convert); 38326108Sminshall for ( ; ; ) { 38413020Ssam alarm(rexmtval); 38526108Sminshall n = recv(peer, dp, PKTSIZE, 0); 3867772Ssam alarm(0); 38726108Sminshall if (n < 0) { /* really? */ 38828070Sminshall syslog(LOG_ERR, "tftpd: read: %m\n"); 38913020Ssam goto abort; 39013020Ssam } 39126108Sminshall dp->th_opcode = ntohs((u_short)dp->th_opcode); 39226108Sminshall dp->th_block = ntohs((u_short)dp->th_block); 39326108Sminshall if (dp->th_opcode == ERROR) 39413020Ssam goto abort; 39526108Sminshall if (dp->th_opcode == DATA) { 39626108Sminshall if (dp->th_block == block) { 39726108Sminshall break; /* normal */ 39826108Sminshall } 39926115Sminshall /* Re-synchronize with the other side */ 40026115Sminshall (void) synchnet(peer); 40126108Sminshall if (dp->th_block == (block-1)) 40226108Sminshall goto send_ack; /* rexmit */ 40326108Sminshall } 40426108Sminshall } 40526108Sminshall /* size = write(file, dp->th_data, n - 4); */ 40626108Sminshall size = writeit(file, &dp, n - 4, pf->f_convert); 40726108Sminshall if (size != (n-4)) { /* ahem */ 40826108Sminshall if (size < 0) nak(errno + 100); 40926108Sminshall else nak(ENOSPACE); 41013020Ssam goto abort; 4117772Ssam } 4127772Ssam } while (size == SEGSIZE); 41326108Sminshall write_behind(file, pf->f_convert); 41426108Sminshall (void) fclose(file); /* close data file */ 41526108Sminshall 41626108Sminshall ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 41726108Sminshall ap->th_block = htons((u_short)(block)); 41826108Sminshall (void) send(peer, ackbuf, 4, 0); 41926108Sminshall 42026108Sminshall signal(SIGALRM, justquit); /* just quit on timeout */ 42126108Sminshall alarm(rexmtval); 42226108Sminshall n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 42326108Sminshall alarm(0); 42426108Sminshall if (n >= 4 && /* if read some data */ 42526108Sminshall dp->th_opcode == DATA && /* and got a data block */ 42626108Sminshall block == dp->th_block) { /* then my last ack was lost */ 42726108Sminshall (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 42826108Sminshall } 42913020Ssam abort: 43026108Sminshall return; 4317772Ssam } 4327772Ssam 4337772Ssam struct errmsg { 4347772Ssam int e_code; 4357772Ssam char *e_msg; 4367772Ssam } errmsgs[] = { 4377772Ssam { EUNDEF, "Undefined error code" }, 4387772Ssam { ENOTFOUND, "File not found" }, 4397772Ssam { EACCESS, "Access violation" }, 4407772Ssam { ENOSPACE, "Disk full or allocation exceeded" }, 4417772Ssam { EBADOP, "Illegal TFTP operation" }, 4427772Ssam { EBADID, "Unknown transfer ID" }, 4437772Ssam { EEXISTS, "File already exists" }, 4447772Ssam { ENOUSER, "No such user" }, 4457772Ssam { -1, 0 } 4467772Ssam }; 4477772Ssam 4487772Ssam /* 4497772Ssam * Send a nak packet (error message). 4507772Ssam * Error code passed in is one of the 4517772Ssam * standard TFTP codes, or a UNIX errno 4527772Ssam * offset by 100. 4537772Ssam */ 4547772Ssam nak(error) 4557772Ssam int error; 4567772Ssam { 4577772Ssam register struct tftphdr *tp; 4587772Ssam int length; 4597772Ssam register struct errmsg *pe; 4607772Ssam 4617772Ssam tp = (struct tftphdr *)buf; 4627772Ssam tp->th_opcode = htons((u_short)ERROR); 4637772Ssam tp->th_code = htons((u_short)error); 4647772Ssam for (pe = errmsgs; pe->e_code >= 0; pe++) 4657772Ssam if (pe->e_code == error) 4667772Ssam break; 46726108Sminshall if (pe->e_code < 0) { 46842413Sbostic pe->e_msg = strerror(error - 100); 46926108Sminshall tp->th_code = EUNDEF; /* set 'undef' errorcode */ 47026108Sminshall } 4717772Ssam strcpy(tp->th_msg, pe->e_msg); 4727772Ssam length = strlen(pe->e_msg); 4737772Ssam tp->th_msg[length] = '\0'; 4747772Ssam length += 5; 47516372Skarels if (send(peer, buf, length, 0) != length) 47628070Sminshall syslog(LOG_ERR, "nak: %m\n"); 4777772Ssam } 478