X-Git-Url: https://git.subgeniuskitty.com/unix-history/.git/blobdiff_plain/103499bcd60a969c62ec27f7085e8abd0cc8f2c5..17a555109e847e02774e0c8070bb1dd05bbcb109:/usr/src/libexec/tftpd/tftpd.c diff --git a/usr/src/libexec/tftpd/tftpd.c b/usr/src/libexec/tftpd/tftpd.c index 59c0432c0b..3e9c0f7d06 100644 --- a/usr/src/libexec/tftpd/tftpd.c +++ b/usr/src/libexec/tftpd/tftpd.c @@ -1,128 +1,233 @@ -/* tftpd.c 4.9 83/06/12 */ +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * %sccs.include.redist.c% + */ + +#ifndef lint +static char copyright[] = +"@(#) Copyright (c) 1983, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char sccsid[] = "@(#)tftpd.c 8.1 (Berkeley) %G%"; +#endif /* not lint */ /* * Trivial file transfer protocol server. + * + * This version includes many modifications by Jim Guyton + * . */ -#include -#include + +#include #include +#include +#include #include - #include +#include -#include -#include -#include -#include -#include #include +#include +#include #include #include +#include +#include +#include +#include +#include +#include + +#include "tftpsubs.h" -#define DEBUG 1 #define TIMEOUT 5 -extern int errno; -struct sockaddr_in sin = { AF_INET }; -int f; +int peer; int rexmtval = TIMEOUT; int maxtimeout = 5*TIMEOUT; -char buf[BUFSIZ]; -int reapchild(); +#define PKTSIZE SEGSIZE+4 +char buf[PKTSIZE]; +char ackbuf[PKTSIZE]; +struct sockaddr_in from; +int fromlen; + +void tftp __P((struct tftphdr *, int)); + +/* + * Null-terminated directory prefix list for absolute pathname requests and + * search list for relative pathname requests. + * + * MAXDIRS should be at least as large as the number of arguments that + * inetd allows (currently 20). + */ +#define MAXDIRS 20 +static struct dirlist { + char *name; + int len; +} dirs[MAXDIRS+1]; +static int suppress_naks; +static int logging; + +static char *errtomsg __P((int)); +static void nak __P((int)); +static char *verifyhost __P((struct sockaddr_in *)); + +int main(argc, argv) + int argc; char *argv[]; { - struct sockaddr_in from; register struct tftphdr *tp; register int n; - struct servent *sp; + int ch, on; + struct sockaddr_in sin; - sp = getservbyname("tftp", "udp"); - if (sp == 0) { - fprintf(stderr, "tftpd: udp/tftp: unknown service\n"); + openlog("tftpd", LOG_PID, LOG_FTP); + while ((ch = getopt(argc, argv, "ln")) != EOF) { + switch (ch) { + case 'l': + logging = 1; + break; + case 'n': + suppress_naks = 1; + break; + default: + syslog(LOG_WARNING, "ignoring unknown option -%c", ch); + } + } + if (optind < argc) { + struct dirlist *dirp; + + /* Get list of directory prefixes. Skip relative pathnames. */ + for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; + optind++) { + if (argv[optind][0] == '/') { + dirp->name = argv[optind]; + dirp->len = strlen(dirp->name); + dirp++; + } + } + } + + on = 1; + if (ioctl(0, FIONBIO, &on) < 0) { + syslog(LOG_ERR, "ioctl(FIONBIO): %m\n"); exit(1); } - sin.sin_port = sp->s_port; -#ifndef DEBUG - if (fork()) - exit(0); - for (f = 0; f < 10; f++) - (void) close(f); - (void) open("/", 0); - (void) dup2(0, 1); - (void) dup2(0, 2); - { int t = open("/dev/tty", 2); - if (t >= 0) { - ioctl(t, TIOCNOTTY, (char *)0); - (void) close(t); - } + fromlen = sizeof (from); + n = recvfrom(0, buf, sizeof (buf), 0, + (struct sockaddr *)&from, &fromlen); + if (n < 0) { + syslog(LOG_ERR, "recvfrom: %m\n"); + exit(1); } -#endif - signal(SIGCHLD, reapchild); - for (;;) { - int fromlen; - - f = socket(AF_INET, SOCK_DGRAM, 0); - if (f < 0) { - perror("tftpd: socket"); - sleep(5); - continue; + /* + * Now that we have read the message out of the UDP + * socket, we fork and exit. Thus, inetd will go back + * to listening to the tftp port, and the next request + * to come in will start up a new instance of tftpd. + * + * We do this so that inetd can run tftpd in "wait" mode. + * The problem with tftpd running in "nowait" mode is that + * inetd may get one or more successful "selects" on the + * tftp port before we do our receive, so more than one + * instance of tftpd may be started up. Worse, if tftpd + * break before doing the above "recvfrom", inetd would + * spawn endless instances, clogging the system. + */ + { + int pid; + int i, j; + + for (i = 1; i < 20; i++) { + pid = fork(); + if (pid < 0) { + sleep(i); + /* + * flush out to most recently sent request. + * + * This may drop some request, but those + * will be resent by the clients when + * they timeout. The positive effect of + * this flush is to (try to) prevent more + * than one tftpd being started up to service + * a single request from a single client. + */ + j = sizeof from; + i = recvfrom(0, buf, sizeof (buf), 0, + (struct sockaddr *)&from, &j); + if (i > 0) { + n = i; + fromlen = j; + } + } else { + break; + } } - if (setsockopt(f, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0) - perror("tftpd: setsockopt (SO_REUSEADDR)"); - sleep(1); /* let child do connect */ - while (bind(f, (caddr_t)&sin, sizeof (sin), 0) < 0) { - perror("tftpd: bind"); - sleep(5); + if (pid < 0) { + syslog(LOG_ERR, "fork: %m\n"); + exit(1); + } else if (pid != 0) { + exit(0); } - do { - fromlen = sizeof (from); - n = recvfrom(f, buf, sizeof (buf), 0, - (caddr_t)&from, &fromlen); - } while (n <= 0); - tp = (struct tftphdr *)buf; - tp->th_opcode = ntohs(tp->th_opcode); - if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) - if (fork() == 0) - tftp(&from, tp, n); - (void) close(f); } + from.sin_family = AF_INET; + alarm(0); + close(0); + close(1); + peer = socket(AF_INET, SOCK_DGRAM, 0); + if (peer < 0) { + syslog(LOG_ERR, "socket: %m\n"); + exit(1); + } + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + if (bind(peer, (struct sockaddr *)&sin, sizeof (sin)) < 0) { + syslog(LOG_ERR, "bind: %m\n"); + exit(1); + } + if (connect(peer, (struct sockaddr *)&from, sizeof(from)) < 0) { + syslog(LOG_ERR, "connect: %m\n"); + exit(1); + } + tp = (struct tftphdr *)buf; + tp->th_opcode = ntohs(tp->th_opcode); + if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) + tftp(tp, n); + exit(1); } -reapchild() -{ - union wait status; - - while (wait3(&status, WNOHANG, 0) > 0) - ; -} - -int validate_access(); -int sendfile(), recvfile(); +struct formats; +int validate_access __P((char **, int)); +void sendfile __P((struct formats *)); +void recvfile __P((struct formats *)); struct formats { char *f_mode; - int (*f_validate)(); - int (*f_send)(); - int (*f_recv)(); + int (*f_validate) __P((char **, int)); + void (*f_send) __P((struct formats *)); + void (*f_recv) __P((struct formats *)); + int f_convert; } formats[] = { - { "netascii", validate_access, sendfile, recvfile }, - { "octet", validate_access, sendfile, recvfile }, + { "netascii", validate_access, sendfile, recvfile, 1 }, + { "octet", validate_access, sendfile, recvfile, 0 }, #ifdef notdef - { "mail", validate_user, sendmail, recvmail }, + { "mail", validate_user, sendmail, recvmail, 1 }, #endif { 0 } }; -int fd; /* file being transferred */ - /* * Handle initial connection protocol. */ -tftp(client, tp, size) - struct sockaddr_in *client; +void +tftp(tp, size) struct tftphdr *tp; int size; { @@ -131,10 +236,6 @@ tftp(client, tp, size) register struct formats *pf; char *filename, *mode; - if (connect(f, (caddr_t)client, sizeof (*client), 0) < 0) { - perror("connect"); - exit(1); - } filename = cp = tp->th_stuff; again: while (cp < buf + size) { @@ -161,8 +262,20 @@ again: nak(EBADOP); exit(1); } - ecode = (*pf->f_validate)(filename, client, tp->th_opcode); + ecode = (*pf->f_validate)(&filename, tp->th_opcode); + if (logging) { + syslog(LOG_INFO, "%s: %s request for %s: %s", + verifyhost(&from), + tp->th_opcode == WRQ ? "write" : "read", + filename, errtomsg(ecode)); + } if (ecode) { + /* + * Avoid storms of naks to a RRQ broadcast for a relative + * bootfile pathname from a diskless Sun. + */ + if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) + exit(0); nak(ecode); exit(1); } @@ -173,41 +286,110 @@ again: exit(0); } + +FILE *file; + /* * Validate file access. Since we * have no uid or gid, for now require * file to exist and be publicly * readable/writable. + * If we were invoked with arguments + * from inetd then the file must also be + * in one of the given directory prefixes. * Note also, full path name must be * given as we have no login directory. */ -validate_access(file, client, mode) - char *file; - struct sockaddr_in *client; +int +validate_access(filep, mode) + char **filep; int mode; { struct stat stbuf; + int fd; + struct dirlist *dirp; + static char pathname[MAXPATHLEN]; + char *filename = *filep; - if (*file != '/') + /* + * Prevent tricksters from getting around the directory restrictions + */ + if (strstr(filename, "/../")) return (EACCESS); - if (stat(file, &stbuf) < 0) - return (errno == ENOENT ? ENOTFOUND : EACCESS); - if (mode == RRQ) { - if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) + + if (*filename == '/') { + /* + * Allow the request if it's in one of the approved locations. + * Special case: check the null prefix ("/") by looking + * for length = 1 and relying on the arg. processing that + * it's a /. + */ + for (dirp = dirs; dirp->name != NULL; dirp++) { + if (dirp->len == 1 || + (!strncmp(filename, dirp->name, dirp->len) && + filename[dirp->len] == '/')) + break; + } + /* If directory list is empty, allow access to any file */ + if (dirp->name == NULL && dirp != dirs) return (EACCESS); + if (stat(filename, &stbuf) < 0) + return (errno == ENOENT ? ENOTFOUND : EACCESS); + if ((stbuf.st_mode & S_IFMT) != S_IFREG) + return (ENOTFOUND); + if (mode == RRQ) { + if ((stbuf.st_mode & S_IROTH) == 0) + return (EACCESS); + } else { + if ((stbuf.st_mode & S_IWOTH) == 0) + return (EACCESS); + } } else { - if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) + int err; + + /* + * Relative file name: search the approved locations for it. + * Don't allow write requests or ones that avoid directory + * restrictions. + */ + + if (mode != RRQ || !strncmp(filename, "../", 3)) return (EACCESS); + + /* + * If the file exists in one of the directories and isn't + * readable, continue looking. However, change the error code + * to give an indication that the file exists. + */ + err = ENOTFOUND; + for (dirp = dirs; dirp->name != NULL; dirp++) { + sprintf(pathname, "%s/%s", dirp->name, filename); + if (stat(pathname, &stbuf) == 0 && + (stbuf.st_mode & S_IFMT) == S_IFREG) { + if ((stbuf.st_mode & S_IROTH) != 0) { + break; + } + err = EACCESS; + } + } + if (dirp->name == NULL) + return (err); + *filep = filename = pathname; } - fd = open(file, mode == RRQ ? 0 : 1); + fd = open(filename, mode == RRQ ? 0 : 1); if (fd < 0) return (errno + 100); + file = fdopen(fd, (mode == RRQ)? "r":"w"); + if (file == NULL) { + return errno+100; + } return (0); } int timeout; jmp_buf timeoutbuf; +void timer() { @@ -220,92 +402,149 @@ timer() /* * Send the requested file. */ +void sendfile(pf) - struct format *pf; + struct formats *pf; { - register struct tftphdr *tp; - register int block = 1, size, n; + struct tftphdr *dp, *r_init(); + register struct tftphdr *ap; /* ack packet */ + register int size, n; + volatile int block; signal(SIGALRM, timer); - tp = (struct tftphdr *)buf; + dp = r_init(); + ap = (struct tftphdr *)ackbuf; + block = 1; do { - size = read(fd, tp->th_data, SEGSIZE); + size = readit(file, &dp, pf->f_convert); if (size < 0) { nak(errno + 100); goto abort; } - tp->th_opcode = htons((u_short)DATA); - tp->th_block = htons((u_short)block); + dp->th_opcode = htons((u_short)DATA); + dp->th_block = htons((u_short)block); timeout = 0; - (void) setjmp(timeoutbuf); - if (write(f, buf, size + 4) != size + 4) { - perror("tftpd: write"); + (void)setjmp(timeoutbuf); + +send_data: + if (send(peer, dp, size + 4, 0) != size + 4) { + syslog(LOG_ERR, "tftpd: write: %m\n"); goto abort; } - do { - alarm(rexmtval); - n = read(f, buf, sizeof (buf)); + read_ahead(file, pf->f_convert); + for ( ; ; ) { + alarm(rexmtval); /* read the ack */ + n = recv(peer, ackbuf, sizeof (ackbuf), 0); alarm(0); if (n < 0) { - perror("tftpd: read"); + syslog(LOG_ERR, "tftpd: read: %m\n"); goto abort; } - tp->th_opcode = ntohs((u_short)tp->th_opcode); - tp->th_block = ntohs((u_short)tp->th_block); - if (tp->th_opcode == ERROR) + ap->th_opcode = ntohs((u_short)ap->th_opcode); + ap->th_block = ntohs((u_short)ap->th_block); + + if (ap->th_opcode == ERROR) goto abort; - } while (tp->th_opcode != ACK || tp->th_block != block); + + if (ap->th_opcode == ACK) { + if (ap->th_block == block) + break; + /* Re-synchronize with the other side */ + (void) synchnet(peer); + if (ap->th_block == (block -1)) + goto send_data; + } + + } block++; } while (size == SEGSIZE); abort: - (void) close(fd); + (void) fclose(file); +} + +void +justquit() +{ + exit(0); } + /* * Receive a file. */ +void recvfile(pf) - struct format *pf; + struct formats *pf; { - register struct tftphdr *tp; - register int block = 0, n, size; + struct tftphdr *dp, *w_init(); + register struct tftphdr *ap; /* ack buffer */ + register int n, size; + volatile int block; signal(SIGALRM, timer); - tp = (struct tftphdr *)buf; + dp = w_init(); + ap = (struct tftphdr *)ackbuf; + block = 0; do { timeout = 0; - tp->th_opcode = htons((u_short)ACK); - tp->th_block = htons((u_short)block); + ap->th_opcode = htons((u_short)ACK); + ap->th_block = htons((u_short)block); block++; (void) setjmp(timeoutbuf); - if (write(f, buf, 4) != 4) { - perror("tftpd: write"); +send_ack: + if (send(peer, ackbuf, 4, 0) != 4) { + syslog(LOG_ERR, "tftpd: write: %m\n"); goto abort; } - do { + write_behind(file, pf->f_convert); + for ( ; ; ) { alarm(rexmtval); - n = read(f, buf, sizeof (buf)); + n = recv(peer, dp, PKTSIZE, 0); alarm(0); - if (n < 0) { - perror("tftpd: read"); + if (n < 0) { /* really? */ + syslog(LOG_ERR, "tftpd: read: %m\n"); goto abort; } - tp->th_opcode = ntohs((u_short)tp->th_opcode); - tp->th_block = ntohs((u_short)tp->th_block); - if (tp->th_opcode == ERROR) + dp->th_opcode = ntohs((u_short)dp->th_opcode); + dp->th_block = ntohs((u_short)dp->th_block); + if (dp->th_opcode == ERROR) goto abort; - } while (tp->th_opcode != DATA || block != tp->th_block); - size = write(fd, tp->th_data, n - 4); - if (size < 0) { - nak(errno + 100); + if (dp->th_opcode == DATA) { + if (dp->th_block == block) { + break; /* normal */ + } + /* Re-synchronize with the other side */ + (void) synchnet(peer); + if (dp->th_block == (block-1)) + goto send_ack; /* rexmit */ + } + } + /* size = write(file, dp->th_data, n - 4); */ + size = writeit(file, &dp, n - 4, pf->f_convert); + if (size != (n-4)) { /* ahem */ + if (size < 0) nak(errno + 100); + else nak(ENOSPACE); goto abort; } } while (size == SEGSIZE); + write_behind(file, pf->f_convert); + (void) fclose(file); /* close data file */ + + ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ + ap->th_block = htons((u_short)(block)); + (void) send(peer, ackbuf, 4, 0); + + signal(SIGALRM, justquit); /* just quit on timeout */ + alarm(rexmtval); + n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ + alarm(0); + if (n >= 4 && /* if read some data */ + dp->th_opcode == DATA && /* and got a data block */ + block == dp->th_block) { /* then my last ack was lost */ + (void) send(peer, ackbuf, 4, 0); /* resend final ack */ + } abort: - tp->th_opcode = htons((u_short)ACK); - tp->th_block = htons((u_short)(block)); - (void) write(f, buf, 4); - (void) close(fd); + return; } struct errmsg { @@ -323,19 +562,34 @@ struct errmsg { { -1, 0 } }; +static char * +errtomsg(error) + int error; +{ + static char buf[20]; + register struct errmsg *pe; + if (error == 0) + return "success"; + for (pe = errmsgs; pe->e_code >= 0; pe++) + if (pe->e_code == error) + return pe->e_msg; + sprintf(buf, "error %d", error); + return buf; +} + /* * Send a nak packet (error message). * Error code passed in is one of the * standard TFTP codes, or a UNIX errno * offset by 100. */ +static void nak(error) int error; { register struct tftphdr *tp; int length; register struct errmsg *pe; - extern char *sys_errlist[]; tp = (struct tftphdr *)buf; tp->th_opcode = htons((u_short)ERROR); @@ -343,12 +597,28 @@ nak(error) for (pe = errmsgs; pe->e_code >= 0; pe++) if (pe->e_code == error) break; - if (pe->e_code < 0) - pe->e_msg = sys_errlist[error - 100]; + if (pe->e_code < 0) { + pe->e_msg = strerror(error - 100); + tp->th_code = EUNDEF; /* set 'undef' errorcode */ + } strcpy(tp->th_msg, pe->e_msg); length = strlen(pe->e_msg); tp->th_msg[length] = '\0'; length += 5; - if (write(f, buf, length) != length) - perror("nak"); + if (send(peer, buf, length, 0) != length) + syslog(LOG_ERR, "nak: %m\n"); +} + +static char * +verifyhost(fromp) + struct sockaddr_in *fromp; +{ + struct hostent *hp; + + hp = gethostbyaddr((char *)&fromp->sin_addr, sizeof (fromp->sin_addr), + fromp->sin_family); + if (hp) + return hp->h_name; + else + return inet_ntoa(fromp->sin_addr); }