121183Sdist /* 221183Sdist * Copyright (c) 1983 Regents of the University of California. 333822Sbostic * All rights reserved. 433822Sbostic * 533822Sbostic * Redistribution and use in source and binary forms are permitted 634774Sbostic * provided that the above copyright notice and this paragraph are 734774Sbostic * duplicated in all such forms and that any documentation, 834774Sbostic * advertising materials, and other materials related to such 934774Sbostic * distribution and use acknowledge that the software was developed 1034774Sbostic * by the University of California, Berkeley. The name of the 1134774Sbostic * University may not be used to endorse or promote products derived 1234774Sbostic * from this software without specific prior written permission. 1334774Sbostic * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 1434774Sbostic * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 1534774Sbostic * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 1621183Sdist */ 1721183Sdist 1813609Ssam #ifndef lint 1921183Sdist char copyright[] = 2021183Sdist "@(#) Copyright (c) 1983 Regents of the University of California.\n\ 2121183Sdist All rights reserved.\n"; 2233822Sbostic #endif /* not lint */ 237772Ssam 2421183Sdist #ifndef lint 25*42413Sbostic static char sccsid[] = "@(#)tftpd.c 5.11 (Berkeley) 05/28/90"; 2633822Sbostic #endif /* not lint */ 2721183Sdist 287772Ssam /* 297772Ssam * Trivial file transfer protocol server. 3026108Sminshall * 3126108Sminshall * This version includes many modifications by Jim Guyton <guyton@rand-unix> 327772Ssam */ 3326108Sminshall 347772Ssam #include <sys/types.h> 357772Ssam #include <sys/socket.h> 369220Ssam #include <sys/ioctl.h> 3713609Ssam #include <sys/wait.h> 3813609Ssam #include <sys/stat.h> 39*42413Sbostic #include <sys/signal.h> 409220Ssam 419220Ssam #include <netinet/in.h> 429220Ssam 4312217Ssam #include <arpa/tftp.h> 4412217Ssam 45*42413Sbostic #include <netdb.h> 46*42413Sbostic #include <setjmp.h> 477772Ssam #include <stdio.h> 487772Ssam #include <errno.h> 497772Ssam #include <ctype.h> 5017188Sralph #include <syslog.h> 51*42413Sbostic #include <string.h> 529220Ssam 5313020Ssam #define TIMEOUT 5 5413020Ssam 557772Ssam extern int errno; 568385Ssam struct sockaddr_in sin = { AF_INET }; 5716372Skarels int peer; 5813020Ssam int rexmtval = TIMEOUT; 5913020Ssam int maxtimeout = 5*TIMEOUT; 6026108Sminshall 6126108Sminshall #define PKTSIZE SEGSIZE+4 6226108Sminshall char buf[PKTSIZE]; 6326108Sminshall char ackbuf[PKTSIZE]; 6416372Skarels struct sockaddr_in from; 6516372Skarels int fromlen; 667772Ssam 6735783Stef #define MAXARG 4 6835783Stef char *dirs[MAXARG+1]; 6935783Stef 7035783Stef main(ac, av) 7135783Stef char **av; 727772Ssam { 737772Ssam register struct tftphdr *tp; 7435783Stef register int n = 0; 7528070Sminshall int on = 1; 767772Ssam 7735783Stef ac--; av++; 7835783Stef while (ac-- > 0 && n < MAXARG) 7935783Stef dirs[n++] = *av++; 8024852Seric openlog("tftpd", LOG_PID, LOG_DAEMON); 8128070Sminshall if (ioctl(0, FIONBIO, &on) < 0) { 8228070Sminshall syslog(LOG_ERR, "ioctl(FIONBIO): %m\n"); 8328070Sminshall exit(1); 8428070Sminshall } 8516372Skarels fromlen = sizeof (from); 8616372Skarels n = recvfrom(0, buf, sizeof (buf), 0, 8716372Skarels (caddr_t)&from, &fromlen); 8816372Skarels if (n < 0) { 8928070Sminshall syslog(LOG_ERR, "recvfrom: %m\n"); 908385Ssam exit(1); 918385Ssam } 9228070Sminshall /* 9328070Sminshall * Now that we have read the message out of the UDP 9428070Sminshall * socket, we fork and exit. Thus, inetd will go back 9528070Sminshall * to listening to the tftp port, and the next request 9628070Sminshall * to come in will start up a new instance of tftpd. 9728070Sminshall * 9828070Sminshall * We do this so that inetd can run tftpd in "wait" mode. 9928070Sminshall * The problem with tftpd running in "nowait" mode is that 10028070Sminshall * inetd may get one or more successful "selects" on the 10128070Sminshall * tftp port before we do our receive, so more than one 10228070Sminshall * instance of tftpd may be started up. Worse, if tftpd 10328070Sminshall * break before doing the above "recvfrom", inetd would 10428070Sminshall * spawn endless instances, clogging the system. 10528070Sminshall */ 10628070Sminshall { 10728070Sminshall int pid; 10828070Sminshall int i, j; 10928070Sminshall 11028070Sminshall for (i = 1; i < 20; i++) { 11128070Sminshall pid = fork(); 11228070Sminshall if (pid < 0) { 11328070Sminshall sleep(i); 11428070Sminshall /* 11528070Sminshall * flush out to most recently sent request. 11628070Sminshall * 11728070Sminshall * This may drop some request, but those 11828070Sminshall * will be resent by the clients when 11928070Sminshall * they timeout. The positive effect of 12028070Sminshall * this flush is to (try to) prevent more 12128070Sminshall * than one tftpd being started up to service 12228070Sminshall * a single request from a single client. 12328070Sminshall */ 12428070Sminshall j = sizeof from; 12528070Sminshall i = recvfrom(0, buf, sizeof (buf), 0, 12628070Sminshall (caddr_t)&from, &j); 12728070Sminshall if (i > 0) { 12828070Sminshall n = i; 12928070Sminshall fromlen = j; 13028070Sminshall } 13128070Sminshall } else { 13228070Sminshall break; 13328070Sminshall } 13428070Sminshall } 13528070Sminshall if (pid < 0) { 13628070Sminshall syslog(LOG_ERR, "fork: %m\n"); 13728070Sminshall exit(1); 13828070Sminshall } else if (pid != 0) { 13928070Sminshall exit(0); 14028070Sminshall } 14128070Sminshall } 14216372Skarels from.sin_family = AF_INET; 14316372Skarels alarm(0); 14416372Skarels close(0); 14516372Skarels close(1); 14616372Skarels peer = socket(AF_INET, SOCK_DGRAM, 0); 14716372Skarels if (peer < 0) { 14828070Sminshall syslog(LOG_ERR, "socket: %m\n"); 14916372Skarels exit(1); 1507772Ssam } 15116372Skarels if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) { 15228070Sminshall syslog(LOG_ERR, "bind: %m\n"); 15316372Skarels exit(1); 15416372Skarels } 15516372Skarels if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) { 15628070Sminshall syslog(LOG_ERR, "connect: %m\n"); 15716372Skarels exit(1); 1587772Ssam } 15916372Skarels tp = (struct tftphdr *)buf; 16016372Skarels tp->th_opcode = ntohs(tp->th_opcode); 16116372Skarels if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 16216372Skarels tftp(tp, n); 16316372Skarels exit(1); 1647772Ssam } 1657772Ssam 1667772Ssam int validate_access(); 1677772Ssam int sendfile(), recvfile(); 1687772Ssam 1697772Ssam struct formats { 1707772Ssam char *f_mode; 1717772Ssam int (*f_validate)(); 1727772Ssam int (*f_send)(); 1737772Ssam int (*f_recv)(); 17426108Sminshall int f_convert; 1757772Ssam } formats[] = { 17626108Sminshall { "netascii", validate_access, sendfile, recvfile, 1 }, 17726108Sminshall { "octet", validate_access, sendfile, recvfile, 0 }, 1787772Ssam #ifdef notdef 17926108Sminshall { "mail", validate_user, sendmail, recvmail, 1 }, 1807772Ssam #endif 1817772Ssam { 0 } 1827772Ssam }; 1837772Ssam 1847772Ssam /* 1857772Ssam * Handle initial connection protocol. 1867772Ssam */ 18716372Skarels tftp(tp, size) 1887772Ssam struct tftphdr *tp; 1897772Ssam int size; 1907772Ssam { 1917772Ssam register char *cp; 1927772Ssam int first = 1, ecode; 1937772Ssam register struct formats *pf; 1947772Ssam char *filename, *mode; 1957772Ssam 1967772Ssam filename = cp = tp->th_stuff; 1977772Ssam again: 1987772Ssam while (cp < buf + size) { 1997772Ssam if (*cp == '\0') 2007772Ssam break; 2017772Ssam cp++; 2027772Ssam } 2037772Ssam if (*cp != '\0') { 2047772Ssam nak(EBADOP); 2057772Ssam exit(1); 2067772Ssam } 2077772Ssam if (first) { 2087772Ssam mode = ++cp; 2097772Ssam first = 0; 2107772Ssam goto again; 2117772Ssam } 2127772Ssam for (cp = mode; *cp; cp++) 2137772Ssam if (isupper(*cp)) 2147772Ssam *cp = tolower(*cp); 2157772Ssam for (pf = formats; pf->f_mode; pf++) 2167772Ssam if (strcmp(pf->f_mode, mode) == 0) 2177772Ssam break; 2187772Ssam if (pf->f_mode == 0) { 2197772Ssam nak(EBADOP); 2207772Ssam exit(1); 2217772Ssam } 22216372Skarels ecode = (*pf->f_validate)(filename, tp->th_opcode); 2237772Ssam if (ecode) { 2247772Ssam nak(ecode); 2257772Ssam exit(1); 2267772Ssam } 2277772Ssam if (tp->th_opcode == WRQ) 2287772Ssam (*pf->f_recv)(pf); 2297772Ssam else 2307772Ssam (*pf->f_send)(pf); 2317772Ssam exit(0); 2327772Ssam } 2337772Ssam 23416372Skarels 23526108Sminshall FILE *file; 23626108Sminshall 2377772Ssam /* 2387772Ssam * Validate file access. Since we 2397772Ssam * have no uid or gid, for now require 2407772Ssam * file to exist and be publicly 2417772Ssam * readable/writable. 24235783Stef * If we were invoked with arguments 24335783Stef * from inetd then the file must also be 24435783Stef * in one of the given directory prefixes. 2457772Ssam * Note also, full path name must be 2467772Ssam * given as we have no login directory. 2477772Ssam */ 24826108Sminshall validate_access(filename, mode) 24926108Sminshall char *filename; 2507772Ssam int mode; 2517772Ssam { 2527772Ssam struct stat stbuf; 25326108Sminshall int fd; 25441146Stef char *cp, **dirp; 2557772Ssam 25626108Sminshall if (*filename != '/') 2577772Ssam return (EACCESS); 25841146Stef /* 25941146Stef * prevent tricksters from getting around the directory restrictions 26041146Stef */ 26141146Stef for (cp = filename + 1; *cp; cp++) 26241146Stef if(*cp == '.' && strncmp(cp-1, "/../", 4) == 0) 26341146Stef return(EACCESS); 26441146Stef for (dirp = dirs; *dirp; dirp++) 26535783Stef if (strncmp(filename, *dirp, strlen(*dirp)) == 0) 26635783Stef break; 26735783Stef if (*dirp==0 && dirp!=dirs) 26835783Stef return (EACCESS); 26926108Sminshall if (stat(filename, &stbuf) < 0) 2707772Ssam return (errno == ENOENT ? ENOTFOUND : EACCESS); 2717772Ssam if (mode == RRQ) { 2727772Ssam if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) 2737772Ssam return (EACCESS); 2747772Ssam } else { 2757772Ssam if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) 2767772Ssam return (EACCESS); 2777772Ssam } 27826108Sminshall fd = open(filename, mode == RRQ ? 0 : 1); 2797772Ssam if (fd < 0) 2807772Ssam return (errno + 100); 28126108Sminshall file = fdopen(fd, (mode == RRQ)? "r":"w"); 28226108Sminshall if (file == NULL) { 28326108Sminshall return errno+100; 28426108Sminshall } 2857772Ssam return (0); 2867772Ssam } 2877772Ssam 28813020Ssam int timeout; 28913020Ssam jmp_buf timeoutbuf; 2907772Ssam 2917772Ssam timer() 2927772Ssam { 29313020Ssam 29413020Ssam timeout += rexmtval; 29513020Ssam if (timeout >= maxtimeout) 2967772Ssam exit(1); 29713020Ssam longjmp(timeoutbuf, 1); 2987772Ssam } 2997772Ssam 3007772Ssam /* 3017772Ssam * Send the requested file. 3027772Ssam */ 3037772Ssam sendfile(pf) 30426108Sminshall struct formats *pf; 3057772Ssam { 30626108Sminshall struct tftphdr *dp, *r_init(); 30726108Sminshall register struct tftphdr *ap; /* ack packet */ 3087772Ssam register int block = 1, size, n; 3097772Ssam 31013020Ssam signal(SIGALRM, timer); 31126108Sminshall dp = r_init(); 31226108Sminshall ap = (struct tftphdr *)ackbuf; 3137772Ssam do { 31426108Sminshall size = readit(file, &dp, pf->f_convert); 3157772Ssam if (size < 0) { 3167772Ssam nak(errno + 100); 31726108Sminshall goto abort; 3187772Ssam } 31926108Sminshall dp->th_opcode = htons((u_short)DATA); 32026108Sminshall dp->th_block = htons((u_short)block); 3217772Ssam timeout = 0; 32213020Ssam (void) setjmp(timeoutbuf); 32326108Sminshall 32426109Sminshall send_data: 32526108Sminshall if (send(peer, dp, size + 4, 0) != size + 4) { 32628070Sminshall syslog(LOG_ERR, "tftpd: write: %m\n"); 32726108Sminshall goto abort; 3287772Ssam } 32926108Sminshall read_ahead(file, pf->f_convert); 33026109Sminshall for ( ; ; ) { 33126108Sminshall alarm(rexmtval); /* read the ack */ 33226108Sminshall n = recv(peer, ackbuf, sizeof (ackbuf), 0); 3337772Ssam alarm(0); 33413020Ssam if (n < 0) { 33528070Sminshall syslog(LOG_ERR, "tftpd: read: %m\n"); 33626108Sminshall goto abort; 33713020Ssam } 33826108Sminshall ap->th_opcode = ntohs((u_short)ap->th_opcode); 33926108Sminshall ap->th_block = ntohs((u_short)ap->th_block); 34026108Sminshall 34126108Sminshall if (ap->th_opcode == ERROR) 34226108Sminshall goto abort; 34326109Sminshall 34426109Sminshall if (ap->th_opcode == ACK) { 34526109Sminshall if (ap->th_block == block) { 34626109Sminshall break; 34726109Sminshall } 34826115Sminshall /* Re-synchronize with the other side */ 34926115Sminshall (void) synchnet(peer); 35026109Sminshall if (ap->th_block == (block -1)) { 35126109Sminshall goto send_data; 35226109Sminshall } 35326109Sminshall } 35426108Sminshall 35526109Sminshall } 3567772Ssam block++; 3577772Ssam } while (size == SEGSIZE); 35826108Sminshall abort: 35926108Sminshall (void) fclose(file); 3607772Ssam } 3617772Ssam 36226108Sminshall justquit() 36326108Sminshall { 36426108Sminshall exit(0); 36526108Sminshall } 36626108Sminshall 36726108Sminshall 3687772Ssam /* 3697772Ssam * Receive a file. 3707772Ssam */ 3717772Ssam recvfile(pf) 37226108Sminshall struct formats *pf; 3737772Ssam { 37426108Sminshall struct tftphdr *dp, *w_init(); 37526108Sminshall register struct tftphdr *ap; /* ack buffer */ 3767772Ssam register int block = 0, n, size; 3777772Ssam 37813020Ssam signal(SIGALRM, timer); 37926108Sminshall dp = w_init(); 38026108Sminshall ap = (struct tftphdr *)ackbuf; 3817772Ssam do { 3827772Ssam timeout = 0; 38326108Sminshall ap->th_opcode = htons((u_short)ACK); 38426108Sminshall ap->th_block = htons((u_short)block); 3857772Ssam block++; 38613020Ssam (void) setjmp(timeoutbuf); 38726108Sminshall send_ack: 38826108Sminshall if (send(peer, ackbuf, 4, 0) != 4) { 38928070Sminshall syslog(LOG_ERR, "tftpd: write: %m\n"); 39013020Ssam goto abort; 3917772Ssam } 39226108Sminshall write_behind(file, pf->f_convert); 39326108Sminshall for ( ; ; ) { 39413020Ssam alarm(rexmtval); 39526108Sminshall n = recv(peer, dp, PKTSIZE, 0); 3967772Ssam alarm(0); 39726108Sminshall if (n < 0) { /* really? */ 39828070Sminshall syslog(LOG_ERR, "tftpd: read: %m\n"); 39913020Ssam goto abort; 40013020Ssam } 40126108Sminshall dp->th_opcode = ntohs((u_short)dp->th_opcode); 40226108Sminshall dp->th_block = ntohs((u_short)dp->th_block); 40326108Sminshall if (dp->th_opcode == ERROR) 40413020Ssam goto abort; 40526108Sminshall if (dp->th_opcode == DATA) { 40626108Sminshall if (dp->th_block == block) { 40726108Sminshall break; /* normal */ 40826108Sminshall } 40926115Sminshall /* Re-synchronize with the other side */ 41026115Sminshall (void) synchnet(peer); 41126108Sminshall if (dp->th_block == (block-1)) 41226108Sminshall goto send_ack; /* rexmit */ 41326108Sminshall } 41426108Sminshall } 41526108Sminshall /* size = write(file, dp->th_data, n - 4); */ 41626108Sminshall size = writeit(file, &dp, n - 4, pf->f_convert); 41726108Sminshall if (size != (n-4)) { /* ahem */ 41826108Sminshall if (size < 0) nak(errno + 100); 41926108Sminshall else nak(ENOSPACE); 42013020Ssam goto abort; 4217772Ssam } 4227772Ssam } while (size == SEGSIZE); 42326108Sminshall write_behind(file, pf->f_convert); 42426108Sminshall (void) fclose(file); /* close data file */ 42526108Sminshall 42626108Sminshall ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 42726108Sminshall ap->th_block = htons((u_short)(block)); 42826108Sminshall (void) send(peer, ackbuf, 4, 0); 42926108Sminshall 43026108Sminshall signal(SIGALRM, justquit); /* just quit on timeout */ 43126108Sminshall alarm(rexmtval); 43226108Sminshall n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 43326108Sminshall alarm(0); 43426108Sminshall if (n >= 4 && /* if read some data */ 43526108Sminshall dp->th_opcode == DATA && /* and got a data block */ 43626108Sminshall block == dp->th_block) { /* then my last ack was lost */ 43726108Sminshall (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 43826108Sminshall } 43913020Ssam abort: 44026108Sminshall return; 4417772Ssam } 4427772Ssam 4437772Ssam struct errmsg { 4447772Ssam int e_code; 4457772Ssam char *e_msg; 4467772Ssam } errmsgs[] = { 4477772Ssam { EUNDEF, "Undefined error code" }, 4487772Ssam { ENOTFOUND, "File not found" }, 4497772Ssam { EACCESS, "Access violation" }, 4507772Ssam { ENOSPACE, "Disk full or allocation exceeded" }, 4517772Ssam { EBADOP, "Illegal TFTP operation" }, 4527772Ssam { EBADID, "Unknown transfer ID" }, 4537772Ssam { EEXISTS, "File already exists" }, 4547772Ssam { ENOUSER, "No such user" }, 4557772Ssam { -1, 0 } 4567772Ssam }; 4577772Ssam 4587772Ssam /* 4597772Ssam * Send a nak packet (error message). 4607772Ssam * Error code passed in is one of the 4617772Ssam * standard TFTP codes, or a UNIX errno 4627772Ssam * offset by 100. 4637772Ssam */ 4647772Ssam nak(error) 4657772Ssam int error; 4667772Ssam { 4677772Ssam register struct tftphdr *tp; 4687772Ssam int length; 4697772Ssam register struct errmsg *pe; 4707772Ssam 4717772Ssam tp = (struct tftphdr *)buf; 4727772Ssam tp->th_opcode = htons((u_short)ERROR); 4737772Ssam tp->th_code = htons((u_short)error); 4747772Ssam for (pe = errmsgs; pe->e_code >= 0; pe++) 4757772Ssam if (pe->e_code == error) 4767772Ssam break; 47726108Sminshall if (pe->e_code < 0) { 478*42413Sbostic pe->e_msg = strerror(error - 100); 47926108Sminshall tp->th_code = EUNDEF; /* set 'undef' errorcode */ 48026108Sminshall } 4817772Ssam strcpy(tp->th_msg, pe->e_msg); 4827772Ssam length = strlen(pe->e_msg); 4837772Ssam tp->th_msg[length] = '\0'; 4847772Ssam length += 5; 48516372Skarels if (send(peer, buf, length, 0) != length) 48628070Sminshall syslog(LOG_ERR, "nak: %m\n"); 4877772Ssam } 488