| /* |
| * 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"); |
| } |