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 6*34774Sbostic * provided that the above copyright notice and this paragraph are 7*34774Sbostic * duplicated in all such forms and that any documentation, 8*34774Sbostic * advertising materials, and other materials related to such 9*34774Sbostic * distribution and use acknowledge that the software was developed 10*34774Sbostic * by the University of California, Berkeley. The name of the 11*34774Sbostic * University may not be used to endorse or promote products derived 12*34774Sbostic * from this software without specific prior written permission. 13*34774Sbostic * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 14*34774Sbostic * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 15*34774Sbostic * 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*34774Sbostic static char sccsid[] = "@(#)tftpd.c 5.8 (Berkeley) 06/18/88"; 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> 399220Ssam 409220Ssam #include <netinet/in.h> 419220Ssam 4212217Ssam #include <arpa/tftp.h> 4312217Ssam 447772Ssam #include <signal.h> 457772Ssam #include <stdio.h> 467772Ssam #include <errno.h> 477772Ssam #include <ctype.h> 488385Ssam #include <netdb.h> 4913020Ssam #include <setjmp.h> 5017188Sralph #include <syslog.h> 519220Ssam 5213020Ssam #define TIMEOUT 5 5313020Ssam 547772Ssam extern int errno; 558385Ssam struct sockaddr_in sin = { AF_INET }; 5616372Skarels int peer; 5713020Ssam int rexmtval = TIMEOUT; 5813020Ssam int maxtimeout = 5*TIMEOUT; 5926108Sminshall 6026108Sminshall #define PKTSIZE SEGSIZE+4 6126108Sminshall char buf[PKTSIZE]; 6226108Sminshall char ackbuf[PKTSIZE]; 6316372Skarels struct sockaddr_in from; 6416372Skarels int fromlen; 657772Ssam 6616372Skarels main() 677772Ssam { 687772Ssam register struct tftphdr *tp; 697772Ssam register int n; 7028070Sminshall int on = 1; 717772Ssam 7224852Seric openlog("tftpd", LOG_PID, LOG_DAEMON); 7328070Sminshall if (ioctl(0, FIONBIO, &on) < 0) { 7428070Sminshall syslog(LOG_ERR, "ioctl(FIONBIO): %m\n"); 7528070Sminshall exit(1); 7628070Sminshall } 7716372Skarels fromlen = sizeof (from); 7816372Skarels n = recvfrom(0, buf, sizeof (buf), 0, 7916372Skarels (caddr_t)&from, &fromlen); 8016372Skarels if (n < 0) { 8128070Sminshall syslog(LOG_ERR, "recvfrom: %m\n"); 828385Ssam exit(1); 838385Ssam } 8428070Sminshall /* 8528070Sminshall * Now that we have read the message out of the UDP 8628070Sminshall * socket, we fork and exit. Thus, inetd will go back 8728070Sminshall * to listening to the tftp port, and the next request 8828070Sminshall * to come in will start up a new instance of tftpd. 8928070Sminshall * 9028070Sminshall * We do this so that inetd can run tftpd in "wait" mode. 9128070Sminshall * The problem with tftpd running in "nowait" mode is that 9228070Sminshall * inetd may get one or more successful "selects" on the 9328070Sminshall * tftp port before we do our receive, so more than one 9428070Sminshall * instance of tftpd may be started up. Worse, if tftpd 9528070Sminshall * break before doing the above "recvfrom", inetd would 9628070Sminshall * spawn endless instances, clogging the system. 9728070Sminshall */ 9828070Sminshall { 9928070Sminshall int pid; 10028070Sminshall int i, j; 10128070Sminshall 10228070Sminshall for (i = 1; i < 20; i++) { 10328070Sminshall pid = fork(); 10428070Sminshall if (pid < 0) { 10528070Sminshall sleep(i); 10628070Sminshall /* 10728070Sminshall * flush out to most recently sent request. 10828070Sminshall * 10928070Sminshall * This may drop some request, but those 11028070Sminshall * will be resent by the clients when 11128070Sminshall * they timeout. The positive effect of 11228070Sminshall * this flush is to (try to) prevent more 11328070Sminshall * than one tftpd being started up to service 11428070Sminshall * a single request from a single client. 11528070Sminshall */ 11628070Sminshall j = sizeof from; 11728070Sminshall i = recvfrom(0, buf, sizeof (buf), 0, 11828070Sminshall (caddr_t)&from, &j); 11928070Sminshall if (i > 0) { 12028070Sminshall n = i; 12128070Sminshall fromlen = j; 12228070Sminshall } 12328070Sminshall } else { 12428070Sminshall break; 12528070Sminshall } 12628070Sminshall } 12728070Sminshall if (pid < 0) { 12828070Sminshall syslog(LOG_ERR, "fork: %m\n"); 12928070Sminshall exit(1); 13028070Sminshall } else if (pid != 0) { 13128070Sminshall exit(0); 13228070Sminshall } 13328070Sminshall } 13416372Skarels from.sin_family = AF_INET; 13516372Skarels alarm(0); 13616372Skarels close(0); 13716372Skarels close(1); 13816372Skarels peer = socket(AF_INET, SOCK_DGRAM, 0); 13916372Skarels if (peer < 0) { 14028070Sminshall syslog(LOG_ERR, "socket: %m\n"); 14116372Skarels exit(1); 1427772Ssam } 14316372Skarels if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) { 14428070Sminshall syslog(LOG_ERR, "bind: %m\n"); 14516372Skarels exit(1); 14616372Skarels } 14716372Skarels if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) { 14828070Sminshall syslog(LOG_ERR, "connect: %m\n"); 14916372Skarels exit(1); 1507772Ssam } 15116372Skarels tp = (struct tftphdr *)buf; 15216372Skarels tp->th_opcode = ntohs(tp->th_opcode); 15316372Skarels if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 15416372Skarels tftp(tp, n); 15516372Skarels exit(1); 1567772Ssam } 1577772Ssam 1587772Ssam int validate_access(); 1597772Ssam int sendfile(), recvfile(); 1607772Ssam 1617772Ssam struct formats { 1627772Ssam char *f_mode; 1637772Ssam int (*f_validate)(); 1647772Ssam int (*f_send)(); 1657772Ssam int (*f_recv)(); 16626108Sminshall int f_convert; 1677772Ssam } formats[] = { 16826108Sminshall { "netascii", validate_access, sendfile, recvfile, 1 }, 16926108Sminshall { "octet", validate_access, sendfile, recvfile, 0 }, 1707772Ssam #ifdef notdef 17126108Sminshall { "mail", validate_user, sendmail, recvmail, 1 }, 1727772Ssam #endif 1737772Ssam { 0 } 1747772Ssam }; 1757772Ssam 1767772Ssam /* 1777772Ssam * Handle initial connection protocol. 1787772Ssam */ 17916372Skarels tftp(tp, size) 1807772Ssam struct tftphdr *tp; 1817772Ssam int size; 1827772Ssam { 1837772Ssam register char *cp; 1847772Ssam int first = 1, ecode; 1857772Ssam register struct formats *pf; 1867772Ssam char *filename, *mode; 1877772Ssam 1887772Ssam filename = cp = tp->th_stuff; 1897772Ssam again: 1907772Ssam while (cp < buf + size) { 1917772Ssam if (*cp == '\0') 1927772Ssam break; 1937772Ssam cp++; 1947772Ssam } 1957772Ssam if (*cp != '\0') { 1967772Ssam nak(EBADOP); 1977772Ssam exit(1); 1987772Ssam } 1997772Ssam if (first) { 2007772Ssam mode = ++cp; 2017772Ssam first = 0; 2027772Ssam goto again; 2037772Ssam } 2047772Ssam for (cp = mode; *cp; cp++) 2057772Ssam if (isupper(*cp)) 2067772Ssam *cp = tolower(*cp); 2077772Ssam for (pf = formats; pf->f_mode; pf++) 2087772Ssam if (strcmp(pf->f_mode, mode) == 0) 2097772Ssam break; 2107772Ssam if (pf->f_mode == 0) { 2117772Ssam nak(EBADOP); 2127772Ssam exit(1); 2137772Ssam } 21416372Skarels ecode = (*pf->f_validate)(filename, tp->th_opcode); 2157772Ssam if (ecode) { 2167772Ssam nak(ecode); 2177772Ssam exit(1); 2187772Ssam } 2197772Ssam if (tp->th_opcode == WRQ) 2207772Ssam (*pf->f_recv)(pf); 2217772Ssam else 2227772Ssam (*pf->f_send)(pf); 2237772Ssam exit(0); 2247772Ssam } 2257772Ssam 22616372Skarels 22726108Sminshall FILE *file; 22826108Sminshall 2297772Ssam /* 2307772Ssam * Validate file access. Since we 2317772Ssam * have no uid or gid, for now require 2327772Ssam * file to exist and be publicly 2337772Ssam * readable/writable. 2347772Ssam * Note also, full path name must be 2357772Ssam * given as we have no login directory. 2367772Ssam */ 23726108Sminshall validate_access(filename, mode) 23826108Sminshall char *filename; 2397772Ssam int mode; 2407772Ssam { 2417772Ssam struct stat stbuf; 24226108Sminshall int fd; 2437772Ssam 24426108Sminshall if (*filename != '/') 2457772Ssam return (EACCESS); 24626108Sminshall if (stat(filename, &stbuf) < 0) 2477772Ssam return (errno == ENOENT ? ENOTFOUND : EACCESS); 2487772Ssam if (mode == RRQ) { 2497772Ssam if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) 2507772Ssam return (EACCESS); 2517772Ssam } else { 2527772Ssam if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) 2537772Ssam return (EACCESS); 2547772Ssam } 25526108Sminshall fd = open(filename, mode == RRQ ? 0 : 1); 2567772Ssam if (fd < 0) 2577772Ssam return (errno + 100); 25826108Sminshall file = fdopen(fd, (mode == RRQ)? "r":"w"); 25926108Sminshall if (file == NULL) { 26026108Sminshall return errno+100; 26126108Sminshall } 2627772Ssam return (0); 2637772Ssam } 2647772Ssam 26513020Ssam int timeout; 26613020Ssam jmp_buf timeoutbuf; 2677772Ssam 2687772Ssam timer() 2697772Ssam { 27013020Ssam 27113020Ssam timeout += rexmtval; 27213020Ssam if (timeout >= maxtimeout) 2737772Ssam exit(1); 27413020Ssam longjmp(timeoutbuf, 1); 2757772Ssam } 2767772Ssam 2777772Ssam /* 2787772Ssam * Send the requested file. 2797772Ssam */ 2807772Ssam sendfile(pf) 28126108Sminshall struct formats *pf; 2827772Ssam { 28326108Sminshall struct tftphdr *dp, *r_init(); 28426108Sminshall register struct tftphdr *ap; /* ack packet */ 2857772Ssam register int block = 1, size, n; 2867772Ssam 28713020Ssam signal(SIGALRM, timer); 28826108Sminshall dp = r_init(); 28926108Sminshall ap = (struct tftphdr *)ackbuf; 2907772Ssam do { 29126108Sminshall size = readit(file, &dp, pf->f_convert); 2927772Ssam if (size < 0) { 2937772Ssam nak(errno + 100); 29426108Sminshall goto abort; 2957772Ssam } 29626108Sminshall dp->th_opcode = htons((u_short)DATA); 29726108Sminshall dp->th_block = htons((u_short)block); 2987772Ssam timeout = 0; 29913020Ssam (void) setjmp(timeoutbuf); 30026108Sminshall 30126109Sminshall send_data: 30226108Sminshall if (send(peer, dp, size + 4, 0) != size + 4) { 30328070Sminshall syslog(LOG_ERR, "tftpd: write: %m\n"); 30426108Sminshall goto abort; 3057772Ssam } 30626108Sminshall read_ahead(file, pf->f_convert); 30726109Sminshall for ( ; ; ) { 30826108Sminshall alarm(rexmtval); /* read the ack */ 30926108Sminshall n = recv(peer, ackbuf, sizeof (ackbuf), 0); 3107772Ssam alarm(0); 31113020Ssam if (n < 0) { 31228070Sminshall syslog(LOG_ERR, "tftpd: read: %m\n"); 31326108Sminshall goto abort; 31413020Ssam } 31526108Sminshall ap->th_opcode = ntohs((u_short)ap->th_opcode); 31626108Sminshall ap->th_block = ntohs((u_short)ap->th_block); 31726108Sminshall 31826108Sminshall if (ap->th_opcode == ERROR) 31926108Sminshall goto abort; 32026109Sminshall 32126109Sminshall if (ap->th_opcode == ACK) { 32226109Sminshall if (ap->th_block == block) { 32326109Sminshall break; 32426109Sminshall } 32526115Sminshall /* Re-synchronize with the other side */ 32626115Sminshall (void) synchnet(peer); 32726109Sminshall if (ap->th_block == (block -1)) { 32826109Sminshall goto send_data; 32926109Sminshall } 33026109Sminshall } 33126108Sminshall 33226109Sminshall } 3337772Ssam block++; 3347772Ssam } while (size == SEGSIZE); 33526108Sminshall abort: 33626108Sminshall (void) fclose(file); 3377772Ssam } 3387772Ssam 33926108Sminshall justquit() 34026108Sminshall { 34126108Sminshall exit(0); 34226108Sminshall } 34326108Sminshall 34426108Sminshall 3457772Ssam /* 3467772Ssam * Receive a file. 3477772Ssam */ 3487772Ssam recvfile(pf) 34926108Sminshall struct formats *pf; 3507772Ssam { 35126108Sminshall struct tftphdr *dp, *w_init(); 35226108Sminshall register struct tftphdr *ap; /* ack buffer */ 3537772Ssam register int block = 0, n, size; 3547772Ssam 35513020Ssam signal(SIGALRM, timer); 35626108Sminshall dp = w_init(); 35726108Sminshall ap = (struct tftphdr *)ackbuf; 3587772Ssam do { 3597772Ssam timeout = 0; 36026108Sminshall ap->th_opcode = htons((u_short)ACK); 36126108Sminshall ap->th_block = htons((u_short)block); 3627772Ssam block++; 36313020Ssam (void) setjmp(timeoutbuf); 36426108Sminshall send_ack: 36526108Sminshall if (send(peer, ackbuf, 4, 0) != 4) { 36628070Sminshall syslog(LOG_ERR, "tftpd: write: %m\n"); 36713020Ssam goto abort; 3687772Ssam } 36926108Sminshall write_behind(file, pf->f_convert); 37026108Sminshall for ( ; ; ) { 37113020Ssam alarm(rexmtval); 37226108Sminshall n = recv(peer, dp, PKTSIZE, 0); 3737772Ssam alarm(0); 37426108Sminshall if (n < 0) { /* really? */ 37528070Sminshall syslog(LOG_ERR, "tftpd: read: %m\n"); 37613020Ssam goto abort; 37713020Ssam } 37826108Sminshall dp->th_opcode = ntohs((u_short)dp->th_opcode); 37926108Sminshall dp->th_block = ntohs((u_short)dp->th_block); 38026108Sminshall if (dp->th_opcode == ERROR) 38113020Ssam goto abort; 38226108Sminshall if (dp->th_opcode == DATA) { 38326108Sminshall if (dp->th_block == block) { 38426108Sminshall break; /* normal */ 38526108Sminshall } 38626115Sminshall /* Re-synchronize with the other side */ 38726115Sminshall (void) synchnet(peer); 38826108Sminshall if (dp->th_block == (block-1)) 38926108Sminshall goto send_ack; /* rexmit */ 39026108Sminshall } 39126108Sminshall } 39226108Sminshall /* size = write(file, dp->th_data, n - 4); */ 39326108Sminshall size = writeit(file, &dp, n - 4, pf->f_convert); 39426108Sminshall if (size != (n-4)) { /* ahem */ 39526108Sminshall if (size < 0) nak(errno + 100); 39626108Sminshall else nak(ENOSPACE); 39713020Ssam goto abort; 3987772Ssam } 3997772Ssam } while (size == SEGSIZE); 40026108Sminshall write_behind(file, pf->f_convert); 40126108Sminshall (void) fclose(file); /* close data file */ 40226108Sminshall 40326108Sminshall ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 40426108Sminshall ap->th_block = htons((u_short)(block)); 40526108Sminshall (void) send(peer, ackbuf, 4, 0); 40626108Sminshall 40726108Sminshall signal(SIGALRM, justquit); /* just quit on timeout */ 40826108Sminshall alarm(rexmtval); 40926108Sminshall n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 41026108Sminshall alarm(0); 41126108Sminshall if (n >= 4 && /* if read some data */ 41226108Sminshall dp->th_opcode == DATA && /* and got a data block */ 41326108Sminshall block == dp->th_block) { /* then my last ack was lost */ 41426108Sminshall (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 41526108Sminshall } 41613020Ssam abort: 41726108Sminshall return; 4187772Ssam } 4197772Ssam 4207772Ssam struct errmsg { 4217772Ssam int e_code; 4227772Ssam char *e_msg; 4237772Ssam } errmsgs[] = { 4247772Ssam { EUNDEF, "Undefined error code" }, 4257772Ssam { ENOTFOUND, "File not found" }, 4267772Ssam { EACCESS, "Access violation" }, 4277772Ssam { ENOSPACE, "Disk full or allocation exceeded" }, 4287772Ssam { EBADOP, "Illegal TFTP operation" }, 4297772Ssam { EBADID, "Unknown transfer ID" }, 4307772Ssam { EEXISTS, "File already exists" }, 4317772Ssam { ENOUSER, "No such user" }, 4327772Ssam { -1, 0 } 4337772Ssam }; 4347772Ssam 4357772Ssam /* 4367772Ssam * Send a nak packet (error message). 4377772Ssam * Error code passed in is one of the 4387772Ssam * standard TFTP codes, or a UNIX errno 4397772Ssam * offset by 100. 4407772Ssam */ 4417772Ssam nak(error) 4427772Ssam int error; 4437772Ssam { 4447772Ssam register struct tftphdr *tp; 4457772Ssam int length; 4467772Ssam register struct errmsg *pe; 4477772Ssam extern char *sys_errlist[]; 4487772Ssam 4497772Ssam tp = (struct tftphdr *)buf; 4507772Ssam tp->th_opcode = htons((u_short)ERROR); 4517772Ssam tp->th_code = htons((u_short)error); 4527772Ssam for (pe = errmsgs; pe->e_code >= 0; pe++) 4537772Ssam if (pe->e_code == error) 4547772Ssam break; 45526108Sminshall if (pe->e_code < 0) { 4567772Ssam pe->e_msg = sys_errlist[error - 100]; 45726108Sminshall tp->th_code = EUNDEF; /* set 'undef' errorcode */ 45826108Sminshall } 4597772Ssam strcpy(tp->th_msg, pe->e_msg); 4607772Ssam length = strlen(pe->e_msg); 4617772Ssam tp->th_msg[length] = '\0'; 4627772Ssam length += 5; 46316372Skarels if (send(peer, buf, length, 0) != length) 46428070Sminshall syslog(LOG_ERR, "nak: %m\n"); 4657772Ssam } 466