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*26109Sminshall static char sccsid[] = "@(#)tftpd.c 5.4 (Berkeley) 02/07/86"; 1521183Sdist #endif not lint 1621183Sdist 1726108Sminshall 187772Ssam /* 197772Ssam * Trivial file transfer protocol server. 2026108Sminshall * 2126108Sminshall * This version includes many modifications by Jim Guyton <guyton@rand-unix> 227772Ssam */ 2326108Sminshall 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; 4926108Sminshall 5026108Sminshall #define PKTSIZE SEGSIZE+4 5126108Sminshall char buf[PKTSIZE]; 5226108Sminshall 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)(); 10226108Sminshall int f_convert; 1037772Ssam } formats[] = { 10426108Sminshall { "netascii", validate_access, sendfile, recvfile, 1 }, 10526108Sminshall { "octet", validate_access, sendfile, recvfile, 0 }, 1067772Ssam #ifdef notdef 10726108Sminshall { "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 16326108Sminshall FILE *file; 16426108Sminshall 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 */ 17326108Sminshall validate_access(filename, mode) 17426108Sminshall char *filename; 1757772Ssam int mode; 1767772Ssam { 1777772Ssam struct stat stbuf; 17826108Sminshall int fd; 1797772Ssam 18026108Sminshall if (*filename != '/') 1817772Ssam return (EACCESS); 18226108Sminshall 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 } 19126108Sminshall fd = open(filename, mode == RRQ ? 0 : 1); 1927772Ssam if (fd < 0) 1937772Ssam return (errno + 100); 19426108Sminshall file = fdopen(fd, (mode == RRQ)? "r":"w"); 19526108Sminshall if (file == NULL) { 19626108Sminshall return errno+100; 19726108Sminshall } 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) 21726108Sminshall struct formats *pf; 2187772Ssam { 21926108Sminshall struct tftphdr *dp, *r_init(); 22026108Sminshall register struct tftphdr *ap; /* ack packet */ 2217772Ssam register int block = 1, size, n; 2227772Ssam 22313020Ssam signal(SIGALRM, timer); 22426108Sminshall dp = r_init(); 22526108Sminshall ap = (struct tftphdr *)ackbuf; 2267772Ssam do { 22726108Sminshall size = readit(file, &dp, pf->f_convert); 2287772Ssam if (size < 0) { 2297772Ssam nak(errno + 100); 23026108Sminshall goto abort; 2317772Ssam } 23226108Sminshall dp->th_opcode = htons((u_short)DATA); 23326108Sminshall dp->th_block = htons((u_short)block); 2347772Ssam timeout = 0; 23513020Ssam (void) setjmp(timeoutbuf); 23626108Sminshall 237*26109Sminshall send_data: 238*26109Sminshall /* Now, we flush anything pending to be read */ 239*26109Sminshall /* This is to try to keep in synch between the two sides */ 240*26109Sminshall while (1) { 241*26109Sminshall int i; 242*26109Sminshall char rbuf[PKTSIZE]; 243*26109Sminshall 244*26109Sminshall (void) ioctl(peer, FIONREAD, &i); 245*26109Sminshall if (i) { 246*26109Sminshall fromlen = sizeof from; 247*26109Sminshall n = recvfrom(peer, rbuf, sizeof (rbuf), 0, 248*26109Sminshall (caddr_t)&from, &fromlen); 249*26109Sminshall } else { 250*26109Sminshall break; 251*26109Sminshall } 252*26109Sminshall } 25326108Sminshall if (send(peer, dp, size + 4, 0) != size + 4) { 25426108Sminshall perror("tftpd: write"); 25526108Sminshall goto abort; 2567772Ssam } 25726108Sminshall read_ahead(file, pf->f_convert); 258*26109Sminshall for ( ; ; ) { 25926108Sminshall alarm(rexmtval); /* read the ack */ 26026108Sminshall n = recv(peer, ackbuf, sizeof (ackbuf), 0); 2617772Ssam alarm(0); 26213020Ssam if (n < 0) { 26326108Sminshall perror("tftpd: read"); 26426108Sminshall goto abort; 26513020Ssam } 26626108Sminshall ap->th_opcode = ntohs((u_short)ap->th_opcode); 26726108Sminshall ap->th_block = ntohs((u_short)ap->th_block); 26826108Sminshall 26926108Sminshall if (ap->th_opcode == ERROR) 27026108Sminshall goto abort; 271*26109Sminshall 272*26109Sminshall if (ap->th_opcode == ACK) { 273*26109Sminshall if (ap->th_block == block) { 274*26109Sminshall break; 275*26109Sminshall } 276*26109Sminshall if (ap->th_block == (block -1)) { 277*26109Sminshall goto send_data; 278*26109Sminshall } 279*26109Sminshall } 28026108Sminshall 281*26109Sminshall } 2827772Ssam block++; 2837772Ssam } while (size == SEGSIZE); 28426108Sminshall abort: 28526108Sminshall (void) fclose(file); 2867772Ssam } 2877772Ssam 28826108Sminshall justquit() 28926108Sminshall { 29026108Sminshall exit(0); 29126108Sminshall } 29226108Sminshall 29326108Sminshall 2947772Ssam /* 2957772Ssam * Receive a file. 2967772Ssam */ 2977772Ssam recvfile(pf) 29826108Sminshall struct formats *pf; 2997772Ssam { 30026108Sminshall struct tftphdr *dp, *w_init(); 30126108Sminshall register struct tftphdr *ap; /* ack buffer */ 3027772Ssam register int block = 0, n, size; 3037772Ssam 30413020Ssam signal(SIGALRM, timer); 30526108Sminshall dp = w_init(); 30626108Sminshall ap = (struct tftphdr *)ackbuf; 3077772Ssam do { 3087772Ssam timeout = 0; 30926108Sminshall ap->th_opcode = htons((u_short)ACK); 31026108Sminshall ap->th_block = htons((u_short)block); 3117772Ssam block++; 31213020Ssam (void) setjmp(timeoutbuf); 31326108Sminshall send_ack: 314*26109Sminshall /* Now, we flush anything pending to be read */ 315*26109Sminshall /* This is to try to keep in synch between the two sides */ 316*26109Sminshall while (1) { 317*26109Sminshall int i; 318*26109Sminshall char rbuf[PKTSIZE]; 319*26109Sminshall 320*26109Sminshall (void) ioctl(peer, FIONREAD, &i); 321*26109Sminshall if (i) { 322*26109Sminshall fromlen = sizeof from; 323*26109Sminshall n = recvfrom(peer, rbuf, sizeof (rbuf), 0, 324*26109Sminshall (caddr_t)&from, &fromlen); 325*26109Sminshall } else { 326*26109Sminshall break; 327*26109Sminshall } 328*26109Sminshall } 32926108Sminshall if (send(peer, ackbuf, 4, 0) != 4) { 33026108Sminshall perror("tftpd: write"); 33113020Ssam goto abort; 3327772Ssam } 33326108Sminshall write_behind(file, pf->f_convert); 33426108Sminshall for ( ; ; ) { 33513020Ssam alarm(rexmtval); 33626108Sminshall n = recv(peer, dp, PKTSIZE, 0); 3377772Ssam alarm(0); 33826108Sminshall if (n < 0) { /* really? */ 33926108Sminshall perror("tftpd: read"); 34013020Ssam goto abort; 34113020Ssam } 34226108Sminshall dp->th_opcode = ntohs((u_short)dp->th_opcode); 34326108Sminshall dp->th_block = ntohs((u_short)dp->th_block); 34426108Sminshall if (dp->th_opcode == ERROR) 34513020Ssam goto abort; 34626108Sminshall if (dp->th_opcode == DATA) { 34726108Sminshall if (dp->th_block == block) { 34826108Sminshall break; /* normal */ 34926108Sminshall } 35026108Sminshall if (dp->th_block == (block-1)) 35126108Sminshall goto send_ack; /* rexmit */ 35226108Sminshall } 35326108Sminshall } 35426108Sminshall /* size = write(file, dp->th_data, n - 4); */ 35526108Sminshall size = writeit(file, &dp, n - 4, pf->f_convert); 35626108Sminshall if (size != (n-4)) { /* ahem */ 35726108Sminshall if (size < 0) nak(errno + 100); 35826108Sminshall else nak(ENOSPACE); 35913020Ssam goto abort; 3607772Ssam } 3617772Ssam } while (size == SEGSIZE); 36226108Sminshall write_behind(file, pf->f_convert); 36326108Sminshall (void) fclose(file); /* close data file */ 36426108Sminshall 36526108Sminshall ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ 36626108Sminshall ap->th_block = htons((u_short)(block)); 36726108Sminshall (void) send(peer, ackbuf, 4, 0); 36826108Sminshall 36926108Sminshall signal(SIGALRM, justquit); /* just quit on timeout */ 37026108Sminshall alarm(rexmtval); 37126108Sminshall n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ 37226108Sminshall alarm(0); 37326108Sminshall if (n >= 4 && /* if read some data */ 37426108Sminshall dp->th_opcode == DATA && /* and got a data block */ 37526108Sminshall block == dp->th_block) { /* then my last ack was lost */ 37626108Sminshall (void) send(peer, ackbuf, 4, 0); /* resend final ack */ 37726108Sminshall } 37813020Ssam abort: 37926108Sminshall return; 3807772Ssam } 3817772Ssam 3827772Ssam struct errmsg { 3837772Ssam int e_code; 3847772Ssam char *e_msg; 3857772Ssam } errmsgs[] = { 3867772Ssam { EUNDEF, "Undefined error code" }, 3877772Ssam { ENOTFOUND, "File not found" }, 3887772Ssam { EACCESS, "Access violation" }, 3897772Ssam { ENOSPACE, "Disk full or allocation exceeded" }, 3907772Ssam { EBADOP, "Illegal TFTP operation" }, 3917772Ssam { EBADID, "Unknown transfer ID" }, 3927772Ssam { EEXISTS, "File already exists" }, 3937772Ssam { ENOUSER, "No such user" }, 3947772Ssam { -1, 0 } 3957772Ssam }; 3967772Ssam 3977772Ssam /* 3987772Ssam * Send a nak packet (error message). 3997772Ssam * Error code passed in is one of the 4007772Ssam * standard TFTP codes, or a UNIX errno 4017772Ssam * offset by 100. 4027772Ssam */ 4037772Ssam nak(error) 4047772Ssam int error; 4057772Ssam { 4067772Ssam register struct tftphdr *tp; 4077772Ssam int length; 4087772Ssam register struct errmsg *pe; 4097772Ssam extern char *sys_errlist[]; 4107772Ssam 4117772Ssam tp = (struct tftphdr *)buf; 4127772Ssam tp->th_opcode = htons((u_short)ERROR); 4137772Ssam tp->th_code = htons((u_short)error); 4147772Ssam for (pe = errmsgs; pe->e_code >= 0; pe++) 4157772Ssam if (pe->e_code == error) 4167772Ssam break; 41726108Sminshall if (pe->e_code < 0) { 4187772Ssam pe->e_msg = sys_errlist[error - 100]; 41926108Sminshall tp->th_code = EUNDEF; /* set 'undef' errorcode */ 42026108Sminshall } 4217772Ssam strcpy(tp->th_msg, pe->e_msg); 4227772Ssam length = strlen(pe->e_msg); 4237772Ssam tp->th_msg[length] = '\0'; 4247772Ssam length += 5; 42516372Skarels if (send(peer, buf, length, 0) != length) 4267772Ssam perror("nak"); 4277772Ssam } 428