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*24852Seric static char sccsid[] = "@(#)tftpd.c 5.2 (Berkeley) 09/17/85"; 1521183Sdist #endif not lint 1621183Sdist 177772Ssam /* 187772Ssam * Trivial file transfer protocol server. 197772Ssam */ 207772Ssam #include <sys/types.h> 217772Ssam #include <sys/socket.h> 229220Ssam #include <sys/ioctl.h> 2313609Ssam #include <sys/wait.h> 2413609Ssam #include <sys/stat.h> 259220Ssam 269220Ssam #include <netinet/in.h> 279220Ssam 2812217Ssam #include <arpa/tftp.h> 2912217Ssam 307772Ssam #include <signal.h> 317772Ssam #include <stdio.h> 327772Ssam #include <errno.h> 337772Ssam #include <ctype.h> 348385Ssam #include <netdb.h> 3513020Ssam #include <setjmp.h> 3617188Sralph #include <syslog.h> 379220Ssam 3813020Ssam #define TIMEOUT 5 3913020Ssam 407772Ssam extern int errno; 418385Ssam struct sockaddr_in sin = { AF_INET }; 4216372Skarels int peer; 4313020Ssam int rexmtval = TIMEOUT; 4413020Ssam int maxtimeout = 5*TIMEOUT; 457772Ssam char buf[BUFSIZ]; 4616372Skarels struct sockaddr_in from; 4716372Skarels int fromlen; 487772Ssam 4916372Skarels main() 507772Ssam { 517772Ssam register struct tftphdr *tp; 527772Ssam register int n; 537772Ssam 54*24852Seric openlog("tftpd", LOG_PID, LOG_DAEMON); 5516372Skarels alarm(10); 5616372Skarels fromlen = sizeof (from); 5716372Skarels n = recvfrom(0, buf, sizeof (buf), 0, 5816372Skarels (caddr_t)&from, &fromlen); 5916372Skarels if (n < 0) { 6016372Skarels perror("tftpd: recvfrom"); 618385Ssam exit(1); 628385Ssam } 6316372Skarels from.sin_family = AF_INET; 6416372Skarels alarm(0); 6516372Skarels close(0); 6616372Skarels close(1); 6716372Skarels peer = socket(AF_INET, SOCK_DGRAM, 0); 6816372Skarels if (peer < 0) { 6917188Sralph syslog(LOG_ERR, "socket: %m"); 7016372Skarels exit(1); 717772Ssam } 7216372Skarels if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) { 7317188Sralph syslog(LOG_ERR, "bind: %m"); 7416372Skarels exit(1); 7516372Skarels } 7616372Skarels if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) { 7717188Sralph syslog(LOG_ERR, "connect: %m"); 7816372Skarels exit(1); 797772Ssam } 8016372Skarels tp = (struct tftphdr *)buf; 8116372Skarels tp->th_opcode = ntohs(tp->th_opcode); 8216372Skarels if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 8316372Skarels tftp(tp, n); 8416372Skarels exit(1); 857772Ssam } 867772Ssam 877772Ssam int validate_access(); 887772Ssam int sendfile(), recvfile(); 897772Ssam 907772Ssam struct formats { 917772Ssam char *f_mode; 927772Ssam int (*f_validate)(); 937772Ssam int (*f_send)(); 947772Ssam int (*f_recv)(); 957772Ssam } formats[] = { 967772Ssam { "netascii", validate_access, sendfile, recvfile }, 977772Ssam { "octet", validate_access, sendfile, recvfile }, 987772Ssam #ifdef notdef 997772Ssam { "mail", validate_user, sendmail, recvmail }, 1007772Ssam #endif 1017772Ssam { 0 } 1027772Ssam }; 1037772Ssam 1047772Ssam /* 1057772Ssam * Handle initial connection protocol. 1067772Ssam */ 10716372Skarels tftp(tp, size) 1087772Ssam struct tftphdr *tp; 1097772Ssam int size; 1107772Ssam { 1117772Ssam register char *cp; 1127772Ssam int first = 1, ecode; 1137772Ssam register struct formats *pf; 1147772Ssam char *filename, *mode; 1157772Ssam 1167772Ssam filename = cp = tp->th_stuff; 1177772Ssam again: 1187772Ssam while (cp < buf + size) { 1197772Ssam if (*cp == '\0') 1207772Ssam break; 1217772Ssam cp++; 1227772Ssam } 1237772Ssam if (*cp != '\0') { 1247772Ssam nak(EBADOP); 1257772Ssam exit(1); 1267772Ssam } 1277772Ssam if (first) { 1287772Ssam mode = ++cp; 1297772Ssam first = 0; 1307772Ssam goto again; 1317772Ssam } 1327772Ssam for (cp = mode; *cp; cp++) 1337772Ssam if (isupper(*cp)) 1347772Ssam *cp = tolower(*cp); 1357772Ssam for (pf = formats; pf->f_mode; pf++) 1367772Ssam if (strcmp(pf->f_mode, mode) == 0) 1377772Ssam break; 1387772Ssam if (pf->f_mode == 0) { 1397772Ssam nak(EBADOP); 1407772Ssam exit(1); 1417772Ssam } 14216372Skarels ecode = (*pf->f_validate)(filename, tp->th_opcode); 1437772Ssam if (ecode) { 1447772Ssam nak(ecode); 1457772Ssam exit(1); 1467772Ssam } 1477772Ssam if (tp->th_opcode == WRQ) 1487772Ssam (*pf->f_recv)(pf); 1497772Ssam else 1507772Ssam (*pf->f_send)(pf); 1517772Ssam exit(0); 1527772Ssam } 1537772Ssam 15416372Skarels int fd; 15516372Skarels 1567772Ssam /* 1577772Ssam * Validate file access. Since we 1587772Ssam * have no uid or gid, for now require 1597772Ssam * file to exist and be publicly 1607772Ssam * readable/writable. 1617772Ssam * Note also, full path name must be 1627772Ssam * given as we have no login directory. 1637772Ssam */ 16416372Skarels validate_access(file, mode) 1657772Ssam char *file; 1667772Ssam int mode; 1677772Ssam { 1687772Ssam struct stat stbuf; 1697772Ssam 1707772Ssam if (*file != '/') 1717772Ssam return (EACCESS); 1727772Ssam if (stat(file, &stbuf) < 0) 1737772Ssam return (errno == ENOENT ? ENOTFOUND : EACCESS); 1747772Ssam if (mode == RRQ) { 1757772Ssam if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) 1767772Ssam return (EACCESS); 1777772Ssam } else { 1787772Ssam if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) 1797772Ssam return (EACCESS); 1807772Ssam } 1817772Ssam fd = open(file, mode == RRQ ? 0 : 1); 1827772Ssam if (fd < 0) 1837772Ssam return (errno + 100); 1847772Ssam return (0); 1857772Ssam } 1867772Ssam 18713020Ssam int timeout; 18813020Ssam jmp_buf timeoutbuf; 1897772Ssam 1907772Ssam timer() 1917772Ssam { 19213020Ssam 19313020Ssam timeout += rexmtval; 19413020Ssam if (timeout >= maxtimeout) 1957772Ssam exit(1); 19613020Ssam longjmp(timeoutbuf, 1); 1977772Ssam } 1987772Ssam 1997772Ssam /* 2007772Ssam * Send the requested file. 2017772Ssam */ 2027772Ssam sendfile(pf) 2037772Ssam struct format *pf; 2047772Ssam { 2057772Ssam register struct tftphdr *tp; 2067772Ssam register int block = 1, size, n; 2077772Ssam 20813020Ssam signal(SIGALRM, timer); 2097772Ssam tp = (struct tftphdr *)buf; 2107772Ssam do { 2117772Ssam size = read(fd, tp->th_data, SEGSIZE); 2127772Ssam if (size < 0) { 2137772Ssam nak(errno + 100); 21416372Skarels return; 2157772Ssam } 2167772Ssam tp->th_opcode = htons((u_short)DATA); 2177772Ssam tp->th_block = htons((u_short)block); 2187772Ssam timeout = 0; 21913020Ssam (void) setjmp(timeoutbuf); 22016372Skarels if (send(peer, buf, size + 4, 0) != size + 4) { 22116372Skarels perror("tftpd: send"); 22216372Skarels return; 2237772Ssam } 22413020Ssam do { 22513020Ssam alarm(rexmtval); 22616372Skarels n = recv(peer, buf, sizeof (buf), 0); 2277772Ssam alarm(0); 22813020Ssam if (n < 0) { 22916372Skarels perror("tftpd: recv"); 23016372Skarels return; 23113020Ssam } 23213020Ssam tp->th_opcode = ntohs((u_short)tp->th_opcode); 23313020Ssam tp->th_block = ntohs((u_short)tp->th_block); 23413020Ssam if (tp->th_opcode == ERROR) 23516372Skarels return; 23613020Ssam } while (tp->th_opcode != ACK || tp->th_block != block); 2377772Ssam block++; 2387772Ssam } while (size == SEGSIZE); 2397772Ssam } 2407772Ssam 2417772Ssam /* 2427772Ssam * Receive a file. 2437772Ssam */ 2447772Ssam recvfile(pf) 2457772Ssam struct format *pf; 2467772Ssam { 2477772Ssam register struct tftphdr *tp; 2487772Ssam register int block = 0, n, size; 2497772Ssam 25013020Ssam signal(SIGALRM, timer); 2517772Ssam tp = (struct tftphdr *)buf; 2527772Ssam do { 2537772Ssam timeout = 0; 2547772Ssam tp->th_opcode = htons((u_short)ACK); 2557772Ssam tp->th_block = htons((u_short)block); 2567772Ssam block++; 25713020Ssam (void) setjmp(timeoutbuf); 25816372Skarels if (send(peer, buf, 4, 0) != 4) { 25916372Skarels perror("tftpd: send"); 26013020Ssam goto abort; 2617772Ssam } 26213020Ssam do { 26313020Ssam alarm(rexmtval); 26416372Skarels n = recv(peer, buf, sizeof (buf), 0); 2657772Ssam alarm(0); 26613020Ssam if (n < 0) { 26716372Skarels perror("tftpd: recv"); 26813020Ssam goto abort; 26913020Ssam } 27013020Ssam tp->th_opcode = ntohs((u_short)tp->th_opcode); 27113020Ssam tp->th_block = ntohs((u_short)tp->th_block); 27213020Ssam if (tp->th_opcode == ERROR) 27313020Ssam goto abort; 27413020Ssam } while (tp->th_opcode != DATA || block != tp->th_block); 2757772Ssam size = write(fd, tp->th_data, n - 4); 2767772Ssam if (size < 0) { 2777772Ssam nak(errno + 100); 27813020Ssam goto abort; 2797772Ssam } 2807772Ssam } while (size == SEGSIZE); 28113020Ssam abort: 2827772Ssam tp->th_opcode = htons((u_short)ACK); 2837772Ssam tp->th_block = htons((u_short)(block)); 28416372Skarels (void) send(peer, buf, 4, 0); 2857772Ssam } 2867772Ssam 2877772Ssam struct errmsg { 2887772Ssam int e_code; 2897772Ssam char *e_msg; 2907772Ssam } errmsgs[] = { 2917772Ssam { EUNDEF, "Undefined error code" }, 2927772Ssam { ENOTFOUND, "File not found" }, 2937772Ssam { EACCESS, "Access violation" }, 2947772Ssam { ENOSPACE, "Disk full or allocation exceeded" }, 2957772Ssam { EBADOP, "Illegal TFTP operation" }, 2967772Ssam { EBADID, "Unknown transfer ID" }, 2977772Ssam { EEXISTS, "File already exists" }, 2987772Ssam { ENOUSER, "No such user" }, 2997772Ssam { -1, 0 } 3007772Ssam }; 3017772Ssam 3027772Ssam /* 3037772Ssam * Send a nak packet (error message). 3047772Ssam * Error code passed in is one of the 3057772Ssam * standard TFTP codes, or a UNIX errno 3067772Ssam * offset by 100. 3077772Ssam */ 3087772Ssam nak(error) 3097772Ssam int error; 3107772Ssam { 3117772Ssam register struct tftphdr *tp; 3127772Ssam int length; 3137772Ssam register struct errmsg *pe; 3147772Ssam extern char *sys_errlist[]; 3157772Ssam 3167772Ssam tp = (struct tftphdr *)buf; 3177772Ssam tp->th_opcode = htons((u_short)ERROR); 3187772Ssam tp->th_code = htons((u_short)error); 3197772Ssam for (pe = errmsgs; pe->e_code >= 0; pe++) 3207772Ssam if (pe->e_code == error) 3217772Ssam break; 3227772Ssam if (pe->e_code < 0) 3237772Ssam pe->e_msg = sys_errlist[error - 100]; 3247772Ssam strcpy(tp->th_msg, pe->e_msg); 3257772Ssam length = strlen(pe->e_msg); 3267772Ssam tp->th_msg[length] = '\0'; 3277772Ssam length += 5; 32816372Skarels if (send(peer, buf, length, 0) != length) 3297772Ssam perror("nak"); 33016372Skarels exit(1); 3317772Ssam } 332