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.

diff -Naurp tftp-hpa.orig/tftpd/tftpd.c tftp-hpa/tftpd/tftpd.c
--- tftp-hpa.orig/tftpd/tftpd.c 2009-02-16 22:51:22.000000000 +0000
+++ tftp-hpa/tftpd/tftpd.c      2011-05-12 12:24:15.000000000 +0100
@@ -46,6 +46,7 @@
 #include <pwd.h>
 #include <limits.h>
 #include <syslog.h>
+#include <stdlib.h>
 
 #include "common/tftpsubs.h"
 #include "recvfrom.h"
@@ -82,11 +83,14 @@ static unsigned long maxtimeout = TIMEOU
 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;
 
@@ -100,10 +104,16 @@ static const char **dirs;
 
 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
@@ -317,9 +327,11 @@ static struct option long_options[] = {
     { "retransmit",  1, NULL, 'T' },
     { "port-range",  1, NULL, 'R' },
     { "map-file",    1, NULL, 'm' },
+    { "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:";
+static const char short_options[] = "46cspvVlLa:B:u:U:r:t:T:R:m:x:M:";
 
 int main(int argc, char **argv)
 {
@@ -463,6 +475,17 @@ int main(int argc, char **argv)
             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;
@@ -498,8 +521,13 @@ int main(int argc, char **argv)
             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);
@@ -1039,6 +1067,7 @@ int tftp(struct tftphdr *tp, int size)
                 nak(EBADOP, "Unknown mode");
                 exit(0);
             }
+	    cgi_remote_addr = inet_ntoa(from.si.sin_addr);
             if (!(filename =
                   (*pf->f_rewrite) (origfilename, tp_opcode,
                                     &errmsgptr))) {
@@ -1064,6 +1093,7 @@ int tftp(struct tftphdr *tp, int size)
                            tmp_p, origfilename,
                            filename);
             }
+	    filename=strcat(basedir,filename);
             ecode =
                 (*pf->f_validate) (filename, tp_opcode, pf, &errmsgptr);
             if (ecode) {
@@ -1341,6 +1371,10 @@ static char *rewrite_access(char *filena
                            rewrite_macros, msg);
         filename = newname;
     }
+    if ( rewrite_helper ) {
+        char *helpedname=rewrite_with_helper(filename);
+        filename = helpedname;
+    }
     return filename;
 }
 
@@ -1349,10 +1383,62 @@ static char *rewrite_access(char *filena
 {
     (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
@@ -1370,10 +1456,13 @@ static int validate_access(char *filenam
 {
     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;
@@ -1414,7 +1503,54 @@ static int validate_access(char *filenam
         (cancreate ? O_CREAT : 0) |
         (unixperms ? O_TRUNC : 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);
+   
     fd = open(filename, mode == RRQ ? rmode : wmode, 0666);
     if (fd < 0) {
         switch (errno) {
@@ -1434,7 +1570,8 @@ static int validate_access(char *filenam
         exit(EX_OSERR);         /* This shouldn't happen */
 
     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);
         }
@@ -1442,20 +1579,21 @@ static int validate_access(char *filenam
         /* We don't know the tsize if conversion is needed */
         tsize_ok = !pf->f_convert;
     } else {
-        if (!unixperms) {
-            if ((stbuf.st_mode & (S_IWRITE >> 6)) == 0) {
+        if ( !unixperms ) {
+            if ( (stbuf.st_mode & (S_IWRITE >> 6)) == 0 ) {
                 *errmsg = "File must have global write permissions";
                 return (EACCESS);
             }
 
             /* We didn't get to truncate the file at open() time */
 #ifdef HAVE_FTRUNCATE
-            if (ftruncate(fd, (off_t) 0)) {
+            if ( ftruncate(fd, (off_t)0) ) {
                 *errmsg = "Cannot reset file size";
-                return (EACCESS);
+                return(EACCESS);
             }
 #endif
         }
+  
         tsize = 0;
         tsize_ok = 1;
     }
@@ -1567,6 +1705,9 @@ static void tftp_sendfile(struct formats
     } while (size == segsize);
   abort:
     (void)fclose(file);
+    if (strcmp(template,"/tmp/TFTP.XXXXXX")!=0) {
+        remove(template);
+    }
 }
 
 /*
diff -Naurp tftp-hpa.orig/tftpd/tftpd.h tftp-hpa/tftpd/tftpd.h
--- tftp-hpa.orig/tftpd/tftpd.h 2009-02-16 22:51:22.000000000 +0000
+++ tftp-hpa/tftpd/tftpd.h      2011-05-12 12:24:15.000000000 +0100
@@ -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;
 
