1*13020Ssam /* tftpd.c 4.9 83/06/12 */ 27772Ssam 37772Ssam /* 47772Ssam * Trivial file transfer protocol server. 57772Ssam */ 67772Ssam #include <sys/types.h> 77772Ssam #include <sys/socket.h> 89220Ssam #include <sys/ioctl.h> 99220Ssam 109220Ssam #include <netinet/in.h> 119220Ssam 1212217Ssam #include <arpa/tftp.h> 1312217Ssam 147772Ssam #include <signal.h> 157772Ssam #include <stat.h> 167772Ssam #include <stdio.h> 177772Ssam #include <wait.h> 187772Ssam #include <errno.h> 197772Ssam #include <ctype.h> 208385Ssam #include <netdb.h> 21*13020Ssam #include <setjmp.h> 229220Ssam 23*13020Ssam #define DEBUG 1 24*13020Ssam #define TIMEOUT 5 25*13020Ssam 267772Ssam extern int errno; 278385Ssam struct sockaddr_in sin = { AF_INET }; 287772Ssam int f; 29*13020Ssam int rexmtval = TIMEOUT; 30*13020Ssam int maxtimeout = 5*TIMEOUT; 317772Ssam char buf[BUFSIZ]; 32*13020Ssam int reapchild(); 337772Ssam 347772Ssam main(argc, argv) 357772Ssam char *argv[]; 367772Ssam { 377772Ssam struct sockaddr_in from; 387772Ssam register struct tftphdr *tp; 397772Ssam register int n; 408385Ssam struct servent *sp; 417772Ssam 428385Ssam sp = getservbyname("tftp", "udp"); 438385Ssam if (sp == 0) { 448385Ssam fprintf(stderr, "tftpd: udp/tftp: unknown service\n"); 458385Ssam exit(1); 468385Ssam } 479971Ssam sin.sin_port = sp->s_port; 487772Ssam #ifndef DEBUG 497772Ssam if (fork()) 507772Ssam exit(0); 517772Ssam for (f = 0; f < 10; f++) 527772Ssam (void) close(f); 537772Ssam (void) open("/", 0); 547772Ssam (void) dup2(0, 1); 557772Ssam (void) dup2(0, 2); 567772Ssam { int t = open("/dev/tty", 2); 577772Ssam if (t >= 0) { 587772Ssam ioctl(t, TIOCNOTTY, (char *)0); 597772Ssam (void) close(t); 607772Ssam } 617772Ssam } 627772Ssam #endif 63*13020Ssam signal(SIGCHLD, reapchild); 647772Ssam for (;;) { 659220Ssam int fromlen; 669220Ssam 67*13020Ssam f = socket(AF_INET, SOCK_DGRAM, 0); 687772Ssam if (f < 0) { 699220Ssam perror("tftpd: socket"); 707772Ssam sleep(5); 717772Ssam continue; 727772Ssam } 73*13020Ssam if (setsockopt(f, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0) 74*13020Ssam perror("tftpd: setsockopt (SO_REUSEADDR)"); 75*13020Ssam sleep(1); /* let child do connect */ 769220Ssam while (bind(f, (caddr_t)&sin, sizeof (sin), 0) < 0) { 779220Ssam perror("tftpd: bind"); 789220Ssam sleep(5); 799220Ssam } 80*13020Ssam do { 81*13020Ssam fromlen = sizeof (from); 82*13020Ssam n = recvfrom(f, buf, sizeof (buf), 0, 83*13020Ssam (caddr_t)&from, &fromlen); 84*13020Ssam } while (n <= 0); 857772Ssam tp = (struct tftphdr *)buf; 867772Ssam tp->th_opcode = ntohs(tp->th_opcode); 877772Ssam if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 887772Ssam if (fork() == 0) 897772Ssam tftp(&from, tp, n); 907772Ssam (void) close(f); 917772Ssam } 927772Ssam } 937772Ssam 94*13020Ssam reapchild() 95*13020Ssam { 96*13020Ssam union wait status; 97*13020Ssam 98*13020Ssam while (wait3(&status, WNOHANG, 0) > 0) 99*13020Ssam ; 100*13020Ssam } 101*13020Ssam 1027772Ssam int validate_access(); 1037772Ssam int sendfile(), recvfile(); 1047772Ssam 1057772Ssam struct formats { 1067772Ssam char *f_mode; 1077772Ssam int (*f_validate)(); 1087772Ssam int (*f_send)(); 1097772Ssam int (*f_recv)(); 1107772Ssam } formats[] = { 1117772Ssam { "netascii", validate_access, sendfile, recvfile }, 1127772Ssam { "octet", validate_access, sendfile, recvfile }, 1137772Ssam #ifdef notdef 1147772Ssam { "mail", validate_user, sendmail, recvmail }, 1157772Ssam #endif 1167772Ssam { 0 } 1177772Ssam }; 1187772Ssam 1197772Ssam int fd; /* file being transferred */ 1207772Ssam 1217772Ssam /* 1227772Ssam * Handle initial connection protocol. 1237772Ssam */ 1247772Ssam tftp(client, tp, size) 1257772Ssam struct sockaddr_in *client; 1267772Ssam struct tftphdr *tp; 1277772Ssam int size; 1287772Ssam { 1297772Ssam register char *cp; 1307772Ssam int first = 1, ecode; 1317772Ssam register struct formats *pf; 1327772Ssam char *filename, *mode; 1337772Ssam 1349220Ssam if (connect(f, (caddr_t)client, sizeof (*client), 0) < 0) { 1357772Ssam perror("connect"); 1367772Ssam exit(1); 1377772Ssam } 1387772Ssam filename = cp = tp->th_stuff; 1397772Ssam again: 1407772Ssam while (cp < buf + size) { 1417772Ssam if (*cp == '\0') 1427772Ssam break; 1437772Ssam cp++; 1447772Ssam } 1457772Ssam if (*cp != '\0') { 1467772Ssam nak(EBADOP); 1477772Ssam exit(1); 1487772Ssam } 1497772Ssam if (first) { 1507772Ssam mode = ++cp; 1517772Ssam first = 0; 1527772Ssam goto again; 1537772Ssam } 1547772Ssam for (cp = mode; *cp; cp++) 1557772Ssam if (isupper(*cp)) 1567772Ssam *cp = tolower(*cp); 1577772Ssam for (pf = formats; pf->f_mode; pf++) 1587772Ssam if (strcmp(pf->f_mode, mode) == 0) 1597772Ssam break; 1607772Ssam if (pf->f_mode == 0) { 1617772Ssam nak(EBADOP); 1627772Ssam exit(1); 1637772Ssam } 1647772Ssam ecode = (*pf->f_validate)(filename, client, tp->th_opcode); 1657772Ssam if (ecode) { 1667772Ssam nak(ecode); 1677772Ssam exit(1); 1687772Ssam } 1697772Ssam if (tp->th_opcode == WRQ) 1707772Ssam (*pf->f_recv)(pf); 1717772Ssam else 1727772Ssam (*pf->f_send)(pf); 1737772Ssam exit(0); 1747772Ssam } 1757772Ssam 1767772Ssam /* 1777772Ssam * Validate file access. Since we 1787772Ssam * have no uid or gid, for now require 1797772Ssam * file to exist and be publicly 1807772Ssam * readable/writable. 1817772Ssam * Note also, full path name must be 1827772Ssam * given as we have no login directory. 1837772Ssam */ 1847772Ssam validate_access(file, client, mode) 1857772Ssam char *file; 1867772Ssam struct sockaddr_in *client; 1877772Ssam int mode; 1887772Ssam { 1897772Ssam struct stat stbuf; 1907772Ssam 1917772Ssam if (*file != '/') 1927772Ssam return (EACCESS); 1937772Ssam if (stat(file, &stbuf) < 0) 1947772Ssam return (errno == ENOENT ? ENOTFOUND : EACCESS); 1957772Ssam if (mode == RRQ) { 1967772Ssam if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) 1977772Ssam return (EACCESS); 1987772Ssam } else { 1997772Ssam if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) 2007772Ssam return (EACCESS); 2017772Ssam } 2027772Ssam fd = open(file, mode == RRQ ? 0 : 1); 2037772Ssam if (fd < 0) 2047772Ssam return (errno + 100); 2057772Ssam return (0); 2067772Ssam } 2077772Ssam 208*13020Ssam int timeout; 209*13020Ssam jmp_buf timeoutbuf; 2107772Ssam 2117772Ssam timer() 2127772Ssam { 213*13020Ssam 214*13020Ssam timeout += rexmtval; 215*13020Ssam if (timeout >= maxtimeout) 2167772Ssam exit(1); 217*13020Ssam longjmp(timeoutbuf, 1); 2187772Ssam } 2197772Ssam 2207772Ssam /* 2217772Ssam * Send the requested file. 2227772Ssam */ 2237772Ssam sendfile(pf) 2247772Ssam struct format *pf; 2257772Ssam { 2267772Ssam register struct tftphdr *tp; 2277772Ssam register int block = 1, size, n; 2287772Ssam 229*13020Ssam signal(SIGALRM, timer); 2307772Ssam tp = (struct tftphdr *)buf; 2317772Ssam do { 2327772Ssam size = read(fd, tp->th_data, SEGSIZE); 2337772Ssam if (size < 0) { 2347772Ssam nak(errno + 100); 235*13020Ssam goto abort; 2367772Ssam } 2377772Ssam tp->th_opcode = htons((u_short)DATA); 2387772Ssam tp->th_block = htons((u_short)block); 2397772Ssam timeout = 0; 240*13020Ssam (void) setjmp(timeoutbuf); 2417772Ssam if (write(f, buf, size + 4) != size + 4) { 242*13020Ssam perror("tftpd: write"); 243*13020Ssam goto abort; 2447772Ssam } 245*13020Ssam do { 246*13020Ssam alarm(rexmtval); 247*13020Ssam n = read(f, buf, sizeof (buf)); 2487772Ssam alarm(0); 249*13020Ssam if (n < 0) { 250*13020Ssam perror("tftpd: read"); 251*13020Ssam goto abort; 252*13020Ssam } 253*13020Ssam tp->th_opcode = ntohs((u_short)tp->th_opcode); 254*13020Ssam tp->th_block = ntohs((u_short)tp->th_block); 255*13020Ssam if (tp->th_opcode == ERROR) 256*13020Ssam goto abort; 257*13020Ssam } while (tp->th_opcode != ACK || tp->th_block != block); 2587772Ssam block++; 2597772Ssam } while (size == SEGSIZE); 260*13020Ssam abort: 2617772Ssam (void) close(fd); 2627772Ssam } 2637772Ssam 2647772Ssam /* 2657772Ssam * Receive a file. 2667772Ssam */ 2677772Ssam recvfile(pf) 2687772Ssam struct format *pf; 2697772Ssam { 2707772Ssam register struct tftphdr *tp; 2717772Ssam register int block = 0, n, size; 2727772Ssam 273*13020Ssam signal(SIGALRM, timer); 2747772Ssam tp = (struct tftphdr *)buf; 2757772Ssam do { 2767772Ssam timeout = 0; 2777772Ssam tp->th_opcode = htons((u_short)ACK); 2787772Ssam tp->th_block = htons((u_short)block); 2797772Ssam block++; 280*13020Ssam (void) setjmp(timeoutbuf); 2817772Ssam if (write(f, buf, 4) != 4) { 282*13020Ssam perror("tftpd: write"); 283*13020Ssam goto abort; 2847772Ssam } 285*13020Ssam do { 286*13020Ssam alarm(rexmtval); 287*13020Ssam n = read(f, buf, sizeof (buf)); 2887772Ssam alarm(0); 289*13020Ssam if (n < 0) { 290*13020Ssam perror("tftpd: read"); 291*13020Ssam goto abort; 292*13020Ssam } 293*13020Ssam tp->th_opcode = ntohs((u_short)tp->th_opcode); 294*13020Ssam tp->th_block = ntohs((u_short)tp->th_block); 295*13020Ssam if (tp->th_opcode == ERROR) 296*13020Ssam goto abort; 297*13020Ssam } while (tp->th_opcode != DATA || block != tp->th_block); 2987772Ssam size = write(fd, tp->th_data, n - 4); 2997772Ssam if (size < 0) { 3007772Ssam nak(errno + 100); 301*13020Ssam goto abort; 3027772Ssam } 3037772Ssam } while (size == SEGSIZE); 304*13020Ssam abort: 3057772Ssam tp->th_opcode = htons((u_short)ACK); 3067772Ssam tp->th_block = htons((u_short)(block)); 3077772Ssam (void) write(f, buf, 4); 3087772Ssam (void) close(fd); 3097772Ssam } 3107772Ssam 3117772Ssam struct errmsg { 3127772Ssam int e_code; 3137772Ssam char *e_msg; 3147772Ssam } errmsgs[] = { 3157772Ssam { EUNDEF, "Undefined error code" }, 3167772Ssam { ENOTFOUND, "File not found" }, 3177772Ssam { EACCESS, "Access violation" }, 3187772Ssam { ENOSPACE, "Disk full or allocation exceeded" }, 3197772Ssam { EBADOP, "Illegal TFTP operation" }, 3207772Ssam { EBADID, "Unknown transfer ID" }, 3217772Ssam { EEXISTS, "File already exists" }, 3227772Ssam { ENOUSER, "No such user" }, 3237772Ssam { -1, 0 } 3247772Ssam }; 3257772Ssam 3267772Ssam /* 3277772Ssam * Send a nak packet (error message). 3287772Ssam * Error code passed in is one of the 3297772Ssam * standard TFTP codes, or a UNIX errno 3307772Ssam * offset by 100. 3317772Ssam */ 3327772Ssam nak(error) 3337772Ssam int error; 3347772Ssam { 3357772Ssam register struct tftphdr *tp; 3367772Ssam int length; 3377772Ssam register struct errmsg *pe; 3387772Ssam extern char *sys_errlist[]; 3397772Ssam 3407772Ssam tp = (struct tftphdr *)buf; 3417772Ssam tp->th_opcode = htons((u_short)ERROR); 3427772Ssam tp->th_code = htons((u_short)error); 3437772Ssam for (pe = errmsgs; pe->e_code >= 0; pe++) 3447772Ssam if (pe->e_code == error) 3457772Ssam break; 3467772Ssam if (pe->e_code < 0) 3477772Ssam pe->e_msg = sys_errlist[error - 100]; 3487772Ssam strcpy(tp->th_msg, pe->e_msg); 3497772Ssam length = strlen(pe->e_msg); 3507772Ssam tp->th_msg[length] = '\0'; 3517772Ssam length += 5; 3527772Ssam if (write(f, buf, length) != length) 3537772Ssam perror("nak"); 3547772Ssam } 355