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*26108Sminshall static char sccsid[] = "@(#)tftpd.c 5.3 (Berkeley) 02/07/86"; 1521183Sdist #endif not lint 1621183Sdist 17*26108Sminshall 187772Ssam /* 197772Ssam * Trivial file transfer protocol server. 20*26108Sminshall * 21*26108Sminshall * This version includes many modifications by Jim Guyton <guyton@rand-unix> 227772Ssam */ 23*26108Sminshall 247772Ssam #include <sys/types.h> 257772Ssam #include <sys/socket.h> 269220Ssam #include <sys/ioctl.h> 2713609Ssam #include <sys/wait.h> 2813609Ssam #include <sys/stat.h> 299220Ssam 309220Ssam #include <netinet/in.h> 319220Ssam 3212217Ssam #include <arpa/tftp.h> 3312217Ssam 347772Ssam #include <signal.h> 357772Ssam #include <stdio.h> 367772Ssam #include <errno.h> 377772Ssam #include <ctype.h> 388385Ssam #include <netdb.h> 3913020Ssam #include <setjmp.h> 4017188Sralph #include <syslog.h> 419220Ssam 4213020Ssam #define TIMEOUT 5 4313020Ssam 447772Ssam extern int errno; 458385Ssam struct sockaddr_in sin = { AF_INET }; 4616372Skarels int peer; 4713020Ssam int rexmtval = TIMEOUT; 4813020Ssam int maxtimeout = 5*TIMEOUT; 49*26108Sminshall 50*26108Sminshall #define PKTSIZE SEGSIZE+4 51*26108Sminshall char buf[PKTSIZE]; 52*26108Sminshall char ackbuf[PKTSIZE]; 5316372Skarels struct sockaddr_in from; 5416372Skarels int fromlen; 557772Ssam 5616372Skarels main() 577772Ssam { 587772Ssam register struct tftphdr *tp; 597772Ssam register int n; 607772Ssam 6124852Seric openlog("tftpd", LOG_PID, LOG_DAEMON); 6216372Skarels alarm(10); 6316372Skarels fromlen = sizeof (from); 6416372Skarels n = recvfrom(0, buf, sizeof (buf), 0, 6516372Skarels (caddr_t)&from, &fromlen); 6616372Skarels if (n < 0) { 6716372Skarels perror("tftpd: recvfrom"); 688385Ssam exit(1); 698385Ssam } 7016372Skarels from.sin_family = AF_INET; 7116372Skarels alarm(0); 7216372Skarels close(0); 7316372Skarels close(1); 7416372Skarels peer = socket(AF_INET, SOCK_DGRAM, 0); 7516372Skarels if (peer < 0) { 7617188Sralph syslog(LOG_ERR, "socket: %m"); 7716372Skarels exit(1); 787772Ssam } 7916372Skarels if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) { 8017188Sralph syslog(LOG_ERR, "bind: %m"); 8116372Skarels exit(1); 8216372Skarels } 8316372Skarels if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) { 8417188Sralph syslog(LOG_ERR, "connect: %m"); 8516372Skarels exit(1); 867772Ssam } 8716372Skarels tp = (struct tftphdr *)buf; 8816372Skarels tp->th_opcode = ntohs(tp->th_opcode); 8916372Skarels if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) 9016372Skarels tftp(tp, n); 9116372Skarels exit(1); 927772Ssam } 937772Ssam 947772Ssam int validate_access(); 957772Ssam int sendfile(), recvfile(); 967772Ssam 977772Ssam struct formats { 987772Ssam char *f_mode; 997772Ssam int (*f_validate)(); 1007772Ssam int (*f_send)(); 1017772Ssam int (*f_recv)(); 102*26108Sminshall int f_convert; 1037772Ssam } formats[] = { 104*26108Sminshall { "netascii", validate_access, sendfile, recvfile, 1 }, 105*26108Sminshall { "octet", validate_access, sendfile, recvfile, 0 }, 1067772Ssam #ifdef notdef 107*26108Sminshall { "mail", validate_user, sendmail, recvmail, 1 }, 1087772Ssam #endif 1097772Ssam { 0 } 1107772Ssam }; 1117772Ssam 1127772Ssam /* 1137772Ssam * Handle initial connection protocol. 1147772Ssam */ 11516372Skarels tftp(tp, size) 1167772Ssam struct tftphdr *tp; 1177772Ssam int size; 1187772Ssam { 1197772Ssam register char *cp; 1207772Ssam int first = 1, ecode; 1217772Ssam register struct formats *pf; 1227772Ssam char *filename, *mode; 1237772Ssam 1247772Ssam filename = cp = tp->th_stuff; 1257772Ssam again: 1267772Ssam while (cp < buf + size) { 1277772Ssam if (*cp == '\0') 1287772Ssam break; 1297772Ssam cp++; 1307772Ssam } 1317772Ssam if (*cp != '\0') { 1327772Ssam nak(EBADOP); 1337772Ssam exit(1); 1347772Ssam } 1357772Ssam if (first) { 1367772Ssam mode = ++cp; 1377772Ssam first = 0; 1387772Ssam goto again; 1397772Ssam } 1407772Ssam for (cp = mode; *cp; cp++) 1417772Ssam if (isupper(*cp)) 1427772Ssam *cp = tolower(*cp); 1437772Ssam for (pf = formats; pf->f_mode; pf++) 1447772Ssam if (strcmp(pf->f_mode, mode) == 0) 1457772Ssam break; 1467772Ssam if (pf->f_mode == 0) { 1477772Ssam nak(EBADOP); 1487772Ssam exit(1); 1497772Ssam } 15016372Skarels ecode = (*pf->f_validate)(filename, tp->th_opcode); 1517772Ssam if (ecode) { 1527772Ssam nak(ecode); 1537772Ssam exit(1); 1547772Ssam } 1557772Ssam if (tp->th_opcode == WRQ) 1567772Ssam (*pf->f_recv)(pf); 1577772Ssam else 1587772Ssam (*pf->f_send)(pf); 1597772Ssam exit(0); 1607772Ssam } 1617772Ssam 16216372Skarels 163*26108Sminshall FILE *file; 164*26108Sminshall 1657772Ssam /* 1667772Ssam * Validate file access. Since we 1677772Ssam * have no uid or gid, for now require 1687772Ssam * file to exist and be publicly 1697772Ssam * readable/writable. 1707772Ssam * Note also, full path name must be 1717772Ssam * given as we have no login directory. 1727772Ssam */ 173*26108Sminshall validate_access(filename, mode) 174*26108Sminshall char *filename; 1757772Ssam int mode; 1767772Ssam { 1777772Ssam struct stat stbuf; 178*26108Sminshall int fd; 1797772Ssam 180*26108Sminshall if (*filename != '/') 1817772Ssam return (EACCESS); 182*26108Sminshall if (stat(filename, &stbuf) < 0) 1837772Ssam return (errno == ENOENT ? ENOTFOUND : EACCESS); 1847772Ssam if (mode == RRQ) { 1857772Ssam if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) 1867772Ssam return (EACCESS); 1877772Ssam } else { 1887772Ssam if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) 1897772Ssam return (EACCESS); 1907772Ssam } 191*26108Sminshall fd = open(filename, mode == RRQ ? 0 : 1); 1927772Ssam if (fd < 0) 1937772Ssam return (errno + 100); 194*26108Sminshall file = fdopen(fd, (mode == RRQ)? "r":"w"); 195*26108Sminshall if (file == NULL) { 196*26108Sminshall return errno+100; 197*26108Sminshall } 1987772Ssam return (0); 1997772Ssam } 2007772Ssam 20113020Ssam int timeout; 20213020Ssam jmp_buf timeoutbuf; 2037772Ssam 2047772Ssam timer() 2057772Ssam { 20613020Ssam 20713020Ssam timeout += rexmtval; 20813020Ssam if (timeout >= maxtimeout) 2097772Ssam exit(1); 21013020Ssam longjmp(timeoutbuf, 1); 2117772Ssam } 2127772Ssam 2137772Ssam /* 2147772Ssam * Send the requested file. 2157772Ssam */ 2167772Ssam sendfile(pf) 217*26108Sminshall struct formats *pf; 2187772Ssam { 219*26108Sminshall struct tftphdr *dp, *r_init(); 220*26108Sminshall register struct tftphdr *ap; /* ack packet */ 2217772Ssam register int block = 1, size, n; 2227772Ssam 22313020Ssam signal(SIGALRM, timer); 224*26108Sminshall dp = r_init(); 225*26108Sminshall ap = (struct tftphdr *)ackbuf; 2267772Ssam do { 227*26108Sminshall size = readit(file, &dp, pf->f_convert); 2287772Ssam if (size < 0) { 2297772Ssam nak(errno + 100); 230*26108Sminshall goto abort; 2317772Ssam } 232*26108Sminshall dp->th_opcode = htons((u_short)DATA); 233*26108Sminshall dp->th_block = htons((u_short)block); 2347772Ssam timeout = 0; 23513020Ssam (void) setjmp(timeoutbuf); 236*26108Sminshall 237*26108Sminshall if (send(peer, dp, size + 4, 0) != size + 4) { 238*26108Sminshall perror("tftpd: write"); 239*26108Sminshall goto abort; 2407772Ssam } 241*26108Sminshall read_ahead(file, pf->f_convert); 24213020Ssam do { 243*26108Sminshall alarm(rexmtval); /* read the ack */ 244*26108Sminshall n = recv(peer, ackbuf, sizeof (ackbuf), 0); 2457772Ssam alarm(0); 24613020Ssam if (n < 0) { 247*26108Sminshall perror("tftpd: read"); 248*26108Sminshall goto abort; 24913020Ssam } 250*26108Sminshall ap->th_opcode = ntohs((u_short)ap->th_opcode); 251*26108Sminshall ap->th_block = ntohs((u_short)ap->th_block); 252*26108Sminshall 253*26108Sminshall if (ap->th_opcode == ERROR) 254*26108Sminshall goto abort; 255*26108Sminshall 256*26108Sminshall } while (ap->th_opcode != ACK || ap->th_block != block); 2577772Ssam block++; 2587772Ssam } while (size == SEGSIZE); 259*26108Sminshall abort: 260*26108Sminshall (void) fclose(file); 2617772Ssam } 2627772Ssam 263*26108Sminshall justquit() 264*26108Sminshall { 265*26108Sminshall exit(0); 266*26108Sminshall } 267*26108Sminshall 268*26108Sminshall 2697772Ssam /* 2707772Ssam * Receive a file. 2717772Ssam */ 2727772Ssam recvfile(pf) 273*26108Sminshall struct formats *pf; 2747772Ssam { 275*26108Sminshall struct tftphdr *dp, *w_init(); 276*26108Sminshall register struct tftphdr *ap; /* ack buffer */ 2777772Ssam register int block = 0, n, size; 2787772Ssam 27913020Ssam signal(SIGALRM, timer); 280*26108Sminshall dp = w_init(); 281*26108Sminshall ap = (struct tftphdr *)ackbuf; 2827772Ssam do { 2837772Ssam timeout = 0; 284*26108Sminshall ap->th_opcode = htons((u_short)ACK); 285*26108Sminshall ap->th_block = htons((u_short)block); 2867772Ssam block++; 28713020Ssam (void) setjmp(timeoutbuf); 288*26108Sminshall send_ack: 289*26108Sminshall if (send(peer, ackbuf, 4, 0) != 4) { 290*26108Sminshall perror("tftpd: write"); 29113020Ssam goto abort; 2927772Ssam } 293*26108Sminshall write_behind(file, pf->f_convert); 294*26108Sminshall for ( ; ; ) { 29513020Ssam alarm(rexmtval); 296*26108Sminshall n = recv(peer, dp, PKTSIZE, 0); 2977772Ssam alarm(0); 298*26108Sminshall if (n < 0) { /* really? */ 299*26108Sminshall perror("tftpd: read"); 30013020Ssam goto abort; 30113020Ssam } 302*26108Sminshall dp->th_opcode = ntohs((u_short)dp->th_opcode); 303*26108Sminshall dp->th_block = ntohs((u_short)dp->th_block); 304*26108Sminshall if (dp->th_opcode == ERROR) 30513020Ssam goto abort; 306*26108Sminshall if (dp->th_opcode == DATA) { 307*26108Sminshall if (dp->th_block == block) { 308*26108Sminshall break; /* normal */ 309*26108Sminshall } 310*26108Sminshall if (dp->th_block == (block-1)) 311*26108Sminshall goto send_ack; /* rexmit */ 312*26108Sminshall } 313*26108Sminshall } 314*26108Sminshall /* size = write(file, dp->th_data, n - 4); */ 315*26108Sminshall size = writeit(file, &dp, n - 4, pf->f_convert); 316*26108Sminshall if (size != (n-4)) { /* ahem */ 317*26108Sminshall if (size < 0) nak(errno + 100); 318*26108Sminshall else nak(ENOSPACE); 31913020Ssam goto abort; 3207772Ssam } 3217772Ssam } while (size == SEGSIZE); 322*26108Sminshall write_behind(file, pf->f_convert); 323*26108Sminshall (void) fclose(file); /* close data file */ 324*26108Sminshall 325*26108Sminshall ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 326*26108Sminshall ap->th_block = htons((u_short)(block)); 327*26108Sminshall (void) send(peer, ackbuf, 4, 0); 328*26108Sminshall 329*26108Sminshall signal(SIGALRM, justquit); /* just quit on timeout */ 330*26108Sminshall alarm(rexmtval); 331*26108Sminshall n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 332*26108Sminshall alarm(0); 333*26108Sminshall if (n >= 4 && /* if read some data */ 334*26108Sminshall dp->th_opcode == DATA && /* and got a data block */ 335*26108Sminshall block == dp->th_block) { /* then my last ack was lost */ 336*26108Sminshall (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 337*26108Sminshall } 33813020Ssam abort: 339*26108Sminshall return; 3407772Ssam } 3417772Ssam 3427772Ssam struct errmsg { 3437772Ssam int e_code; 3447772Ssam char *e_msg; 3457772Ssam } errmsgs[] = { 3467772Ssam { EUNDEF, "Undefined error code" }, 3477772Ssam { ENOTFOUND, "File not found" }, 3487772Ssam { EACCESS, "Access violation" }, 3497772Ssam { ENOSPACE, "Disk full or allocation exceeded" }, 3507772Ssam { EBADOP, "Illegal TFTP operation" }, 3517772Ssam { EBADID, "Unknown transfer ID" }, 3527772Ssam { EEXISTS, "File already exists" }, 3537772Ssam { ENOUSER, "No such user" }, 3547772Ssam { -1, 0 } 3557772Ssam }; 3567772Ssam 3577772Ssam /* 3587772Ssam * Send a nak packet (error message). 3597772Ssam * Error code passed in is one of the 3607772Ssam * standard TFTP codes, or a UNIX errno 3617772Ssam * offset by 100. 3627772Ssam */ 3637772Ssam nak(error) 3647772Ssam int error; 3657772Ssam { 3667772Ssam register struct tftphdr *tp; 3677772Ssam int length; 3687772Ssam register struct errmsg *pe; 3697772Ssam extern char *sys_errlist[]; 3707772Ssam 3717772Ssam tp = (struct tftphdr *)buf; 3727772Ssam tp->th_opcode = htons((u_short)ERROR); 3737772Ssam tp->th_code = htons((u_short)error); 3747772Ssam for (pe = errmsgs; pe->e_code >= 0; pe++) 3757772Ssam if (pe->e_code == error) 3767772Ssam break; 377*26108Sminshall if (pe->e_code < 0) { 3787772Ssam pe->e_msg = sys_errlist[error - 100]; 379*26108Sminshall tp->th_code = EUNDEF; /* set 'undef' errorcode */ 380*26108Sminshall } 3817772Ssam strcpy(tp->th_msg, pe->e_msg); 3827772Ssam length = strlen(pe->e_msg); 3837772Ssam tp->th_msg[length] = '\0'; 3847772Ssam length += 5; 38516372Skarels if (send(peer, buf, length, 0) != length) 3867772Ssam perror("nak"); 3877772Ssam } 388