121183Sdist /* 221183Sdist * Copyright (c) 1983 Regents of the University of California. 321183Sdist * All rights reserved. The Berkeley software License Agreement 421183Sdist * specifies the terms and conditions for redistribution. 521183Sdist */ 621183Sdist 713609Ssam #ifndef lint 821183Sdist char copyright[] = 921183Sdist "@(#) Copyright (c) 1983 Regents of the University of California.\n\ 1021183Sdist All rights reserved.\n"; 1121183Sdist #endif not lint 127772Ssam 1321183Sdist #ifndef lint 14*28070Sminshall static char sccsid[] = "@(#)tftpd.c 5.6 (Berkeley) 05/13/86"; 1521183Sdist #endif not lint 1621183Sdist 1726108Sminshall 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> 299220Ssam 309220Ssam #include <netinet/in.h> 319220Ssam 3212217Ssam #include <arpa/tftp.h> 3312217Ssam 347772Ssam #include <signal.h> 357772Ssam #include <stdio.h> 367772Ssam #include <errno.h> 377772Ssam #include <ctype.h> 388385Ssam #include <netdb.h> 3913020Ssam #include <setjmp.h> 4017188Sralph #include <syslog.h> 419220Ssam 4213020Ssam #define TIMEOUT 5 4313020Ssam 447772Ssam extern int errno; 458385Ssam struct sockaddr_in sin = { AF_INET }; 4616372Skarels int peer; 4713020Ssam int rexmtval = TIMEOUT; 4813020Ssam int maxtimeout = 5*TIMEOUT; 4926108Sminshall 5026108Sminshall #define PKTSIZE SEGSIZE+4 5126108Sminshall char buf[PKTSIZE]; 5226108Sminshall char ackbuf[PKTSIZE]; 5316372Skarels struct sockaddr_in from; 5416372Skarels int fromlen; 557772Ssam 5616372Skarels main() 577772Ssam { 587772Ssam register struct tftphdr *tp; 597772Ssam register int n; 60*28070Sminshall int on = 1; 617772Ssam 6224852Seric openlog("tftpd", LOG_PID, LOG_DAEMON); 63*28070Sminshall if (ioctl(0, FIONBIO, &on) < 0) { 64*28070Sminshall syslog(LOG_ERR, "ioctl(FIONBIO): %m\n"); 65*28070Sminshall exit(1); 66*28070Sminshall } 6716372Skarels fromlen = sizeof (from); 6816372Skarels n = recvfrom(0, buf, sizeof (buf), 0, 6916372Skarels (caddr_t)&from, &fromlen); 7016372Skarels if (n < 0) { 71*28070Sminshall syslog(LOG_ERR, "recvfrom: %m\n"); 728385Ssam exit(1); 738385Ssam } 74*28070Sminshall /* 75*28070Sminshall * Now that we have read the message out of the UDP 76*28070Sminshall * socket, we fork and exit. Thus, inetd will go back 77*28070Sminshall * to listening to the tftp port, and the next request 78*28070Sminshall * to come in will start up a new instance of tftpd. 79*28070Sminshall * 80*28070Sminshall * We do this so that inetd can run tftpd in "wait" mode. 81*28070Sminshall * The problem with tftpd running in "nowait" mode is that 82*28070Sminshall * inetd may get one or more successful "selects" on the 83*28070Sminshall * tftp port before we do our receive, so more than one 84*28070Sminshall * instance of tftpd may be started up. Worse, if tftpd 85*28070Sminshall * break before doing the above "recvfrom", inetd would 86*28070Sminshall * spawn endless instances, clogging the system. 87*28070Sminshall */ 88*28070Sminshall { 89*28070Sminshall int pid; 90*28070Sminshall int i, j; 91*28070Sminshall 92*28070Sminshall for (i = 1; i < 20; i++) { 93*28070Sminshall pid = fork(); 94*28070Sminshall if (pid < 0) { 95*28070Sminshall sleep(i); 96*28070Sminshall /* 97*28070Sminshall * flush out to most recently sent request. 98*28070Sminshall * 99*28070Sminshall * This may drop some request, but those 100*28070Sminshall * will be resent by the clients when 101*28070Sminshall * they timeout. The positive effect of 102*28070Sminshall * this flush is to (try to) prevent more 103*28070Sminshall * than one tftpd being started up to service 104*28070Sminshall * a single request from a single client. 105*28070Sminshall */ 106*28070Sminshall j = sizeof from; 107*28070Sminshall i = recvfrom(0, buf, sizeof (buf), 0, 108*28070Sminshall (caddr_t)&from, &j); 109*28070Sminshall if (i > 0) { 110*28070Sminshall n = i; 111*28070Sminshall fromlen = j; 112*28070Sminshall } 113*28070Sminshall } else { 114*28070Sminshall break; 115*28070Sminshall } 116*28070Sminshall } 117*28070Sminshall if (pid < 0) { 118*28070Sminshall syslog(LOG_ERR, "fork: %m\n"); 119*28070Sminshall exit(1); 120*28070Sminshall } else if (pid != 0) { 121*28070Sminshall exit(0); 122*28070Sminshall } 123*28070Sminshall } 12416372Skarels from.sin_family = AF_INET; 12516372Skarels alarm(0); 12616372Skarels close(0); 12716372Skarels close(1); 12816372Skarels peer = socket(AF_INET, SOCK_DGRAM, 0); 12916372Skarels if (peer < 0) { 130*28070Sminshall syslog(LOG_ERR, "socket: %m\n"); 13116372Skarels exit(1); 1327772Ssam } 13316372Skarels if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) { 134*28070Sminshall syslog(LOG_ERR, "bind: %m\n"); 13516372Skarels exit(1); 13616372Skarels } 13716372Skarels if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) { 138*28070Sminshall syslog(LOG_ERR, "connect: %m\n"); 13916372Skarels exit(1); 1407772Ssam } 14116372Skarels tp = (struct tftphdr *)buf; 14216372Skarels tp->th_opcode = ntohs(tp->th_opcode); 14316372Skarels if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 14416372Skarels tftp(tp, n); 14516372Skarels exit(1); 1467772Ssam } 1477772Ssam 1487772Ssam int validate_access(); 1497772Ssam int sendfile(), recvfile(); 1507772Ssam 1517772Ssam struct formats { 1527772Ssam char *f_mode; 1537772Ssam int (*f_validate)(); 1547772Ssam int (*f_send)(); 1557772Ssam int (*f_recv)(); 15626108Sminshall int f_convert; 1577772Ssam } formats[] = { 15826108Sminshall { "netascii", validate_access, sendfile, recvfile, 1 }, 15926108Sminshall { "octet", validate_access, sendfile, recvfile, 0 }, 1607772Ssam #ifdef notdef 16126108Sminshall { "mail", validate_user, sendmail, recvmail, 1 }, 1627772Ssam #endif 1637772Ssam { 0 } 1647772Ssam }; 1657772Ssam 1667772Ssam /* 1677772Ssam * Handle initial connection protocol. 1687772Ssam */ 16916372Skarels tftp(tp, size) 1707772Ssam struct tftphdr *tp; 1717772Ssam int size; 1727772Ssam { 1737772Ssam register char *cp; 1747772Ssam int first = 1, ecode; 1757772Ssam register struct formats *pf; 1767772Ssam char *filename, *mode; 1777772Ssam 1787772Ssam filename = cp = tp->th_stuff; 1797772Ssam again: 1807772Ssam while (cp < buf + size) { 1817772Ssam if (*cp == '\0') 1827772Ssam break; 1837772Ssam cp++; 1847772Ssam } 1857772Ssam if (*cp != '\0') { 1867772Ssam nak(EBADOP); 1877772Ssam exit(1); 1887772Ssam } 1897772Ssam if (first) { 1907772Ssam mode = ++cp; 1917772Ssam first = 0; 1927772Ssam goto again; 1937772Ssam } 1947772Ssam for (cp = mode; *cp; cp++) 1957772Ssam if (isupper(*cp)) 1967772Ssam *cp = tolower(*cp); 1977772Ssam for (pf = formats; pf->f_mode; pf++) 1987772Ssam if (strcmp(pf->f_mode, mode) == 0) 1997772Ssam break; 2007772Ssam if (pf->f_mode == 0) { 2017772Ssam nak(EBADOP); 2027772Ssam exit(1); 2037772Ssam } 20416372Skarels ecode = (*pf->f_validate)(filename, tp->th_opcode); 2057772Ssam if (ecode) { 2067772Ssam nak(ecode); 2077772Ssam exit(1); 2087772Ssam } 2097772Ssam if (tp->th_opcode == WRQ) 2107772Ssam (*pf->f_recv)(pf); 2117772Ssam else 2127772Ssam (*pf->f_send)(pf); 2137772Ssam exit(0); 2147772Ssam } 2157772Ssam 21616372Skarels 21726108Sminshall FILE *file; 21826108Sminshall 2197772Ssam /* 2207772Ssam * Validate file access. Since we 2217772Ssam * have no uid or gid, for now require 2227772Ssam * file to exist and be publicly 2237772Ssam * readable/writable. 2247772Ssam * Note also, full path name must be 2257772Ssam * given as we have no login directory. 2267772Ssam */ 22726108Sminshall validate_access(filename, mode) 22826108Sminshall char *filename; 2297772Ssam int mode; 2307772Ssam { 2317772Ssam struct stat stbuf; 23226108Sminshall int fd; 2337772Ssam 23426108Sminshall if (*filename != '/') 2357772Ssam return (EACCESS); 23626108Sminshall if (stat(filename, &stbuf) < 0) 2377772Ssam return (errno == ENOENT ? ENOTFOUND : EACCESS); 2387772Ssam if (mode == RRQ) { 2397772Ssam if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) 2407772Ssam return (EACCESS); 2417772Ssam } else { 2427772Ssam if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) 2437772Ssam return (EACCESS); 2447772Ssam } 24526108Sminshall fd = open(filename, mode == RRQ ? 0 : 1); 2467772Ssam if (fd < 0) 2477772Ssam return (errno + 100); 24826108Sminshall file = fdopen(fd, (mode == RRQ)? "r":"w"); 24926108Sminshall if (file == NULL) { 25026108Sminshall return errno+100; 25126108Sminshall } 2527772Ssam return (0); 2537772Ssam } 2547772Ssam 25513020Ssam int timeout; 25613020Ssam jmp_buf timeoutbuf; 2577772Ssam 2587772Ssam timer() 2597772Ssam { 26013020Ssam 26113020Ssam timeout += rexmtval; 26213020Ssam if (timeout >= maxtimeout) 2637772Ssam exit(1); 26413020Ssam longjmp(timeoutbuf, 1); 2657772Ssam } 2667772Ssam 2677772Ssam /* 2687772Ssam * Send the requested file. 2697772Ssam */ 2707772Ssam sendfile(pf) 27126108Sminshall struct formats *pf; 2727772Ssam { 27326108Sminshall struct tftphdr *dp, *r_init(); 27426108Sminshall register struct tftphdr *ap; /* ack packet */ 2757772Ssam register int block = 1, size, n; 2767772Ssam 27713020Ssam signal(SIGALRM, timer); 27826108Sminshall dp = r_init(); 27926108Sminshall ap = (struct tftphdr *)ackbuf; 2807772Ssam do { 28126108Sminshall size = readit(file, &dp, pf->f_convert); 2827772Ssam if (size < 0) { 2837772Ssam nak(errno + 100); 28426108Sminshall goto abort; 2857772Ssam } 28626108Sminshall dp->th_opcode = htons((u_short)DATA); 28726108Sminshall dp->th_block = htons((u_short)block); 2887772Ssam timeout = 0; 28913020Ssam (void) setjmp(timeoutbuf); 29026108Sminshall 29126109Sminshall send_data: 29226108Sminshall if (send(peer, dp, size + 4, 0) != size + 4) { 293*28070Sminshall syslog(LOG_ERR, "tftpd: write: %m\n"); 29426108Sminshall goto abort; 2957772Ssam } 29626108Sminshall read_ahead(file, pf->f_convert); 29726109Sminshall for ( ; ; ) { 29826108Sminshall alarm(rexmtval); /* read the ack */ 29926108Sminshall n = recv(peer, ackbuf, sizeof (ackbuf), 0); 3007772Ssam alarm(0); 30113020Ssam if (n < 0) { 302*28070Sminshall syslog(LOG_ERR, "tftpd: read: %m\n"); 30326108Sminshall goto abort; 30413020Ssam } 30526108Sminshall ap->th_opcode = ntohs((u_short)ap->th_opcode); 30626108Sminshall ap->th_block = ntohs((u_short)ap->th_block); 30726108Sminshall 30826108Sminshall if (ap->th_opcode == ERROR) 30926108Sminshall goto abort; 31026109Sminshall 31126109Sminshall if (ap->th_opcode == ACK) { 31226109Sminshall if (ap->th_block == block) { 31326109Sminshall break; 31426109Sminshall } 31526115Sminshall /* Re-synchronize with the other side */ 31626115Sminshall (void) synchnet(peer); 31726109Sminshall if (ap->th_block == (block -1)) { 31826109Sminshall goto send_data; 31926109Sminshall } 32026109Sminshall } 32126108Sminshall 32226109Sminshall } 3237772Ssam block++; 3247772Ssam } while (size == SEGSIZE); 32526108Sminshall abort: 32626108Sminshall (void) fclose(file); 3277772Ssam } 3287772Ssam 32926108Sminshall justquit() 33026108Sminshall { 33126108Sminshall exit(0); 33226108Sminshall } 33326108Sminshall 33426108Sminshall 3357772Ssam /* 3367772Ssam * Receive a file. 3377772Ssam */ 3387772Ssam recvfile(pf) 33926108Sminshall struct formats *pf; 3407772Ssam { 34126108Sminshall struct tftphdr *dp, *w_init(); 34226108Sminshall register struct tftphdr *ap; /* ack buffer */ 3437772Ssam register int block = 0, n, size; 3447772Ssam 34513020Ssam signal(SIGALRM, timer); 34626108Sminshall dp = w_init(); 34726108Sminshall ap = (struct tftphdr *)ackbuf; 3487772Ssam do { 3497772Ssam timeout = 0; 35026108Sminshall ap->th_opcode = htons((u_short)ACK); 35126108Sminshall ap->th_block = htons((u_short)block); 3527772Ssam block++; 35313020Ssam (void) setjmp(timeoutbuf); 35426108Sminshall send_ack: 35526108Sminshall if (send(peer, ackbuf, 4, 0) != 4) { 356*28070Sminshall syslog(LOG_ERR, "tftpd: write: %m\n"); 35713020Ssam goto abort; 3587772Ssam } 35926108Sminshall write_behind(file, pf->f_convert); 36026108Sminshall for ( ; ; ) { 36113020Ssam alarm(rexmtval); 36226108Sminshall n = recv(peer, dp, PKTSIZE, 0); 3637772Ssam alarm(0); 36426108Sminshall if (n < 0) { /* really? */ 365*28070Sminshall syslog(LOG_ERR, "tftpd: read: %m\n"); 36613020Ssam goto abort; 36713020Ssam } 36826108Sminshall dp->th_opcode = ntohs((u_short)dp->th_opcode); 36926108Sminshall dp->th_block = ntohs((u_short)dp->th_block); 37026108Sminshall if (dp->th_opcode == ERROR) 37113020Ssam goto abort; 37226108Sminshall if (dp->th_opcode == DATA) { 37326108Sminshall if (dp->th_block == block) { 37426108Sminshall break; /* normal */ 37526108Sminshall } 37626115Sminshall /* Re-synchronize with the other side */ 37726115Sminshall (void) synchnet(peer); 37826108Sminshall if (dp->th_block == (block-1)) 37926108Sminshall goto send_ack; /* rexmit */ 38026108Sminshall } 38126108Sminshall } 38226108Sminshall /* size = write(file, dp->th_data, n - 4); */ 38326108Sminshall size = writeit(file, &dp, n - 4, pf->f_convert); 38426108Sminshall if (size != (n-4)) { /* ahem */ 38526108Sminshall if (size < 0) nak(errno + 100); 38626108Sminshall else nak(ENOSPACE); 38713020Ssam goto abort; 3887772Ssam } 3897772Ssam } while (size == SEGSIZE); 39026108Sminshall write_behind(file, pf->f_convert); 39126108Sminshall (void) fclose(file); /* close data file */ 39226108Sminshall 39326108Sminshall ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 39426108Sminshall ap->th_block = htons((u_short)(block)); 39526108Sminshall (void) send(peer, ackbuf, 4, 0); 39626108Sminshall 39726108Sminshall signal(SIGALRM, justquit); /* just quit on timeout */ 39826108Sminshall alarm(rexmtval); 39926108Sminshall n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 40026108Sminshall alarm(0); 40126108Sminshall if (n >= 4 && /* if read some data */ 40226108Sminshall dp->th_opcode == DATA && /* and got a data block */ 40326108Sminshall block == dp->th_block) { /* then my last ack was lost */ 40426108Sminshall (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 40526108Sminshall } 40613020Ssam abort: 40726108Sminshall return; 4087772Ssam } 4097772Ssam 4107772Ssam struct errmsg { 4117772Ssam int e_code; 4127772Ssam char *e_msg; 4137772Ssam } errmsgs[] = { 4147772Ssam { EUNDEF, "Undefined error code" }, 4157772Ssam { ENOTFOUND, "File not found" }, 4167772Ssam { EACCESS, "Access violation" }, 4177772Ssam { ENOSPACE, "Disk full or allocation exceeded" }, 4187772Ssam { EBADOP, "Illegal TFTP operation" }, 4197772Ssam { EBADID, "Unknown transfer ID" }, 4207772Ssam { EEXISTS, "File already exists" }, 4217772Ssam { ENOUSER, "No such user" }, 4227772Ssam { -1, 0 } 4237772Ssam }; 4247772Ssam 4257772Ssam /* 4267772Ssam * Send a nak packet (error message). 4277772Ssam * Error code passed in is one of the 4287772Ssam * standard TFTP codes, or a UNIX errno 4297772Ssam * offset by 100. 4307772Ssam */ 4317772Ssam nak(error) 4327772Ssam int error; 4337772Ssam { 4347772Ssam register struct tftphdr *tp; 4357772Ssam int length; 4367772Ssam register struct errmsg *pe; 4377772Ssam extern char *sys_errlist[]; 4387772Ssam 4397772Ssam tp = (struct tftphdr *)buf; 4407772Ssam tp->th_opcode = htons((u_short)ERROR); 4417772Ssam tp->th_code = htons((u_short)error); 4427772Ssam for (pe = errmsgs; pe->e_code >= 0; pe++) 4437772Ssam if (pe->e_code == error) 4447772Ssam break; 44526108Sminshall if (pe->e_code < 0) { 4467772Ssam pe->e_msg = sys_errlist[error - 100]; 44726108Sminshall tp->th_code = EUNDEF; /* set 'undef' errorcode */ 44826108Sminshall } 4497772Ssam strcpy(tp->th_msg, pe->e_msg); 4507772Ssam length = strlen(pe->e_msg); 4517772Ssam tp->th_msg[length] = '\0'; 4527772Ssam length += 5; 45316372Skarels if (send(peer, buf, length, 0) != length) 454*28070Sminshall syslog(LOG_ERR, "nak: %m\n"); 4557772Ssam } 456