1*13609Ssam #ifndef lint 2*13609Ssam static char sccsid[] = "@(#)tftpd.c 4.11 (Berkeley) 07/02/83"; 3*13609Ssam #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> 11*13609Ssam #include <sys/wait.h> 12*13609Ssam #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> 249220Ssam 2513020Ssam #define TIMEOUT 5 2613020Ssam 277772Ssam extern int errno; 288385Ssam struct sockaddr_in sin = { AF_INET }; 297772Ssam int f; 3013020Ssam int rexmtval = TIMEOUT; 3113020Ssam int maxtimeout = 5*TIMEOUT; 327772Ssam char buf[BUFSIZ]; 3313020Ssam int reapchild(); 347772Ssam 357772Ssam main(argc, argv) 367772Ssam char *argv[]; 377772Ssam { 387772Ssam struct sockaddr_in from; 397772Ssam register struct tftphdr *tp; 407772Ssam register int n; 418385Ssam struct servent *sp; 427772Ssam 438385Ssam sp = getservbyname("tftp", "udp"); 448385Ssam if (sp == 0) { 458385Ssam fprintf(stderr, "tftpd: udp/tftp: unknown service\n"); 468385Ssam exit(1); 478385Ssam } 489971Ssam sin.sin_port = sp->s_port; 497772Ssam #ifndef DEBUG 507772Ssam if (fork()) 517772Ssam exit(0); 527772Ssam for (f = 0; f < 10; f++) 537772Ssam (void) close(f); 547772Ssam (void) open("/", 0); 557772Ssam (void) dup2(0, 1); 567772Ssam (void) dup2(0, 2); 577772Ssam { int t = open("/dev/tty", 2); 587772Ssam if (t >= 0) { 597772Ssam ioctl(t, TIOCNOTTY, (char *)0); 607772Ssam (void) close(t); 617772Ssam } 627772Ssam } 637772Ssam #endif 6413020Ssam signal(SIGCHLD, reapchild); 657772Ssam for (;;) { 669220Ssam int fromlen; 679220Ssam 6813020Ssam f = socket(AF_INET, SOCK_DGRAM, 0); 697772Ssam if (f < 0) { 709220Ssam perror("tftpd: socket"); 717772Ssam sleep(5); 727772Ssam continue; 737772Ssam } 7413020Ssam if (setsockopt(f, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0) 7513020Ssam perror("tftpd: setsockopt (SO_REUSEADDR)"); 7613020Ssam sleep(1); /* let child do connect */ 779220Ssam while (bind(f, (caddr_t)&sin, sizeof (sin), 0) < 0) { 789220Ssam perror("tftpd: bind"); 799220Ssam sleep(5); 809220Ssam } 8113020Ssam do { 8213020Ssam fromlen = sizeof (from); 8313020Ssam n = recvfrom(f, buf, sizeof (buf), 0, 8413020Ssam (caddr_t)&from, &fromlen); 8513020Ssam } while (n <= 0); 867772Ssam tp = (struct tftphdr *)buf; 877772Ssam tp->th_opcode = ntohs(tp->th_opcode); 887772Ssam if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 897772Ssam if (fork() == 0) 907772Ssam tftp(&from, tp, n); 917772Ssam (void) close(f); 927772Ssam } 937772Ssam } 947772Ssam 9513020Ssam reapchild() 9613020Ssam { 9713020Ssam union wait status; 9813020Ssam 9913020Ssam while (wait3(&status, WNOHANG, 0) > 0) 10013020Ssam ; 10113020Ssam } 10213020Ssam 1037772Ssam int validate_access(); 1047772Ssam int sendfile(), recvfile(); 1057772Ssam 1067772Ssam struct formats { 1077772Ssam char *f_mode; 1087772Ssam int (*f_validate)(); 1097772Ssam int (*f_send)(); 1107772Ssam int (*f_recv)(); 1117772Ssam } formats[] = { 1127772Ssam { "netascii", validate_access, sendfile, recvfile }, 1137772Ssam { "octet", validate_access, sendfile, recvfile }, 1147772Ssam #ifdef notdef 1157772Ssam { "mail", validate_user, sendmail, recvmail }, 1167772Ssam #endif 1177772Ssam { 0 } 1187772Ssam }; 1197772Ssam 1207772Ssam int fd; /* file being transferred */ 1217772Ssam 1227772Ssam /* 1237772Ssam * Handle initial connection protocol. 1247772Ssam */ 1257772Ssam tftp(client, tp, size) 1267772Ssam struct sockaddr_in *client; 1277772Ssam struct tftphdr *tp; 1287772Ssam int size; 1297772Ssam { 1307772Ssam register char *cp; 1317772Ssam int first = 1, ecode; 1327772Ssam register struct formats *pf; 1337772Ssam char *filename, *mode; 1347772Ssam 1359220Ssam if (connect(f, (caddr_t)client, sizeof (*client), 0) < 0) { 1367772Ssam perror("connect"); 1377772Ssam exit(1); 1387772Ssam } 1397772Ssam filename = cp = tp->th_stuff; 1407772Ssam again: 1417772Ssam while (cp < buf + size) { 1427772Ssam if (*cp == '\0') 1437772Ssam break; 1447772Ssam cp++; 1457772Ssam } 1467772Ssam if (*cp != '\0') { 1477772Ssam nak(EBADOP); 1487772Ssam exit(1); 1497772Ssam } 1507772Ssam if (first) { 1517772Ssam mode = ++cp; 1527772Ssam first = 0; 1537772Ssam goto again; 1547772Ssam } 1557772Ssam for (cp = mode; *cp; cp++) 1567772Ssam if (isupper(*cp)) 1577772Ssam *cp = tolower(*cp); 1587772Ssam for (pf = formats; pf->f_mode; pf++) 1597772Ssam if (strcmp(pf->f_mode, mode) == 0) 1607772Ssam break; 1617772Ssam if (pf->f_mode == 0) { 1627772Ssam nak(EBADOP); 1637772Ssam exit(1); 1647772Ssam } 1657772Ssam ecode = (*pf->f_validate)(filename, client, tp->th_opcode); 1667772Ssam if (ecode) { 1677772Ssam nak(ecode); 1687772Ssam exit(1); 1697772Ssam } 1707772Ssam if (tp->th_opcode == WRQ) 1717772Ssam (*pf->f_recv)(pf); 1727772Ssam else 1737772Ssam (*pf->f_send)(pf); 1747772Ssam exit(0); 1757772Ssam } 1767772Ssam 1777772Ssam /* 1787772Ssam * Validate file access. Since we 1797772Ssam * have no uid or gid, for now require 1807772Ssam * file to exist and be publicly 1817772Ssam * readable/writable. 1827772Ssam * Note also, full path name must be 1837772Ssam * given as we have no login directory. 1847772Ssam */ 1857772Ssam validate_access(file, client, mode) 1867772Ssam char *file; 1877772Ssam struct sockaddr_in *client; 1887772Ssam int mode; 1897772Ssam { 1907772Ssam struct stat stbuf; 1917772Ssam 1927772Ssam if (*file != '/') 1937772Ssam return (EACCESS); 1947772Ssam if (stat(file, &stbuf) < 0) 1957772Ssam return (errno == ENOENT ? ENOTFOUND : EACCESS); 1967772Ssam if (mode == RRQ) { 1977772Ssam if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) 1987772Ssam return (EACCESS); 1997772Ssam } else { 2007772Ssam if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) 2017772Ssam return (EACCESS); 2027772Ssam } 2037772Ssam fd = open(file, mode == RRQ ? 0 : 1); 2047772Ssam if (fd < 0) 2057772Ssam return (errno + 100); 2067772Ssam return (0); 2077772Ssam } 2087772Ssam 20913020Ssam int timeout; 21013020Ssam jmp_buf timeoutbuf; 2117772Ssam 2127772Ssam timer() 2137772Ssam { 21413020Ssam 21513020Ssam timeout += rexmtval; 21613020Ssam if (timeout >= maxtimeout) 2177772Ssam exit(1); 21813020Ssam longjmp(timeoutbuf, 1); 2197772Ssam } 2207772Ssam 2217772Ssam /* 2227772Ssam * Send the requested file. 2237772Ssam */ 2247772Ssam sendfile(pf) 2257772Ssam struct format *pf; 2267772Ssam { 2277772Ssam register struct tftphdr *tp; 2287772Ssam register int block = 1, size, n; 2297772Ssam 23013020Ssam signal(SIGALRM, timer); 2317772Ssam tp = (struct tftphdr *)buf; 2327772Ssam do { 2337772Ssam size = read(fd, tp->th_data, SEGSIZE); 2347772Ssam if (size < 0) { 2357772Ssam nak(errno + 100); 23613020Ssam goto abort; 2377772Ssam } 2387772Ssam tp->th_opcode = htons((u_short)DATA); 2397772Ssam tp->th_block = htons((u_short)block); 2407772Ssam timeout = 0; 24113020Ssam (void) setjmp(timeoutbuf); 2427772Ssam if (write(f, buf, size + 4) != size + 4) { 24313020Ssam perror("tftpd: write"); 24413020Ssam goto abort; 2457772Ssam } 24613020Ssam do { 24713020Ssam alarm(rexmtval); 24813020Ssam n = read(f, buf, sizeof (buf)); 2497772Ssam alarm(0); 25013020Ssam if (n < 0) { 25113020Ssam perror("tftpd: read"); 25213020Ssam goto abort; 25313020Ssam } 25413020Ssam tp->th_opcode = ntohs((u_short)tp->th_opcode); 25513020Ssam tp->th_block = ntohs((u_short)tp->th_block); 25613020Ssam if (tp->th_opcode == ERROR) 25713020Ssam goto abort; 25813020Ssam } while (tp->th_opcode != ACK || tp->th_block != block); 2597772Ssam block++; 2607772Ssam } while (size == SEGSIZE); 26113020Ssam abort: 2627772Ssam (void) close(fd); 2637772Ssam } 2647772Ssam 2657772Ssam /* 2667772Ssam * Receive a file. 2677772Ssam */ 2687772Ssam recvfile(pf) 2697772Ssam struct format *pf; 2707772Ssam { 2717772Ssam register struct tftphdr *tp; 2727772Ssam register int block = 0, n, size; 2737772Ssam 27413020Ssam signal(SIGALRM, timer); 2757772Ssam tp = (struct tftphdr *)buf; 2767772Ssam do { 2777772Ssam timeout = 0; 2787772Ssam tp->th_opcode = htons((u_short)ACK); 2797772Ssam tp->th_block = htons((u_short)block); 2807772Ssam block++; 28113020Ssam (void) setjmp(timeoutbuf); 2827772Ssam if (write(f, buf, 4) != 4) { 28313020Ssam perror("tftpd: write"); 28413020Ssam goto abort; 2857772Ssam } 28613020Ssam do { 28713020Ssam alarm(rexmtval); 28813020Ssam n = read(f, buf, sizeof (buf)); 2897772Ssam alarm(0); 29013020Ssam if (n < 0) { 29113020Ssam perror("tftpd: read"); 29213020Ssam goto abort; 29313020Ssam } 29413020Ssam tp->th_opcode = ntohs((u_short)tp->th_opcode); 29513020Ssam tp->th_block = ntohs((u_short)tp->th_block); 29613020Ssam if (tp->th_opcode == ERROR) 29713020Ssam goto abort; 29813020Ssam } while (tp->th_opcode != DATA || block != tp->th_block); 2997772Ssam size = write(fd, tp->th_data, n - 4); 3007772Ssam if (size < 0) { 3017772Ssam nak(errno + 100); 30213020Ssam goto abort; 3037772Ssam } 3047772Ssam } while (size == SEGSIZE); 30513020Ssam abort: 3067772Ssam tp->th_opcode = htons((u_short)ACK); 3077772Ssam tp->th_block = htons((u_short)(block)); 3087772Ssam (void) write(f, buf, 4); 3097772Ssam (void) close(fd); 3107772Ssam } 3117772Ssam 3127772Ssam struct errmsg { 3137772Ssam int e_code; 3147772Ssam char *e_msg; 3157772Ssam } errmsgs[] = { 3167772Ssam { EUNDEF, "Undefined error code" }, 3177772Ssam { ENOTFOUND, "File not found" }, 3187772Ssam { EACCESS, "Access violation" }, 3197772Ssam { ENOSPACE, "Disk full or allocation exceeded" }, 3207772Ssam { EBADOP, "Illegal TFTP operation" }, 3217772Ssam { EBADID, "Unknown transfer ID" }, 3227772Ssam { EEXISTS, "File already exists" }, 3237772Ssam { ENOUSER, "No such user" }, 3247772Ssam { -1, 0 } 3257772Ssam }; 3267772Ssam 3277772Ssam /* 3287772Ssam * Send a nak packet (error message). 3297772Ssam * Error code passed in is one of the 3307772Ssam * standard TFTP codes, or a UNIX errno 3317772Ssam * offset by 100. 3327772Ssam */ 3337772Ssam nak(error) 3347772Ssam int error; 3357772Ssam { 3367772Ssam register struct tftphdr *tp; 3377772Ssam int length; 3387772Ssam register struct errmsg *pe; 3397772Ssam extern char *sys_errlist[]; 3407772Ssam 3417772Ssam tp = (struct tftphdr *)buf; 3427772Ssam tp->th_opcode = htons((u_short)ERROR); 3437772Ssam tp->th_code = htons((u_short)error); 3447772Ssam for (pe = errmsgs; pe->e_code >= 0; pe++) 3457772Ssam if (pe->e_code == error) 3467772Ssam break; 3477772Ssam if (pe->e_code < 0) 3487772Ssam pe->e_msg = sys_errlist[error - 100]; 3497772Ssam strcpy(tp->th_msg, pe->e_msg); 3507772Ssam length = strlen(pe->e_msg); 3517772Ssam tp->th_msg[length] = '\0'; 3527772Ssam length += 5; 3537772Ssam if (write(f, buf, length) != length) 3547772Ssam perror("nak"); 3557772Ssam } 356