/* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #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[] = "from: @(#)tftpd.c 5.13 (Berkeley) 2/26/91";*/ /*static char rcsid[] = "$Id: tftpd.c,v 1.3 1993/08/01 18:28:53 mycroft Exp $";*/ #endif /* not lint */ /* * Trivial file transfer protocol server. * * This version includes many modifications by Jim Guyton <guyton@rand-unix> */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <unistd.h> #include <signal.h> #include <fcntl.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <setjmp.h> #include <syslog.h> #include <stdio.h> #include <errno.h> #include <ctype.h> #include <string.h> #include <stdlib.h> #include "tftp.h" #ifndef MSG_CONFIRM #define MSG_CONFIRM 0 #warning Please, upgrade kernel, otherwise this tftpd has no advantages. #endif #define TIMEOUT 5 int peer; int rexmtval = TIMEOUT; int maxtimeout = 5*TIMEOUT; #define PKTSIZE SEGSIZE+4 char buf[PKTSIZE]; char ackbuf[PKTSIZE]; union { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; } from; socklen_t fromlen; #define MAXARG 1 char *dirs[MAXARG+1]; void tftp(struct tftphdr *tp, int size) __attribute__((noreturn)); void nak(int error); int validate_access(char *filename, int mode); struct formats; void sendfile(struct formats *pf); void recvfile(struct formats *pf); int main(int ac, char **av) { register struct tftphdr *tp; register int n = 0; int on = 1; /* Sanity. If parent forgot to setuid() on us. */ if (geteuid() == 0) { setgid(65534); setuid(65534); } ac--; av++; while (ac-- > 0 && n < MAXARG) dirs[n++] = *av++; openlog("tftpd", LOG_PID, LOG_DAEMON); if (ioctl(0, FIONBIO, &on) < 0) { syslog(LOG_ERR, "ioctl(FIONBIO): %m\n"); exit(1); } fromlen = sizeof (from); n = recvfrom(0, buf, sizeof (buf), 0, (struct sockaddr *)&from, &fromlen); if (n < 0) { if (errno != EAGAIN) 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; socklen_t 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 (pid < 0) { syslog(LOG_ERR, "fork: %m\n"); exit(1); } else if (pid != 0) { exit(0); } } alarm(0); close(0); close(1); peer = socket(from.sa.sa_family, SOCK_DGRAM, 0); if (peer < 0) { syslog(LOG_ERR, "socket: %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); } struct formats { char *f_mode; int (*f_validate)(char *filename, int mode); void (*f_send)(struct formats*); void (*f_recv)(struct formats*); 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 } }; /* * Handle initial connection protocol. */ void tftp(struct tftphdr *tp, int size) { register char *cp; int first = 1, ecode; register struct formats *pf; char *filename, *mode = NULL; filename = cp = tp->th_stuff; again: while (cp < buf + size) { if (*cp == '\0') break; cp++; } if (*cp != '\0') { nak(EBADOP); exit(1); } if (first) { mode = ++cp; first = 0; 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) { nak(EBADOP); exit(1); } ecode = (*pf->f_validate)(filename, tp->th_opcode); if (ecode) { nak(ecode); exit(1); } if (tp->th_opcode == WRQ) (*pf->f_recv)(pf); else (*pf->f_send)(pf); 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. */ int validate_access(char *filename, int mode) { struct stat stbuf; int fd; char *cp; char fnamebuf[1024+512]; for (cp = filename; *cp; cp++) { if(*cp == '.' && (cp == filename || strncmp(cp-1, "/../", 4) == 0)) { syslog(LOG_ERR, "bad path %s", filename); return(EACCESS); } } if (*filename == '/') filename++; if (!*dirs) { syslog(LOG_ERR, "no dirs"); return EACCESS; } snprintf(fnamebuf, sizeof(fnamebuf)-1, "%s/%s", *dirs, filename); filename = fnamebuf; if (stat(filename, &stbuf) < 0) { syslog(LOG_ERR, "stat %s : %m", filename); return (errno == ENOENT ? ENOTFOUND : EACCESS); } if (mode == RRQ) { if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) { syslog(LOG_ERR, "not readable %s", filename); return (EACCESS); } } else { if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) { syslog(LOG_ERR, "not writable %s", filename); return (EACCESS); } } fd = open(filename, mode == RRQ ? 0 : 1); if (fd < 0) { syslog(LOG_ERR, "cannot open %s: %m", filename); return (errno + 100); } file = fdopen(fd, (mode == RRQ)? "r":"w"); if (file == NULL) { return errno+100; } return (0); } int confirmed; int timeout; jmp_buf timeoutbuf; void timer(int signo) { confirmed = 0; timeout += rexmtval; if (timeout >= maxtimeout) exit(1); longjmp(timeoutbuf, 1); } /* * Send the requested file. */ void sendfile(struct formats *pf) { struct tftphdr *dp; register struct tftphdr *ap; /* ack packet */ volatile int block = 1; int size, n; confirmed = 0; signal(SIGALRM, timer); dp = r_init(); ap = (struct tftphdr *)ackbuf; 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((u_short)block); timeout = 0; (void) setjmp(timeoutbuf); send_data: if (send(peer, dp, size + 4, confirmed) != size + 4) { syslog(LOG_ERR, "tftpd: write: %m\n"); goto abort; } confirmed = 0; 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((u_short)ap->th_block); if (ap->th_opcode == ERROR) goto abort; if (ap->th_opcode == ACK) { if (ap->th_block == block) { confirmed = MSG_CONFIRM; break; } /* Re-synchronize with the other side */ synchnet(peer); if (ap->th_block == (block -1)) { goto send_data; } } } block++; } while (size == SEGSIZE); abort: (void) fclose(file); } void justquit(int signo) { exit(0); } /* * Receive a file. */ void recvfile(struct formats *pf) { struct tftphdr *dp; register struct tftphdr *ap; /* ack buffer */ volatile int block = 0, n, size; confirmed = 0; signal(SIGALRM, timer); dp = w_init(); ap = (struct tftphdr *)ackbuf; do { timeout = 0; ap->th_opcode = htons((u_short)ACK); ap->th_block = htons((u_short)block); block++; (void) setjmp(timeoutbuf); send_ack: if (send(peer, ackbuf, 4, confirmed) != 4) { syslog(LOG_ERR, "tftpd: write: %m\n"); goto abort; } confirmed = 0; write_behind(file, pf->f_convert); for ( ; ; ) { alarm(rexmtval); n = recv(peer, dp, PKTSIZE, 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((u_short)dp->th_block); if (dp->th_opcode == ERROR) goto abort; if (dp->th_opcode == DATA) { if (dp->th_block == block) { confirmed = MSG_CONFIRM; 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, confirmed); 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: return; } struct errmsg { int e_code; 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" }, { -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. */ void nak(int error) { register struct tftphdr *tp; int length; register struct errmsg *pe; 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 = 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 (send(peer, buf, length, 0) != length) syslog(LOG_ERR, "nak: %m\n"); }