123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742 |
- /*
- * 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 <guyton@rand-unix>
- *
- * Further modifications by Markus Gutschke <gutschk@math.uni-muenster.de>
- * - RFC1782 option parsing
- * - RFC1783 extended blocksize
- * - "-c" option for changing the root directory
- * - "-d" option for debugging output
- */
-
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <sys/ioctl.h>
- #include <sys/wait.h>
- #include <sys/stat.h>
-
- #include <netinet/in.h>
-
- #include <arpa/tftp.h>
-
- #include <alloca.h>
- #include <string.h>
- #include <signal.h>
- #include <stdio.h>
- #include <errno.h>
- #include <ctype.h>
- #include <netdb.h>
- #include <setjmp.h>
- #include <syslog.h>
-
- #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");
- }
|