1*8385Ssam /* tftpd.c 4.3 82/10/08 */ 27772Ssam 37772Ssam /* 47772Ssam * Trivial file transfer protocol server. 57772Ssam */ 67772Ssam #include <sys/types.h> 77772Ssam #include <net/in.h> 87772Ssam #include <sys/socket.h> 97772Ssam #include <signal.h> 107772Ssam #include <sys/ioctl.h> 117772Ssam #include <stat.h> 127772Ssam #include <stdio.h> 137772Ssam #include <wait.h> 147772Ssam #include <errno.h> 157772Ssam #include <ctype.h> 16*8385Ssam #include <netdb.h> 177772Ssam #include "tftp.h" 187772Ssam 197772Ssam extern int errno; 20*8385Ssam struct sockaddr_in sin = { AF_INET }; 217772Ssam int f; 227772Ssam int options; 237772Ssam char buf[BUFSIZ]; 247772Ssam 257772Ssam main(argc, argv) 267772Ssam char *argv[]; 277772Ssam { 287772Ssam union wait status; 297772Ssam struct sockaddr_in from; 307772Ssam register struct tftphdr *tp; 317772Ssam register int n; 32*8385Ssam struct servent *sp; 337772Ssam 34*8385Ssam sp = getservbyname("tftp", "udp"); 35*8385Ssam if (sp == 0) { 36*8385Ssam fprintf(stderr, "tftpd: udp/tftp: unknown service\n"); 37*8385Ssam exit(1); 38*8385Ssam } 39*8385Ssam sin.sin_port = htons(sp->s_port); 407772Ssam #ifndef DEBUG 417772Ssam if (fork()) 427772Ssam exit(0); 437772Ssam for (f = 0; f < 10; f++) 447772Ssam (void) close(f); 457772Ssam (void) open("/", 0); 467772Ssam (void) dup2(0, 1); 477772Ssam (void) dup2(0, 2); 487772Ssam { int t = open("/dev/tty", 2); 497772Ssam if (t >= 0) { 507772Ssam ioctl(t, TIOCNOTTY, (char *)0); 517772Ssam (void) close(t); 527772Ssam } 537772Ssam } 547772Ssam #endif 557772Ssam argc--, argv++; 567772Ssam if (argc > 0 && !strcmp(argv[0], "-d")) 577772Ssam options |= SO_DEBUG; 587772Ssam for (;;) { 597772Ssam errno = 0; 607772Ssam f = socket(SOCK_DGRAM, 0, &sin, options); 617772Ssam if (f < 0) { 627772Ssam perror("socket"); 637772Ssam sleep(5); 647772Ssam continue; 657772Ssam } 667772Ssam again: 677772Ssam n = receive(f, &from, buf, sizeof (buf)); 687772Ssam if (n <= 0) { 697772Ssam if (n < 0) 707772Ssam perror("receive"); 717772Ssam goto again; 727772Ssam } 737772Ssam tp = (struct tftphdr *)buf; 747772Ssam #if vax || pdp11 757772Ssam tp->th_opcode = ntohs(tp->th_opcode); 767772Ssam #endif 777772Ssam if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 787772Ssam if (fork() == 0) 797772Ssam tftp(&from, tp, n); 807772Ssam (void) close(f); 817772Ssam #ifdef notdef 827772Ssam while (wait3(status, WNOHANG, 0) > 0) 837772Ssam #else 847772Ssam while (wait3(status, 0, 0) > 0) 857772Ssam continue; 867772Ssam } 877772Ssam } 887772Ssam 897772Ssam int validate_access(); 907772Ssam int sendfile(), recvfile(); 917772Ssam 927772Ssam struct formats { 937772Ssam char *f_mode; 947772Ssam int (*f_validate)(); 957772Ssam int (*f_send)(); 967772Ssam int (*f_recv)(); 977772Ssam } formats[] = { 987772Ssam { "netascii", validate_access, sendfile, recvfile }, 997772Ssam { "octet", validate_access, sendfile, recvfile }, 1007772Ssam #ifdef notdef 1017772Ssam { "mail", validate_user, sendmail, recvmail }, 1027772Ssam #endif 1037772Ssam { 0 } 1047772Ssam }; 1057772Ssam 1067772Ssam int fd; /* file being transferred */ 1077772Ssam 1087772Ssam /* 1097772Ssam * Handle initial connection protocol. 1107772Ssam */ 1117772Ssam tftp(client, tp, size) 1127772Ssam struct sockaddr_in *client; 1137772Ssam struct tftphdr *tp; 1147772Ssam int size; 1157772Ssam { 1167772Ssam register char *cp; 1177772Ssam int first = 1, ecode; 1187772Ssam register struct formats *pf; 1197772Ssam char *filename, *mode; 1207772Ssam 1217772Ssam if (connect(f, client) < 0) { 1227772Ssam perror("connect"); 1237772Ssam exit(1); 1247772Ssam } 1257772Ssam filename = cp = tp->th_stuff; 1267772Ssam again: 1277772Ssam while (cp < buf + size) { 1287772Ssam if (*cp == '\0') 1297772Ssam break; 1307772Ssam cp++; 1317772Ssam } 1327772Ssam if (*cp != '\0') { 1337772Ssam nak(EBADOP); 1347772Ssam exit(1); 1357772Ssam } 1367772Ssam if (first) { 1377772Ssam mode = ++cp; 1387772Ssam first = 0; 1397772Ssam goto again; 1407772Ssam } 1417772Ssam for (cp = mode; *cp; cp++) 1427772Ssam if (isupper(*cp)) 1437772Ssam *cp = tolower(*cp); 1447772Ssam for (pf = formats; pf->f_mode; pf++) 1457772Ssam if (strcmp(pf->f_mode, mode) == 0) 1467772Ssam break; 1477772Ssam if (pf->f_mode == 0) { 1487772Ssam nak(EBADOP); 1497772Ssam exit(1); 1507772Ssam } 1517772Ssam ecode = (*pf->f_validate)(filename, client, tp->th_opcode); 1527772Ssam if (ecode) { 1537772Ssam nak(ecode); 1547772Ssam exit(1); 1557772Ssam } 1567772Ssam if (tp->th_opcode == WRQ) 1577772Ssam (*pf->f_recv)(pf); 1587772Ssam else 1597772Ssam (*pf->f_send)(pf); 1607772Ssam exit(0); 1617772Ssam } 1627772Ssam 1637772Ssam /* 1647772Ssam * Validate file access. Since we 1657772Ssam * have no uid or gid, for now require 1667772Ssam * file to exist and be publicly 1677772Ssam * readable/writable. 1687772Ssam * Note also, full path name must be 1697772Ssam * given as we have no login directory. 1707772Ssam */ 1717772Ssam validate_access(file, client, mode) 1727772Ssam char *file; 1737772Ssam struct sockaddr_in *client; 1747772Ssam int mode; 1757772Ssam { 1767772Ssam struct stat stbuf; 1777772Ssam 1787772Ssam if (*file != '/') 1797772Ssam return (EACCESS); 1807772Ssam if (stat(file, &stbuf) < 0) 1817772Ssam return (errno == ENOENT ? ENOTFOUND : EACCESS); 1827772Ssam if (mode == RRQ) { 1837772Ssam if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) 1847772Ssam return (EACCESS); 1857772Ssam } else { 1867772Ssam if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) 1877772Ssam return (EACCESS); 1887772Ssam } 1897772Ssam fd = open(file, mode == RRQ ? 0 : 1); 1907772Ssam if (fd < 0) 1917772Ssam return (errno + 100); 1927772Ssam return (0); 1937772Ssam } 1947772Ssam 1957772Ssam int timeout; 1967772Ssam 1977772Ssam timer() 1987772Ssam { 1997772Ssam timeout += TIMEOUT; 2007772Ssam if (timeout >= MAXTIMEOUT) 2017772Ssam exit(1); 2027772Ssam alarm(TIMEOUT); 2037772Ssam } 2047772Ssam 2057772Ssam /* 2067772Ssam * Send the requested file. 2077772Ssam */ 2087772Ssam sendfile(pf) 2097772Ssam struct format *pf; 2107772Ssam { 2117772Ssam register struct tftphdr *tp; 2127772Ssam register int block = 1, size, n; 2137772Ssam 2147772Ssam sigset(SIGALRM, timer); 2157772Ssam tp = (struct tftphdr *)buf; 2167772Ssam do { 2177772Ssam size = read(fd, tp->th_data, SEGSIZE); 2187772Ssam if (size < 0) { 2197772Ssam nak(errno + 100); 2207772Ssam break; 2217772Ssam } 2227772Ssam tp->th_opcode = htons((u_short)DATA); 2237772Ssam tp->th_block = htons((u_short)block); 2247772Ssam timeout = 0; 2257772Ssam alarm(TIMEOUT); 2267772Ssam rexmt: 2277772Ssam if (write(f, buf, size + 4) != size + 4) { 2287772Ssam perror("send"); 2297772Ssam break; 2307772Ssam } 2317772Ssam again: 2327772Ssam n = read(f, buf, sizeof (buf)); 2337772Ssam if (n <= 0) { 2347772Ssam if (n == 0) 2357772Ssam goto again; 2367772Ssam if (errno == EINTR) 2377772Ssam goto rexmt; 2387772Ssam alarm(0); 2397772Ssam perror("receive"); 2407772Ssam break; 2417772Ssam } 2427772Ssam alarm(0); 2437772Ssam #if vax || pdp11 2447772Ssam tp->th_opcode = ntohs(tp->th_opcode); 2457772Ssam tp->th_block = ntohs(tp->th_block); 2467772Ssam #endif 2477772Ssam if (tp->th_opcode == ERROR) 2487772Ssam break; 2497772Ssam if (tp->th_opcode != ACK || tp->th_block != block) 2507772Ssam goto again; 2517772Ssam block++; 2527772Ssam } while (size == SEGSIZE); 2537772Ssam (void) close(fd); 2547772Ssam } 2557772Ssam 2567772Ssam /* 2577772Ssam * Receive a file. 2587772Ssam */ 2597772Ssam recvfile(pf) 2607772Ssam struct format *pf; 2617772Ssam { 2627772Ssam register struct tftphdr *tp; 2637772Ssam register int block = 0, n, size; 2647772Ssam 2657772Ssam sigset(SIGALRM, timer); 2667772Ssam tp = (struct tftphdr *)buf; 2677772Ssam do { 2687772Ssam timeout = 0; 2697772Ssam alarm(TIMEOUT); 2707772Ssam tp->th_opcode = htons((u_short)ACK); 2717772Ssam tp->th_block = htons((u_short)block); 2727772Ssam block++; 2737772Ssam rexmt: 2747772Ssam if (write(f, buf, 4) != 4) { 2757772Ssam perror("ack"); 2767772Ssam break; 2777772Ssam } 2787772Ssam again: 2797772Ssam n = read(f, buf, sizeof (buf)); 2807772Ssam if (n <= 0) { 2817772Ssam if (n == 0) 2827772Ssam goto again; 2837772Ssam if (errno == EINTR) 2847772Ssam goto rexmt; 2857772Ssam alarm(0); 2867772Ssam perror("receive"); 2877772Ssam break; 2887772Ssam } 2897772Ssam alarm(0); 2907772Ssam #if vax || pdp11 2917772Ssam tp->th_opcode = ntohs(tp->th_opcode); 2927772Ssam tp->th_block = ntohs(tp->th_block); 2937772Ssam #endif 2947772Ssam if (tp->th_opcode == ERROR) 2957772Ssam break; 2967772Ssam if (tp->th_opcode != DATA || block != tp->th_block) 2977772Ssam goto again; 2987772Ssam size = write(fd, tp->th_data, n - 4); 2997772Ssam if (size < 0) { 3007772Ssam nak(errno + 100); 3017772Ssam break; 3027772Ssam } 3037772Ssam } while (size == SEGSIZE); 3047772Ssam tp->th_opcode = htons((u_short)ACK); 3057772Ssam tp->th_block = htons((u_short)(block)); 3067772Ssam (void) write(f, buf, 4); 3077772Ssam (void) close(fd); 3087772Ssam } 3097772Ssam 3107772Ssam struct errmsg { 3117772Ssam int e_code; 3127772Ssam char *e_msg; 3137772Ssam } errmsgs[] = { 3147772Ssam { EUNDEF, "Undefined error code" }, 3157772Ssam { ENOTFOUND, "File not found" }, 3167772Ssam { EACCESS, "Access violation" }, 3177772Ssam { ENOSPACE, "Disk full or allocation exceeded" }, 3187772Ssam { EBADOP, "Illegal TFTP operation" }, 3197772Ssam { EBADID, "Unknown transfer ID" }, 3207772Ssam { EEXISTS, "File already exists" }, 3217772Ssam { ENOUSER, "No such user" }, 3227772Ssam { -1, 0 } 3237772Ssam }; 3247772Ssam 3257772Ssam /* 3267772Ssam * Send a nak packet (error message). 3277772Ssam * Error code passed in is one of the 3287772Ssam * standard TFTP codes, or a UNIX errno 3297772Ssam * offset by 100. 3307772Ssam */ 3317772Ssam nak(error) 3327772Ssam int error; 3337772Ssam { 3347772Ssam register struct tftphdr *tp; 3357772Ssam int length; 3367772Ssam register struct errmsg *pe; 3377772Ssam extern char *sys_errlist[]; 3387772Ssam 3397772Ssam tp = (struct tftphdr *)buf; 3407772Ssam tp->th_opcode = htons((u_short)ERROR); 3417772Ssam tp->th_code = htons((u_short)error); 3427772Ssam for (pe = errmsgs; pe->e_code >= 0; pe++) 3437772Ssam if (pe->e_code == error) 3447772Ssam break; 3457772Ssam if (pe->e_code < 0) 3467772Ssam pe->e_msg = sys_errlist[error - 100]; 3477772Ssam strcpy(tp->th_msg, pe->e_msg); 3487772Ssam length = strlen(pe->e_msg); 3497772Ssam tp->th_msg[length] = '\0'; 3507772Ssam length += 5; 3517772Ssam if (write(f, buf, length) != length) 3527772Ssam perror("nak"); 3537772Ssam } 354