Author: Frank Lee <rl201@cam.ac.uk>
Description:
 Adds the -x option to execute and return results of files which have the
 executable bit set.
 Adds the -M option to execute an aribtrary script to change the name of the
 requested file.

--- tftp-hpa-5.2.orig/tftpd/tftpd.c	2011-12-11 22:13:52.000000000 +0000
+++ tftp-hpa-5.2/tftpd/tftpd.c	2014-11-18 09:32:35.552967597 +0000
@@ -48,6 +48,7 @@
 #include <pwd.h>
 #include <limits.h>
 #include <syslog.h>
+#include <stdlib.h>
 
 #include "common/tftpsubs.h"
 #include "recvfrom.h"
@@ -84,11 +85,14 @@
 static int timeout_quit = 0;
 static sigjmp_buf timeoutbuf;
 static uint16_t rollover_val = 0;
+char *rewrite_helper = NULL;  /* A script to remap file names */
 
 #define	PKTSIZE	MAX_SEGSIZE+4
 static char buf[PKTSIZE];
 static char ackbuf[PKTSIZE];
 static unsigned int max_blksize = MAX_SEGSIZE;
+// Placeholder for a temporary file
+char template[] = "/tmp/TFTP.XXXXXX";
 
 static char tmpbuf[INET6_ADDRSTRLEN], *tmp_p;
 
@@ -102,10 +106,16 @@
 
 static int secure = 0;
 int cancreate = 0;
+int execcgi = 0;
 int unixperms = 0;
 int portrange = 0;
 unsigned int portrange_from, portrange_to;
 int verbosity = 0;
+/* Somewhere to put the remote address */
+char *cgi_remote_addr = NULL;
+/* Directory to prepend (execcgi and chroot can't co-exist) */
+char *basedir = NULL;
+
 
 struct formats;
 #ifdef WITH_REGEX
@@ -347,9 +357,11 @@
     { "port-range",  1, NULL, 'R' },
     { "map-file",    1, NULL, 'm' },
     { "pidfile",     1, NULL, 'P' },
+    { "map-script",  1, NULL, 'M' },
+    { "execute-cgi", 1, NULL, 'x' },
     { NULL, 0, NULL, 0 }
 };
-static const char short_options[] = "46cspvVlLa:B:u:U:r:t:T:R:m:P:";
+static const char short_options[] = "46cspvVlLa:B:u:U:r:t:T:R:m:P:x:M:";
 
 int main(int argc, char **argv)
 {
@@ -494,6 +506,17 @@
             rewrite_file = optarg;
             break;
 #endif
+        case 'M': /* Pass filename through munging script */
+            if ( rewrite_helper ) {
+                syslog(LOG_ERR, "Multiple -M options");
+                exit(EX_USAGE);
+            }
+            rewrite_helper = optarg;
+            break;
+        case 'x': /* Execute files with execute bit set, return output */
+            execcgi = 1;
+            basedir = optarg;
+            break;
         case 'v':
             verbosity++;
             break;
@@ -532,8 +555,13 @@
             syslog(LOG_ERR, "%s: %m", dirs[0]);
             exit(EX_NOINPUT);
         }
+	basedir=dirs[0];
+    }
+    if (execcgi) {
+        chdir(basedir);
     }
 
+
     pw = getpwnam(user);
     if (!pw) {
         syslog(LOG_ERR, "no user %s: %m", user);
@@ -1075,6 +1103,7 @@
     const struct formats *pf = NULL;
     char *origfilename;
     char *filename, *mode = NULL;
+    char newfilename[4096];
     const char *errmsgptr;
     u_short tp_opcode = ntohs(tp->th_opcode);
 
@@ -1112,6 +1141,7 @@
                 nak(EBADOP, "Unknown mode");
                 exit(0);
             }
+	    cgi_remote_addr = inet_ntoa(from.si.sin_addr);
             if (!(filename =
                   (*pf->f_rewrite) (origfilename, tp_opcode,
                                     &errmsgptr))) {
@@ -1137,6 +1167,10 @@
                            tmp_p, origfilename,
                            filename);
             }
+            // rc454 - fixing overflow with long filenames
+            strncpy(newfilename, basedir, 4096-1);
+            filename = strncat(newfilename, filename, (4096-strlen(newfilename)-1));
+            
             ecode =
                 (*pf->f_validate) (filename, tp_opcode, pf, &errmsgptr);
             if (ecode) {
@@ -1407,6 +1441,10 @@
                            rewrite_macros, msg);
         filename = newname;
     }
+    if ( rewrite_helper ) {
+        char *helpedname=rewrite_with_helper(filename);
+        filename = helpedname;
+    }
     return filename;
 }
 
@@ -1415,10 +1453,62 @@
 {
     (void)mode;                 /* Avoid warning */
     (void)msg;
+    if ( rewrite_helper ) {
+        char * newname=rewrite_with_helper(filename);
+        if (newname==NULL) {
+            syslog(LOG_NOTICE,"Rewrite failed: %s",strerror(errno));
+        } else {
+            filename = newname;
+        }
+    }
+
     return filename;
 }
 #endif
 
