4.4BSD snapshot (revision 8.1); add 1993 to copyright
[unix-history] / usr / src / libexec / tftpd / tftpd.c
index f0b8e09..3e9c0f7 100644 (file)
@@ -1,53 +1,52 @@
 /*
 /*
- * Copyright (c) 1983 Regents of the University of California.
- * All rights reserved.
+ * Copyright (c) 1983, 1993
+ *     The Regents of the University of California.  All rights reserved.
  *
  *
- * Redistribution and use in source and binary forms are permitted
- * provided that this notice is preserved and that due credit is given
- * to the University of California at 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'' without express or implied warranty.
+ * %sccs.include.redist.c%
  */
 
 #ifndef lint
  */
 
 #ifndef lint
-char copyright[] =
-"@(#) Copyright (c) 1983 Regents of the University of California.\n\
- All rights reserved.\n";
+static char copyright[] =
+"@(#) Copyright (c) 1983, 1993\n\
      The Regents of the University of California.  All rights reserved.\n";
 #endif /* not lint */
 
 #ifndef lint
 #endif /* not lint */
 
 #ifndef lint
-static char sccsid[] = "@(#)tftpd.c    5.7 (Berkeley) %G%";
+static char sccsid[] = "@(#)tftpd.c    8.1 (Berkeley) %G%";
 #endif /* not lint */
 
 /*
  * Trivial file transfer protocol server.
  *
 #endif /* not lint */
 
 /*
  * Trivial file transfer protocol server.
  *
- * This version includes many modifications by Jim Guyton <guyton@rand-unix>
+ * This version includes many modifications by Jim Guyton
+ * <guyton@rand-unix>.
  */
 
  */
 
-#include <sys/types.h>
-#include <sys/socket.h>
+#include <sys/param.h>
 #include <sys/ioctl.h>
 #include <sys/ioctl.h>
-#include <sys/wait.h>
 #include <sys/stat.h>
 #include <sys/stat.h>
+#include <sys/socket.h>
 
 #include <netinet/in.h>
 
 #include <netinet/in.h>
-
 #include <arpa/tftp.h>
 #include <arpa/tftp.h>
+#include <arpa/inet.h>
 
 
-#include <signal.h>
-#include <stdio.h>
-#include <errno.h>
 #include <ctype.h>
 #include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
 #include <netdb.h>
 #include <setjmp.h>
 #include <netdb.h>
 #include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 #include <syslog.h>
 #include <syslog.h>
+#include <unistd.h>
+
+#include "tftpsubs.h"
 
 #define        TIMEOUT         5
 
 
 #define        TIMEOUT         5
 
-extern int errno;
-struct sockaddr_in sin = { AF_INET };
 int    peer;
 int    rexmtval = TIMEOUT;
 int    maxtimeout = 5*TIMEOUT;
 int    peer;
 int    rexmtval = TIMEOUT;
 int    maxtimeout = 5*TIMEOUT;
@@ -58,20 +57,72 @@ char        ackbuf[PKTSIZE];
 struct sockaddr_in from;
 int    fromlen;
 
 struct sockaddr_in from;
 int    fromlen;
 
-main()
+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[];
 {
        register struct tftphdr *tp;
        register int n;
 {
        register struct tftphdr *tp;
        register int n;
-       int on = 1;
+       int ch, on;
+       struct sockaddr_in sin;
+
+       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++;
+                       }
+               }
+       }
 
 
-       openlog("tftpd", LOG_PID, LOG_DAEMON);
+       on = 1;
        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,
        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,
