/* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms and that any documentation, * advertising materials, and other materials related to such * distribution and use acknowledge that the software was developed * by the University of California, Berkeley. The name of the * University may not be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #ifndef lint char copyright[] = "@(#) Copyright (c) 1983 Regents of the University of California.\n\ All rights reserved.\n"; #endif /* not lint */ #ifndef lint static char sccsid[] = "@(#)tftpd.c 5.8 (Berkeley) 6/18/88"; #endif /* not lint */ /* * Trivial file transfer protocol server. * * This version includes many modifications by Jim Guyton * * Further modifications by Markus Gutschke * - RFC1782 option parsing * - RFC1783 extended blocksize * - "-c" option for changing the root directory * - "-d" option for debugging output */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TIMEOUT 5 #ifndef OACK #define OACK 06 #endif #ifndef EOPTNEG #define EOPTNEG 8 #endif extern int errno; struct sockaddr_in sin = { AF_INET }; int peer; int rexmtval = TIMEOUT; int maxtimeout = 5*TIMEOUT; #define PKTSIZE (1432+4) /* SEGSIZE+4 */ int segsize = SEGSIZE; char buf[PKTSIZE]; char ackbuf[PKTSIZE]; struct sockaddr_in from; int fromlen; char *rootdir = NULL; int debug = 0; struct filters { struct filters *next; char *fname; } *filters = NULL; int isfilter = 0; main(argc, argv) char *argv[]; { register struct tftphdr *tp; register int n; int on = 1; extern int optind; extern char *optarg; openlog(argv[0], LOG_PID, LOG_DAEMON); while ((n = getopt(argc, argv, "c:dr:")) >= 0) { switch (n) { case 'c': if (rootdir) goto usage; rootdir = optarg; break; case 'd': debug++; break; case 'r': { struct filters *fp = (void *) malloc(sizeof(struct filters) + strlen(optarg) + 1); fp->next = filters; fp->fname = (char *)(fp + 1); strcpy(fp->fname, optarg); filters = fp; break; } default: usage: syslog(LOG_ERR, "Usage: %s [-c chroot] " "[-r readfilter] [-d]\n", argv[0]); exit(1); } } if (argc-optind != 0) goto usage; ioctl(0, FIONBIO, &on); /* if (ioctl(0, FIONBIO, &on) < 0) { syslog(LOG_ERR, "ioctl(FIONBIO): %m\n"); exit(1); } */ fromlen = sizeof (from); n = recvfrom(0, buf, segsize+4, 0, (struct sockaddr *)&from, &fromlen); if (n < 0) { syslog(LOG_ERR, "recvfrom: %m\n"); exit(1); } /* * 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, segsize+4, 0, (struct sockaddr *)&from, &j); if (i > 0) { n = i; fromlen = j; } } else { break; } } if (pid < 0) { syslog(LOG_ERR, "fork: %m\n"); exit(1); } else if (pid != 0) { exit(0); } } 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); } 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); } int validate_access(); int sendfile(), recvfile(); struct formats { char *f_mode; int (*f_validate)(); int (*f_send)(); int (*f_recv)(); int f_convert; } formats[] = { { "netascii", validate_access, sendfile, recvfile, 1 }, { "octet", validate_access, sendfile, recvfile, 0 }, #ifdef notdef { "mail", validate_user, sendmail, recvmail, 1 }, #endif { 0 } }; int set_blksize(); struct options { char *o_opt; int (*o_fnc)(); } options[] = { { "blksize", set_blksize }, { 0 } }; /* * Set a non-standard block size (c.f. RFC1783) */ set_blksize(val, ret) char *val; char **ret; { static char b_ret[5]; int sz = atoi(val); if (sz < 8) { if (debug) syslog(LOG_ERR, "Requested packetsize %d < 8\n", sz); return(0); } else if (sz > PKTSIZE-4) { if (debug) syslog(LOG_INFO, "Requested packetsize %d > %d\n", sz, PKTSIZE-4); sz = PKTSIZE-4; } else if (debug) syslog(LOG_INFO, "Adjusted packetsize to %d octets\n", sz); segsize = sz; sprintf(*ret = b_ret, "%d", sz); return(1); } /* * Parse RFC1782 style options */ do_opt(opt, val, ap) char *opt; char *val; char **ap; { struct options *po; char *ret; for (po = options; po->o_opt; po++) if (strcasecmp(po->o_opt, opt) == 0) { if (po->o_fnc(val, &ret)) { if (*ap + strlen(opt) + strlen(ret) + 2 >= ackbuf + sizeof(ackbuf)) { if (debug) syslog(LOG_ERR, "Ackbuf overflow\n"); nak(ENOSPACE); exit(1); } *ap = strrchr(strcpy(strrchr(strcpy(*ap, opt), '\000')+1, val), '\000')+1; } else { nak(EOPTNEG); exit(1); } break; } if (debug && !po->o_opt) syslog(LOG_WARNING, "Unhandled option: %d = %d\n", opt, val); return; } /* * Handle initial connection protocol. */ tftp(tp, size) struct tftphdr *tp; int size; { register char *cp; int argn = 0, ecode; register struct formats *pf; char *filename, *mode; char *val, *opt; char *ap = ackbuf+2; int isopts; ((struct tftphdr *)ackbuf)->th_opcode = ntohs(OACK); filename = cp = tp->th_stuff; again: while (cp < buf + size) { if (*cp == '\0') break; cp++; } if (*cp != '\0') { if (debug) syslog(LOG_WARNING, "Received illegal request\n"); nak(EBADOP); exit(1); } if (!argn++) { mode = ++cp; goto again; } else { if (debug && argn == 3) syslog(LOG_INFO, "Found RFC1782 style options\n"); *(argn & 1 ? &val : &opt) = ++cp; if (argn & 1) do_opt(opt, val, &ap); if (cp < buf + size && *cp != '\000') goto again; } for (cp = mode; *cp; cp++) if (isupper(*cp)) *cp = tolower(*cp); for (pf = formats; pf->f_mode; pf++) if (strcmp(pf->f_mode, mode) == 0) break; if (pf->f_mode == 0) { if (debug) syslog(LOG_WARNING, "Unknown data format: %s\n", mode); nak(EBADOP); exit(1); } if (rootdir) { cp = alloca(strlen(rootdir) + strlen(filename) + 1); if (cp == NULL) { nak(100+ENOMEM); exit(1); } if (*filename != '/') { if (debug) syslog(LOG_ERR, "Filename has to be absolute: %s\n", filename); nak(EACCESS); exit(1); } filename = strcat(strcpy(cp, rootdir), filename); } ecode = (*pf->f_validate)(filename, tp->th_opcode); if (ecode) { nak(ecode, ERROR); exit(1); } isopts = ap != (ackbuf+2); (tp->th_opcode == WRQ ? *pf->f_recv : *pf->f_send) (pf, isopts ? ackbuf : NULL, isopts ? ap-ackbuf : 0); 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. * Note also, full path name must be * given as we have no login directory. */ validate_access(filename, mode) char *filename; int mode; { struct stat stbuf; int fd; char *cp; isfilter = 0; if (mode == RRQ) { struct filters *fp = filters; for (; fp; fp = fp->next) { if (!strcmp(fp->fname, filename + (rootdir ? strlen(rootdir) : 0))) { if (debug) syslog(LOG_INFO, "Opening input " "filter: %s\n", filename); if ((file = popen(filename, "r")) == NULL) { syslog(LOG_ERR, "Failed to open input " "filter\n"); return (EACCESS); } fd = fileno(file); isfilter = 1; return (0); } } } if (*filename != '/') { if (debug) syslog(LOG_ERR, "Filename has to be absolute: %s\n", filename); return (EACCESS); } for (cp = filename; *cp; cp++) if (*cp == '~' || *cp == '$' || (*cp == '/' && cp[1] == '.' && cp[2] == '.')) { if (debug) syslog(LOG_ERR, "Illegal filename: %s\n", filename); return (EACCESS); } if (debug) syslog(LOG_INFO, "Validating \"%s\" for %sing\n", filename, mode == RRQ ? "read" : "writ"); if (stat(filename, &stbuf) < 0) return (errno == ENOENT ? ENOTFOUND : EACCESS); if (mode == RRQ) { if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) return (EACCESS); } else { if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) return (EACCESS); } 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(int sig) { timeout += rexmtval; if (timeout >= maxtimeout) { if (debug) syslog(LOG_WARNING, "Timeout!\n"); exit(1); } longjmp(timeoutbuf, 1); } /* * Send the requested file. */ sendfile(pf, oap, oacklen) struct formats *pf; struct tftphdr *oap; int oacklen; { struct tftphdr *dp, *r_init(); register struct tftphdr *ap; /* ack packet */ register int size, n; u_short block = 1; signal(SIGALRM, timer); ap = (struct tftphdr *)ackbuf; if (oap) { timeout = 0; (void) setjmp(timeoutbuf); oack: if (send(peer, oap, oacklen, 0) != oacklen) { syslog(LOG_ERR, "tftpd: write: %m\n"); goto abort; } for ( ; ; ) { alarm(rexmtval); n = recv(peer, ackbuf, sizeof (ackbuf), 0); alarm(0); if (n < 0) { syslog(LOG_ERR, "tftpd: read: %m\n"); goto abort; } ap->th_opcode = ntohs((u_short)ap->th_opcode); ap->th_block = ntohs(ap->th_block); if (ap->th_opcode == ERROR) { if (debug) syslog(LOG_ERR, "Client does not " "accept options\n"); goto abort; } if (ap->th_opcode == ACK) { if (ap->th_block == 0) { if (debug) syslog(LOG_DEBUG, "RFC1782 option " "negotiation " "succeeded\n"); break; } /* Re-synchronize with the other side */ (void) synchnet(peer); goto oack; } } } dp = r_init(); do { size = readit(file, &dp, pf->f_convert); if (size < 0) { nak(errno + 100); goto abort; } dp->th_opcode = htons((u_short)DATA); dp->th_block = htons(block); timeout = 0; (void) setjmp(timeoutbuf); send_data: if (send(peer, dp, size + 4, 0) != size + 4) { syslog(LOG_ERR, "tftpd: write: %m\n"); goto abort; } read_ahead(file, pf->f_convert); for ( ; ; ) { alarm(rexmtval); /* read the ack */ n = recv(peer, ackbuf, sizeof (ackbuf), 0); alarm(0); if (n < 0) { syslog(LOG_ERR, "tftpd: read: %m\n"); goto abort; } ap->th_opcode = ntohs((u_short)ap->th_opcode); ap->th_block = ntohs(ap->th_block); if (ap->th_opcode == ERROR) goto abort; 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: if (isfilter) pclose(file); else (void) fclose(file); isfilter = 0; } void justquit(int sig) { exit(0); } /* * Receive a file. */ recvfile(pf, oap, oacklen) struct formats *pf; struct tftphdr *oap; int oacklen; { struct tftphdr *dp, *w_init(); register struct tftphdr *ap; /* ack buffer */ register int acksize, n, size; u_short block = 0; signal(SIGALRM, timer); dp = w_init(); do { timeout = 0; if (!block++ && oap) { ap = (struct tftphdr *)oap; acksize = oacklen; } else { ap = (struct tftphdr *)ackbuf; ap->th_opcode = htons((u_short)ACK); ap->th_block = htons(block-1); acksize = 4; } (void) setjmp(timeoutbuf); send_ack: if (send(peer, (char *)ap, acksize, 0) != acksize) { syslog(LOG_ERR, "tftpd: write: %m\n"); goto abort; } write_behind(file, pf->f_convert); for ( ; ; ) { alarm(rexmtval); n = recv(peer, dp, segsize+4, 0); alarm(0); if (n < 0) { /* really? */ syslog(LOG_ERR, "tftpd: read: %m\n"); goto abort; } dp->th_opcode = ntohs((u_short)dp->th_opcode); dp->th_block = ntohs(dp->th_block); if (dp->th_opcode == ERROR) goto abort; 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); if (isfilter) pclose(file); else (void) fclose(file); /* close data file */ isfilter = 0; ap = (struct tftphdr *)ackbuf; ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ ap->th_block = htons(block); (void) send(peer, ackbuf, 4, 0); signal(SIGALRM, justquit); /* just quit on timeout */ alarm(rexmtval); n = recv(peer, buf, segsize, 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: return; } struct errmsg { int e_code; const char *e_msg; } errmsgs[] = { { EUNDEF, "Undefined error code" }, { ENOTFOUND, "File not found" }, { EACCESS, "Access violation" }, { ENOSPACE, "Disk full or allocation exceeded" }, { EBADOP, "Illegal TFTP operation" }, { EBADID, "Unknown transfer ID" }, { EEXISTS, "File already exists" }, { ENOUSER, "No such user" }, { EOPTNEG, "Failure to negotiate RFC1782 options" }, { -1, 0 } }; /* * Send a nak packet (error message). * Error code passed in is one of the * standard TFTP codes, or a UNIX errno * offset by 100. */ 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); tp->th_code = htons((u_short)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]; 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 (debug) syslog(LOG_ERR, "Negative acknowledge: %s\n", tp->th_msg); if (send(peer, buf, length, 0) != length) syslog(LOG_ERR, "nak: %m\n"); }