113609Ssam #ifndef lint 2*17188Sralph static char sccsid[] = "@(#)tftpd.c 4.14 (Berkeley) 09/13/84"; 313609Ssam #endif 47772Ssam 57772Ssam /* 67772Ssam * Trivial file transfer protocol server. 77772Ssam */ 87772Ssam #include <sys/types.h> 97772Ssam #include <sys/socket.h> 109220Ssam #include <sys/ioctl.h> 1113609Ssam #include <sys/wait.h> 1213609Ssam #include <sys/stat.h> 139220Ssam 149220Ssam #include <netinet/in.h> 159220Ssam 1612217Ssam #include <arpa/tftp.h> 1712217Ssam 187772Ssam #include <signal.h> 197772Ssam #include <stdio.h> 207772Ssam #include <errno.h> 217772Ssam #include <ctype.h> 228385Ssam #include <netdb.h> 2313020Ssam #include <setjmp.h> 24*17188Sralph #include <syslog.h> 259220Ssam 2613020Ssam #define TIMEOUT 5 2713020Ssam 287772Ssam extern int errno; 298385Ssam struct sockaddr_in sin = { AF_INET }; 3016372Skarels int peer; 3113020Ssam int rexmtval = TIMEOUT; 3213020Ssam int maxtimeout = 5*TIMEOUT; 337772Ssam char buf[BUFSIZ]; 3416372Skarels struct sockaddr_in from; 3516372Skarels int fromlen; 367772Ssam 3716372Skarels main() 387772Ssam { 397772Ssam register struct tftphdr *tp; 407772Ssam register int n; 417772Ssam 4216372Skarels alarm(10); 4316372Skarels fromlen = sizeof (from); 4416372Skarels n = recvfrom(0, buf, sizeof (buf), 0, 4516372Skarels (caddr_t)&from, &fromlen); 4616372Skarels if (n < 0) { 4716372Skarels perror("tftpd: recvfrom"); 488385Ssam exit(1); 498385Ssam } 5016372Skarels from.sin_family = AF_INET; 5116372Skarels alarm(0); 5216372Skarels close(0); 5316372Skarels close(1); 5416372Skarels peer = socket(AF_INET, SOCK_DGRAM, 0); 5516372Skarels if (peer < 0) { 56*17188Sralph openlog("tftpd", LOG_PID, 0); 57*17188Sralph syslog(LOG_ERR, "socket: %m"); 5816372Skarels exit(1); 597772Ssam } 6016372Skarels if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) { 61*17188Sralph openlog("tftpd", LOG_PID, 0); 62*17188Sralph syslog(LOG_ERR, "bind: %m"); 6316372Skarels exit(1); 6416372Skarels } 6516372Skarels if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) { 66*17188Sralph openlog("tftpd", LOG_PID, 0); 67*17188Sralph syslog(LOG_ERR, "connect: %m"); 6816372Skarels exit(1); 697772Ssam } 7016372Skarels tp = (struct tftphdr *)buf; 7116372Skarels tp->th_opcode = ntohs(tp->th_opcode); 7216372Skarels if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 7316372Skarels tftp(tp, n); 7416372Skarels exit(1); 757772Ssam } 767772Ssam 777772Ssam int validate_access(); 787772Ssam int sendfile(), recvfile(); 797772Ssam 807772Ssam struct formats { 817772Ssam char *f_mode; 827772Ssam int (*f_validate)(); 837772Ssam int (*f_send)(); 847772Ssam int (*f_recv)(); 857772Ssam } formats[] = { 867772Ssam { "netascii", validate_access, sendfile, recvfile }, 877772Ssam { "octet", validate_access, sendfile, recvfile }, 887772Ssam #ifdef notdef 897772Ssam { "mail", validate_user, sendmail, recvmail }, 907772Ssam #endif 917772Ssam { 0 } 927772Ssam }; 937772Ssam 947772Ssam /* 957772Ssam * Handle initial connection protocol. 967772Ssam */ 9716372Skarels tftp(tp, size) 987772Ssam struct tftphdr *tp; 997772Ssam int size; 1007772Ssam { 1017772Ssam register char *cp; 1027772Ssam int first = 1, ecode; 1037772Ssam register struct formats *pf; 1047772Ssam char *filename, *mode; 1057772Ssam 1067772Ssam filename = cp = tp->th_stuff; 1077772Ssam again: 1087772Ssam while (cp < buf + size) { 1097772Ssam if (*cp == '\0') 1107772Ssam break; 1117772Ssam cp++; 1127772Ssam } 1137772Ssam if (*cp != '\0') { 1147772Ssam nak(EBADOP); 1157772Ssam exit(1); 1167772Ssam } 1177772Ssam if (first) { 1187772Ssam mode = ++cp; 1197772Ssam first = 0; 1207772Ssam goto again; 1217772Ssam } 1227772Ssam for (cp = mode; *cp; cp++) 1237772Ssam if (isupper(*cp)) 1247772Ssam *cp = tolower(*cp); 1257772Ssam for (pf = formats; pf->f_mode; pf++) 1267772Ssam if (strcmp(pf->f_mode, mode) == 0) 1277772Ssam break; 1287772Ssam if (pf->f_mode == 0) { 1297772Ssam nak(EBADOP); 1307772Ssam exit(1); 1317772Ssam } 13216372Skarels ecode = (*pf->f_validate)(filename, tp->th_opcode); 1337772Ssam if (ecode) { 1347772Ssam nak(ecode); 1357772Ssam exit(1); 1367772Ssam } 1377772Ssam if (tp->th_opcode == WRQ) 1387772Ssam (*pf->f_recv)(pf); 1397772Ssam else 1407772Ssam (*pf->f_send)(pf); 1417772Ssam exit(0); 1427772Ssam } 1437772Ssam 14416372Skarels int fd; 14516372Skarels 1467772Ssam /* 1477772Ssam * Validate file access. Since we 1487772Ssam * have no uid or gid, for now require 1497772Ssam * file to exist and be publicly 1507772Ssam * readable/writable. 1517772Ssam * Note also, full path name must be 1527772Ssam * given as we have no login directory. 1537772Ssam */ 15416372Skarels validate_access(file, mode) 1557772Ssam char *file; 1567772Ssam int mode; 1577772Ssam { 1587772Ssam struct stat stbuf; 1597772Ssam 1607772Ssam if (*file != '/') 1617772Ssam return (EACCESS); 1627772Ssam if (stat(file, &stbuf) < 0) 1637772Ssam return (errno == ENOENT ? ENOTFOUND : EACCESS); 1647772Ssam if (mode == RRQ) { 1657772Ssam if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) 1667772Ssam return (EACCESS); 1677772Ssam } else { 1687772Ssam if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) 1697772Ssam return (EACCESS); 1707772Ssam } 1717772Ssam fd = open(file, mode == RRQ ? 0 : 1); 1727772Ssam if (fd < 0) 1737772Ssam return (errno + 100); 1747772Ssam return (0); 1757772Ssam } 1767772Ssam 17713020Ssam int timeout; 17813020Ssam jmp_buf timeoutbuf; 1797772Ssam 1807772Ssam timer() 1817772Ssam { 18213020Ssam 18313020Ssam timeout += rexmtval; 18413020Ssam if (timeout >= maxtimeout) 1857772Ssam exit(1); 18613020Ssam longjmp(timeoutbuf, 1); 1877772Ssam } 1887772Ssam 1897772Ssam /* 1907772Ssam * Send the requested file. 1917772Ssam */ 1927772Ssam sendfile(pf) 1937772Ssam struct format *pf; 1947772Ssam { 1957772Ssam register struct tftphdr *tp; 1967772Ssam register int block = 1, size, n; 1977772Ssam 19813020Ssam signal(SIGALRM, timer); 1997772Ssam tp = (struct tftphdr *)buf; 2007772Ssam do { 2017772Ssam size = read(fd, tp->th_data, SEGSIZE); 2027772Ssam if (size < 0) { 2037772Ssam nak(errno + 100); 20416372Skarels return; 2057772Ssam } 2067772Ssam tp->th_opcode = htons((u_short)DATA); 2077772Ssam tp->th_block = htons((u_short)block); 2087772Ssam timeout = 0; 20913020Ssam (void) setjmp(timeoutbuf); 21016372Skarels if (send(peer, buf, size + 4, 0) != size + 4) { 21116372Skarels perror("tftpd: send"); 21216372Skarels return; 2137772Ssam } 21413020Ssam do { 21513020Ssam alarm(rexmtval); 21616372Skarels n = recv(peer, buf, sizeof (buf), 0); 2177772Ssam alarm(0); 21813020Ssam if (n < 0) { 21916372Skarels perror("tftpd: recv"); 22016372Skarels return; 22113020Ssam } 22213020Ssam tp->th_opcode = ntohs((u_short)tp->th_opcode); 22313020Ssam tp->th_block = ntohs((u_short)tp->th_block); 22413020Ssam if (tp->th_opcode == ERROR) 22516372Skarels return; 22613020Ssam } while (tp->th_opcode != ACK || tp->th_block != block); 2277772Ssam block++; 2287772Ssam } while (size == SEGSIZE); 2297772Ssam } 2307772Ssam 2317772Ssam /* 2327772Ssam * Receive a file. 2337772Ssam */ 2347772Ssam recvfile(pf) 2357772Ssam struct format *pf; 2367772Ssam { 2377772Ssam register struct tftphdr *tp; 2387772Ssam register int block = 0, n, size; 2397772Ssam 24013020Ssam signal(SIGALRM, timer); 2417772Ssam tp = (struct tftphdr *)buf; 2427772Ssam do { 2437772Ssam timeout = 0; 2447772Ssam tp->th_opcode = htons((u_short)ACK); 2457772Ssam tp->th_block = htons((u_short)block); 2467772Ssam block++; 24713020Ssam (void) setjmp(timeoutbuf); 24816372Skarels if (send(peer, buf, 4, 0) != 4) { 24916372Skarels perror("tftpd: send"); 25013020Ssam goto abort; 2517772Ssam } 25213020Ssam do { 25313020Ssam alarm(rexmtval); 25416372Skarels n = recv(peer, buf, sizeof (buf), 0); 2557772Ssam alarm(0); 25613020Ssam if (n < 0) { 25716372Skarels perror("tftpd: recv"); 25813020Ssam goto abort; 25913020Ssam } 26013020Ssam tp->th_opcode = ntohs((u_short)tp->th_opcode); 26113020Ssam tp->th_block = ntohs((u_short)tp->th_block); 26213020Ssam if (tp->th_opcode == ERROR) 26313020Ssam goto abort; 26413020Ssam } while (tp->th_opcode != DATA || block != tp->th_block); 2657772Ssam size = write(fd, tp->th_data, n - 4); 2667772Ssam if (size < 0) { 2677772Ssam nak(errno + 100); 26813020Ssam goto abort; 2697772Ssam } 2707772Ssam } while (size == SEGSIZE); 27113020Ssam abort: 2727772Ssam tp->th_opcode = htons((u_short)ACK); 2737772Ssam tp->th_block = htons((u_short)(block)); 27416372Skarels (void) send(peer, buf, 4, 0); 2757772Ssam } 2767772Ssam 2777772Ssam struct errmsg { 2787772Ssam int e_code; 2797772Ssam char *e_msg; 2807772Ssam } errmsgs[] = { 2817772Ssam { EUNDEF, "Undefined error code" }, 2827772Ssam { ENOTFOUND, "File not found" }, 2837772Ssam { EACCESS, "Access violation" }, 2847772Ssam { ENOSPACE, "Disk full or allocation exceeded" }, 2857772Ssam { EBADOP, "Illegal TFTP operation" }, 2867772Ssam { EBADID, "Unknown transfer ID" }, 2877772Ssam { EEXISTS, "File already exists" }, 2887772Ssam { ENOUSER, "No such user" }, 2897772Ssam { -1, 0 } 2907772Ssam }; 2917772Ssam 2927772Ssam /* 2937772Ssam * Send a nak packet (error message). 2947772Ssam * Error code passed in is one of the 2957772Ssam * standard TFTP codes, or a UNIX errno 2967772Ssam * offset by 100. 2977772Ssam */ 2987772Ssam nak(error) 2997772Ssam int error; 3007772Ssam { 3017772Ssam register struct tftphdr *tp; 3027772Ssam int length; 3037772Ssam register struct errmsg *pe; 3047772Ssam extern char *sys_errlist[]; 3057772Ssam 3067772Ssam tp = (struct tftphdr *)buf; 3077772Ssam tp->th_opcode = htons((u_short)ERROR); 3087772Ssam tp->th_code = htons((u_short)error); 3097772Ssam for (pe = errmsgs; pe->e_code >= 0; pe++) 3107772Ssam if (pe->e_code == error) 3117772Ssam break; 3127772Ssam if (pe->e_code < 0) 3137772Ssam pe->e_msg = sys_errlist[error - 100]; 3147772Ssam strcpy(tp->th_msg, pe->e_msg); 3157772Ssam length = strlen(pe->e_msg); 3167772Ssam tp->th_msg[length] = '\0'; 3177772Ssam length += 5; 31816372Skarels if (send(peer, buf, length, 0) != length) 3197772Ssam perror("nak"); 32016372Skarels exit(1); 3217772Ssam } 322