-           (caddr_t)&from, &fromlen);
+           (struct sockaddr *)&from, &fromlen);
        if (n < 0) {
                syslog(LOG_ERR, "recvfrom: %m\n");
                exit(1);
        if (n < 0) {
                syslog(LOG_ERR, "recvfrom: %m\n");
                exit(1);
@@ -110,7 +161,7 @@ main()
                                 */
                                j = sizeof from;
                                i = recvfrom(0, buf, sizeof (buf), 0,
                                 */
                                j = sizeof from;
                                i = recvfrom(0, buf, sizeof (buf), 0,
-                                   (caddr_t)&from, &j);
+                                   (struct sockaddr *)&from, &j);
                                if (i > 0) {
                                        n = i;
                                        fromlen = j;
                                if (i > 0) {
                                        n = i;
                                        fromlen = j;
@@ -135,11 +186,13 @@ main()
                syslog(LOG_ERR, "socket: %m\n");
                exit(1);
        }
                syslog(LOG_ERR, "socket: %m\n");
                exit(1);
        }
-       if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) {
+       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);
        }
                syslog(LOG_ERR, "bind: %m\n");
                exit(1);
        }
-       if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) {
+       if (connect(peer, (struct sockaddr *)&from, sizeof(from)) < 0) {
                syslog(LOG_ERR, "connect: %m\n");
                exit(1);
        }
                syslog(LOG_ERR, "connect: %m\n");
                exit(1);
        }
@@ -150,14 +203,16 @@ main()
        exit(1);
 }
 
        exit(1);
 }
 
-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;
 
 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, 1 },
        int     f_convert;
 } formats[] = {
        { "netascii",   validate_access,        sendfile,       recvfile, 1 },
@@ -171,6 +226,7 @@ struct formats {
 /*
  * Handle initial connection protocol.
  */
 /*
  * Handle initial connection protocol.
  */
+void
 tftp(tp, size)
        struct tftphdr *tp;
        int size;
 tftp(tp, size)
        struct tftphdr *tp;
        int size;
@@ -206,8 +262,20 @@ again:
                nak(EBADOP);
                exit(1);
        }
                nak(EBADOP);
                exit(1);
        }
-       ecode = (*pf->f_validate)(filename, 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) {
        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);
        }
                nak(ecode);
                exit(1);
        }
@@ -226,26 +294,87 @@ FILE *file;
  * have no uid or gid, for now require
  * file to exist and be publicly
  * readable/writable.
  * 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.
  */
  * Note also, full path name must be
  * given as we have no login directory.
  */