+/*
+ * Use the rewrite_helper script to 
+ * adjust the filename
+ */
+char * rewrite_with_helper(char *filename) {
+    char newfile[255];
+    FILE *tfile;
+    //int result;
+    int len=sizeof(newfile);
+    if ( rewrite_helper ) {
+        /* Populate the environment with Peer and OriginalFile */
+        if (setenv("Peer",cgi_remote_addr,1)>0) {
+            syslog(LOG_NOTICE, "Can't write to environment variable 'Peer'!");
+            exit(EX_OSERR);
+        } 
+        if (setenv("OriginalFile",filename,1)>0) {
+            syslog(LOG_NOTICE, 
+                   "Can't write to environment variable 'OriginalFile'!");
+            exit(EX_OSERR);
+        } 
+        syslog(LOG_NOTICE, "Spawning rewrite_helper %s",rewrite_helper);
+        tfile=popen(rewrite_helper,"r");
+        if (tfile==NULL) {
+            syslog(LOG_NOTICE,"Cannot execute rewrite_helper:  %s",
+                   strerror(errno));
+        }
+        fgets(newfile,len,tfile);
+        pclose(tfile);
+        syslog(LOG_INFO,"Rewrite_helper maps %s to %s length %u",
+               filename,newfile,strlen(newfile));
+        if (1>strlen(newfile)) {
+            syslog(LOG_ERR,"Rewrite_helper script returned null output.");
+            exit(EX_OSERR);
+        }
+        // Trim off the trailing newline and NULL character
+        strncpy(filename,newfile,strlen(newfile)-1);
+        filename[strlen(newfile)-1]='\0';
+    }
+    syslog(LOG_INFO,"Rewrite_helper returning new filename '%s'",filename);
+    return filename;
+}
+
+
 static FILE *file;
 /*
  * Validate file access.  Since we
@@ -1436,10 +1526,13 @@
 {
     struct stat stbuf;
     int i, len;
+    char buffer[1024];
     int fd, wmode, rmode;
     char *cp;
     const char **dirp;
     char stdio_mode[3];
+    char *fullfile;
+    struct stat fileStat;
 
     tsize_ok = 0;
     *errmsg = NULL;
@@ -1479,6 +1572,55 @@
     wmode = O_WRONLY | (cancreate ? O_CREAT : 0) | (pf->f_convert ? O_TEXT : O_BINARY);
     rmode = O_RDONLY | (pf->f_convert ? O_TEXT : O_BINARY);
 
+    /* Perhaps we cannot stat the file, so we drop back to previous code */
+    if (access(filename,R_OK)!=0) {
+        syslog(LOG_NOTICE, "Unable to read the file: %s",strerror(errno));
+        return(EACCESS);
+    }
+    if (stat(filename,&fileStat) >0) {
+        syslog(LOG_NOTICE, "Unable to stat the file");
+        *errmsg = "Cannot 'stat' the file";
+        return(EACCESS);
+    }
+    /* If canexec is set and the file is executable, run it */
+    if (execcgi && (fileStat.st_mode & S_IXOTH)) {
+        /* Push from IP address onto environment. */
+        if (setenv("Peer",cgi_remote_addr,1)>0) {
+            syslog(LOG_NOTICE, "Can't write to environment variable 'Peer'!");
+        }
+        /* Run the process, send STDOUT to client */
+        // Remove dirname //
+        fullfile=filename;
+        syslog(LOG_NOTICE, "Begin execution of %s",fullfile);
+   
+        /* execute and save into a temporary file */
+        file=popen(fullfile,"r");
+       if (file==NULL) {
+            syslog(LOG_NOTICE,"Cannot execute fullfile %s: %s",
+                   fullfile,strerror(errno));
+        }
+        if ((fd=mkstemp(template)) == -1) {
+            syslog(LOG_NOTICE,"Unable to create temporary file %s %s",
+                   template,strerror(errno));
+            exit(EX_OSERR);
+        }
+        while((len = fread(buffer,1,sizeof(buffer),file)) > 0) {
+            write(fd,buffer,len);
+        }
+        if (ferror(file)) {
+            syslog(LOG_NOTICE,"Read error on %s",file);
+        }
+  
+        if (close(fd)!=0) {
+            syslog(LOG_NOTICE,"Cannot close temporary file %s: %s",
+                   template,strerror(errno));
+        }
+        syslog(LOG_NOTICE,"Adjusting filename to template");
+        filename=template;
+    } 
+    syslog(LOG_NOTICE,"Opening file %s",filename);
+
+
 #ifndef HAVE_FTRUNCATE
     wmode |= O_TRUNC;		/* This really sucks on a dupe */
 #endif
@@ -1506,7 +1648,8 @@
 	exit(0);
 
     if (mode == RRQ) {
-        if (!unixperms && (stbuf.st_mode & (S_IREAD >> 6)) == 0) {
+        // execcgi -> tempfile -> no global read //
+        if (!execcgi && !unixperms && (stbuf.st_mode & (S_IREAD >> 6)) == 0 ) {
             *errmsg = "File must have global read permissions";
             return (EACCESS);
         }
@@ -1639,6 +1782,9 @@
     } while (size == segsize);
   abort:
     (void)fclose(file);
+    if (strcmp(template,"/tmp/TFTP.XXXXXX")!=0) {
+        remove(template);
+    }
 }
 
 /*
--- tftp-hpa-5.2.orig/tftpd/tftpd.h	2011-12-11 22:13:52.000000000 +0000
+++ tftp-hpa-5.2/tftpd/tftpd.h	2014-11-18 09:32:35.552967597 +0000
@@ -20,6 +20,7 @@
 void set_signal(int, void (*)(int), int);
 void *tfmalloc(size_t);
 char *tfstrdup(const char *);
+char *rewrite_with_helper(char *);
 
 extern int verbosity;
 