-validate_access(filename, mode)
-       char *filename;
+int
+validate_access(filep, mode)
+       char **filep;
        int mode;
 {
        struct stat stbuf;
        int     fd;
        int mode;
 {
        struct stat stbuf;
        int     fd;
+       struct dirlist *dirp;
+       static char pathname[MAXPATHLEN];
+       char *filename = *filep;
 
 
-       if (*filename != '/')
+       /*
+        * Prevent tricksters from getting around the directory restrictions
+        */
+       if (strstr(filename, "/../"))
                return (EACCESS);
                return (EACCESS);
-       if (stat(filename, &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);
                        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 {
        } 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);
                        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(filename, mode == RRQ ? 0 : 1);
        if (fd < 0)
        }
        fd = open(filename, mode == RRQ ? 0 : 1);
        if (fd < 0)
@@ -260,6 +389,7 @@ validate_access(filename, mode)
 int    timeout;
 jmp_buf        timeoutbuf;
 
 int    timeout;
 jmp_buf        timeoutbuf;
 
+void
 timer()
 {
 
 timer()
 {
 
@@ -272,16 +402,19 @@ timer()
 /*
  * Send the requested file.
  */
 /*
  * Send the requested file.
  */
+void
 sendfile(pf)
        struct formats *pf;
 {
        struct tftphdr *dp, *r_init();
        register struct tftphdr *ap;    /* ack packet */
 sendfile(pf)
        struct formats *pf;
 {
        struct tftphdr *dp, *r_init();
        register struct tftphdr *ap;    /* ack packet */
-       register int block = 1, size, n;
+       register int size, n;
+       volatile int block;
 
        signal(SIGALRM, timer);
        dp = r_init();
        ap = (struct tftphdr *)ackbuf;
 
        signal(SIGALRM, timer);
        dp = r_init();
        ap = (struct tftphdr *)ackbuf;
+       block = 1;
        do {
                size = readit(file, &dp, pf->f_convert);
                if (size < 0) {
        do {
                size = readit(file, &dp, pf->f_convert);
                if (size < 0) {
@@ -291,7 +424,7 @@ sendfile(pf)
                dp->th_opcode = htons((u_short)DATA);
                dp->th_block = htons((u_short)block);
                timeout = 0;
                dp->th_opcode = htons((u_short)DATA);
                dp->th_block = htons((u_short)block);
                timeout = 0;
-               (void) setjmp(timeoutbuf);
+               (void)setjmp(timeoutbuf);
 
 send_data:
                if (send(peer, dp, size + 4, 0) != size + 4) {
 
 send_data:
                if (send(peer, dp, size + 4, 0) != size + 4) {
@@ -312,16 +445,14 @@ send_data:
 
                        if (ap->th_opcode == ERROR)
                                goto abort;
 
                        if (ap->th_opcode == ERROR)
                                goto abort;
-                       
+
                        if (ap->th_opcode == ACK) {
                        if (ap->th_opcode == ACK) {
-                               if (ap->th_block == block) {
+                               if (ap->th_block == block)
                                        break;
                                        break;
-                               }
                                /* Re-synchronize with the other side */
                                (void) synchnet(peer);
                                /* Re-synchronize with the other side */
                                (void) synchnet(peer);
-                               if (ap->th_block == (block -1)) {
+                               if (ap->th_block == (block -1))
                                        goto send_data;
                                        goto send_data;
-                               }
                        }
 
                }
                        }
 
                }
@@ -331,6 +462,7 @@ abort:
        (void) fclose(file);
 }
 
        (void) fclose(file);
 }
 
+void
 justquit()
 {
        exit(0);
 justquit()
 {
        exit(0);
@@ -340,16 +472,19 @@ justquit()
 /*
  * Receive a file.
  */
 /*
  * Receive a file.
  */
+void
 recvfile(pf)
        struct formats *pf;
 {
        struct tftphdr *dp, *w_init();
        register struct tftphdr *ap;    /* ack buffer */
 recvfile(pf)
        struct formats *pf;
 {
        struct tftphdr *dp, *w_init();
        register struct tftphdr *ap;    /* ack buffer */
-       register int block = 0, n, size;
+       register int n, size;
+       volatile int block;
 
        signal(SIGALRM, timer);
        dp = w_init();
        ap = (struct tftphdr *)ackbuf;
 
        signal(SIGALRM, timer);
        dp = w_init();
        ap = (struct tftphdr *)ackbuf;
+       block = 0;
        do {
                timeout = 0;
                ap->th_opcode = htons((u_short)ACK);
        do {
                timeout = 0;
                ap->th_opcode = htons((u_short)ACK);
@@ -427,19 +562,34 @@ struct errmsg {
        { -1,           0 }
 };
 
        { -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.
  */
 /*
  * 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;
 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 = (struct tftphdr *)buf;
        tp->th_opcode = htons((u_short)ERROR);
@@ -448,7 +598,7 @@ nak(error)
                if (pe->e_code == error)
                        break;
        if (pe->e_code < 0) {
                if (pe->e_code == error)
                        break;
        if (pe->e_code < 0) {
-               pe->e_msg = sys_errlist[error - 100];
+               pe->e_msg = strerror(error - 100);
                tp->th_code = EUNDEF;   /* set 'undef' errorcode */
        }
        strcpy(tp->th_msg, pe->e_msg);
                tp->th_code = EUNDEF;   /* set 'undef' errorcode */
        }
        strcpy(tp->th_msg, pe->e_msg);
@@ -458,3 +608,17 @@ nak(error)
        if (send(peer, buf, length, 0) != length)
                syslog(LOG_ERR, "nak: %m\n");
 }
        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);
+}