Subject: Re: PATCH: restricted throughput, traffic count, etc.

From: [email-masked] (Ralf S. Engelschall)
Date: Sun, 1 Feb 1998 13:50:45 +0100 (MET)

Ops, sorry, due to automatic mail reader tab->space conversion, the
posted patch didn't applied cleanly against B16. And additionally
an #ifdef was wrong. Here is the revised patch.

                                       Ralf S. Engelschall
                                       [email-masked]
                                       www.engelschall.com

=============

This is a patch against the sources of WU-FTPd 2.4.2-B16.
It adds three features which have to be enabled explicitly
by setting one or more of the following preprocessor defines:

   THROUGHPUT .............. Restrict network transfer throughput
                             (This is needed for an archive I set up)
   TRAFFIC_COUNT ........... Count total amount of transfer bytes
                             (I just found it interesting)
   NO_SUCKING_NEWLINES ..... Disable some sucking newlines in outputs
                             (The extra blank lines for message and
                              banner annoyed me a bit...)

Feel free to do with it what you want...

Greetings,
                                       Ralf S. Engelschall
                                       [email-masked]
                                       www.engelschall.com

diff -r -C3 wu-ftpd-2.4.2-beta-16.orig/doc/ftpaccess.5 wu-ftpd-2.4.2-beta-16/doc/ftpaccess.5
*** wu-ftpd-2.4.2-beta-16.orig/doc/ftpaccess.5	Mon Dec 22 00:03:32 1997
--- wu-ftpd-2.4.2-beta-16/doc/ftpaccess.5	Sat Jan 31 22:10:44 1998
***************
*** 389,394 ****
--- 389,427 ----
  The upload keyword only applies to users who
  have a home directory (the argument to the chroot() )
  of <root-dir>.
+ 
+ .TP 0.5i
+ .B throughput <root-dir> <subdir-glob> <file-glob-list> <bytes-per-second> <bytes-per-second-multiply> <remote-glob-list>
+ 
+ Define files via comma-seperated <file-glob-list> in subdir matched by
+ <subdir-glob> under <root-dir> that have restricted transfer throughput of
+ <bytes-per-second> on download when the remote hostname or remote IP address
+ matches the comma-seperated <remote-glob-list>.
+ 
+ Entries are matched on a best-match basis.
+ 
+ For example:
+  throughput /e/ftp *    *      oo   -   *
+  throughput /e/ftp /sw* *      1024 0.5 *
+  throughput /e/ftp /sw* README oo   -   *
+  throughput /e/ftp /sw* *      oo   -   *.foo.com
+ 
+ This would set maximum throughput per default, but restrict download to 1024
+ bytes/s for any files under /e/ftp/sw/ which are not named README. The only
+ exceptions are remote hosts from within the domain foo.com which always get
+ maximum throughput. Every time a remote client has retrieved a file under
+ /e/ftp/sw/ the bytes per seconds of the matched entry line are internally
+ multiplied by a factor, here 0.5. So when the remote client retrieves its
+ second file it is served with 512 bytes/s, the third time with only 254
+ bytes/s, the fourth time with only 128 bytes/s and so on.
+ 
+ The string "oo" for the bytes per second field means no throughput
+ restriction. A multiply factor of 1.0 or "-" means no change of the throughput
+ after every successful transfer.
+ 
+ Note that the <root-dir> here must match the home directory specified in the
+ password database for the "ftp" user.  The throughput keyword only applies to
+ users who have a home directory (the argument to the chroot() ) of <root-dir>.
  .SH Files
  FTPLIB/ftpaccess
  .SH See Also
diff -r -C3 wu-ftpd-2.4.2-beta-16.orig/src/extensions.c wu-ftpd-2.4.2-beta-16/src/extensions.c
*** wu-ftpd-2.4.2-beta-16.orig/src/extensions.c	Mon Dec 22 00:24:56 1997
--- wu-ftpd-2.4.2-beta-16/src/extensions.c	Sat Jan 31 22:11:19 1998
***************
*** 96,101 ****
--- 96,104 ----
  #endif
   *globerr,
    remotehost[],
+ #ifdef THROUGHPUT
+   remoteaddr[],
+ #endif
    hostname[],
    authuser[],
   *autospout,
***************
*** 384,390 ****
--- 387,395 ----
  		   lreply(msgcode, "%s", outbuf);
  		 }
  	    fclose(infile);
+ #ifndef NO_SUCKING_NEWLINES
  	    lreply(msgcode, "");
+ #endif
  	  }
        }
      else {
***************
*** 400,406 ****
--- 405,413 ----
  		  lreply(msgcode, "%s", outbuf);
  		}
  		fclose(infile);
+ #ifndef NO_SUCKING_NEWLINES
  		lreply(msgcode, "");
+ #endif
  	    }
  	 }
  #ifdef VIRTUAL
***************
*** 477,483 ****
--- 484,492 ----
                      lreply(msgcode, "%s", outbuf);
                  }
                  fclose(infile);
+ #ifndef NO_SUCKING_NEWLINES
                  lreply(msgcode, "");
+ #endif
              }
          }
      }
***************
*** 1185,1191 ****
--- 1194,1398 ----
     return 0;
  }
  
+ #ifdef THROUGHPUT
  
+ int
+ #ifdef __STDC__
+ file_compare(char *patterns, char *file)
+ #else
+ file_compare(patterns, file)
+     char *patterns;
+     char *file;
+ #endif
+ {
+     char buf[BUFSIZ];
+     char *cp;
+     char *cp2;
+     int i;
+     int matches = 0;
+ 
+     strcpy(buf, patterns);
+     i = strlen(buf);
+     buf[i++] = ',';
+     buf[i++] = '\0';
+ 
+     cp = buf;
+     while ((cp2 = strchr(cp, ',')) != NULL) {
+         *cp2++ = '\0';
+         if (fnmatch(cp, file, 0) == 0) {
+             matches = 1;
+             break;
+         }
+     }
+     return matches;
+ }
  
+ int
+ #ifdef __STDC__
+ remote_compare(char *patterns)
+ #else
+ remote_compare(patterns)
+     char *patterns;
+ #endif
+ {
+     char buf[BUFSIZ];
+     char *cp;
+     char *cp2;
+     int i;
+     int matches = 0;
+ 
+     strcpy(buf, patterns);
+     i = strlen(buf);
+     buf[i++] = ',';
+     buf[i++] = '\0';
+ 
+     cp = buf;
+     while ((cp2 = strchr(cp, ',')) != NULL) {
+         *cp2++ = '\0';
+         if (fnmatch(cp, remotehost, 0) == 0) {
+             matches = 1;
+             break;
+         }
+         else if (fnmatch(cp, remoteaddr, NULL) == 0) {
+             matches = 1;
+             break;
+         }
+     }
+     return matches;
+ }
  
+ void
+ #ifdef __STDC__
+ throughput_calc(char *name, int *bps, double *bpsmult)
+ #else
+ throughput_calc(name)
+     char *name;
+     int *bps;
+     double *bpsmult;
+ #endif
+ {
+     int match_value = -1;
+     char cwdir[BUFSIZ];
+     char path[BUFSIZ];
+     char file[BUFSIZ];
+     char x[BUFSIZ];
+     char *ap1 = NULL, *ap2 = NULL, *ap3 = NULL, *ap4 = NULL;
+     struct aclmember *entry = NULL;
+     extern struct passwd *pw;
+     char *sp;
+     int i;
+ 
+     /* default is maximum throughput */
+     *bps = -1;
+     *bpsmult = 1.0;
+ 
+     /* what's our current directory? */
+     strcpy(path, name);
+     if (sp = strrchr(path, '/'))
+         *sp = '\0';
+     else
+         strcpy(path, ".");
+     if (sp = strrchr(name, '/'))
+         strcpy(file, sp + 1);
+     else
+         strcpy(file, name);
+     if ((realpath(path, cwdir)) == NULL) {
+         perror_reply(553, "Could not determine cwdir");
+         return;
+     }
+ 
+     /* find best matching entry */
+     while (getaclentry("throughput", &entry) && ARG0 && ARG1 && ARG2 && ARG3 && ARG4 && ARG5 != NULL) {
+         if ((!strcmp(ARG0, pw->pw_dir)) &&
+             ((i = path_compare(ARG1, cwdir)) >= match_value)) {
+             if (file_compare(ARG2, file)) {
+                 if (remote_compare(ARG5)) {
+                     match_value = i;
+                     ap3 = ARG3;
+                     ap4 = ARG4;
+                 }
+             }
+         }
+     }
+ 
+     /* if we did get matches */
+     if (match_value >= 0) {
+         if (strcmp(ap3, "oo") == 0)
+             *bps = -1;
+         else
+             *bps = atoi(ap3);
+         if (strcmp(ap4, "-") == 0)
+             *bpsmult = 1.0;
+         else
+             *bpsmult = atof(ap4);
+     }
+     return;
+ }
+ 
+ void
+ #ifdef __STDC__
+ throughput_adjust(char *name)
+ #else
+ throughput_adjust(name)
+     char *name;
+     int *bps;
+     double *bpsmult;
+ #endif
+ {
+     int match_value = -1;
+     char cwdir[BUFSIZ];
+     char path[BUFSIZ];
+     char file[BUFSIZ];
+     char buf[BUFSIZ];
+     char *ap1 = NULL, *ap2 = NULL, *ap3 = NULL, *ap4 = NULL, *ap5 = NULL;
+     char **pap3;
+     struct aclmember *entry = NULL;
+     extern struct passwd *pw;
+     char *sp;
+     int i;
+ 
+     /* what's our current directory? */
+     strcpy(path, name);
+     if (sp = strrchr(path, '/'))
+         *sp = '\0';
+     else
+         strcpy(path, ".");
+     if (sp = strrchr(name, '/'))
+         strcpy(file, sp + 1);
+     else
+         strcpy(file, name);
+     if ((realpath(path, cwdir)) == NULL) {
+         perror_reply(553, "Could not determine cwdir");
+         return;
+     }
+ 
+     /* find best matching entry */
+     while (getaclentry("throughput", &entry) && ARG0 && ARG1 && ARG2 && ARG3 && ARG4 && ARG5 != NULL) {
+         if ((!strcmp(ARG0, pw->pw_dir)) &&
+             ((i = path_compare(ARG1, cwdir)) >= match_value)) {
+             if (file_compare(ARG2, file)) {
+                 if (remote_compare(ARG5)) {
+                     match_value = i;
+                     ap3 = ARG3;
+                     pap3 = &ARG3;
+                     ap4 = ARG4;
+                 }
+             }
+         }
+     }
+ 
+     /* if we did get matches */
+     if (match_value >= 0) {
+         if (strcmp(ap3, "oo") != 0) {
+             if (strcmp(ap4, "-") != 0) {
+                 sprintf(buf, "%.0f", atoi(ap3) * atof(ap4));
+                 *pap3 = (char *) malloc(strlen(buf) + 1);
+                 strcpy(*pap3, buf);
+             }
+         }
+     }
+     return;
+ }
+ 
+ #endif
  
diff -r -C3 wu-ftpd-2.4.2-beta-16.orig/src/ftpcmd.y wu-ftpd-2.4.2-beta-16/src/ftpcmd.y
*** wu-ftpd-2.4.2-beta-16.orig/src/ftpcmd.y	Mon Dec 22 00:25:05 1997
--- wu-ftpd-2.4.2-beta-16/src/ftpcmd.y	Sat Jan 31 22:11:44 1998
***************
*** 102,107 ****
--- 102,112 ----
  extern  char    *modenames[];
  extern  char    *formnames[];
  
+ #ifdef TRAFFIC_COUNT
+ extern  int data_count_total;
+ extern  int byte_count_total;
+ #endif
+ 
  static	unsigned short cliport = 0;
  static	struct in_addr cliaddr;
  static  int cmd_type;
***************
*** 699,704 ****
--- 704,713 ----
      |   QUIT CRLF
          = {
              if (log_commands) syslog(LOG_INFO, "QUIT");
+ #ifdef TRAFFIC_COUNT
+             lreply(221, "You have transferred %d bytes (total traffic: %d bytes).", data_count_total, byte_count_total);
+             lreply(221, "Thank you for using the FTP service on %s.", hostname);
+ #endif
              reply(221, "Goodbye.");
              dologout(0);
          }
diff -r -C3 wu-ftpd-2.4.2-beta-16.orig/src/ftpd.c wu-ftpd-2.4.2-beta-16/src/ftpd.c
*** wu-ftpd-2.4.2-beta-16.orig/src/ftpd.c	Mon Dec 22 00:25:06 1997
--- wu-ftpd-2.4.2-beta-16/src/ftpd.c	Sat Jan 31 22:13:14 1998
***************
*** 258,263 ****
--- 258,268 ----
  off_t file_size;
  off_t byte_count;
  
+ #ifdef TRAFFIC_COUNT
+ int data_count_total = 0; /* total number of data bytes */
+ int byte_count_total = 0; /* total number of general traffic */
+ #endif
+ 
  #if !defined(CMASK) || CMASK == 0
  #undef CMASK
  #define CMASK 022
***************
*** 362,368 ****
--- 367,377 ----
  
  #ifdef __STDC__
  void end_login(void);
+ #ifdef THROUGHPUT
+ void send_data(char *name, FILE *, FILE *, off_t);
+ #else
  void send_data(FILE *, FILE *, off_t);
+ #endif
  void dolog(struct sockaddr_in *);
  void dologout(int);
  void perror_reply(int, char *);
***************
*** 374,379 ****
--- 383,397 ----
  void perror_reply();
  #endif
  
+ #ifdef THROUGHPUT
+ #ifdef __STDC__
+ extern void throughput_calc(char *, int *, double *);
+ extern void throughput_adjust(char *);
+ #else
+ extern void throughput_calc();
+ extern void throughput_adjust();
+ #endif
+ #endif
  
  void
  #ifdef __STDC__
***************
*** 1911,1920 ****
--- 1929,1946 ----
      if (dout == NULL)
          goto done;
  #ifdef HAVE_ST_BLKSIZE
+ #ifdef THROUGHPUT
+     send_data(name, fin, dout, st.st_blksize*2);
+ #else
      send_data(fin, dout, st.st_blksize*2);
+ #endif
+ #else
+ #ifdef THROUGHPUT
+     send_data(name, fin, dout, BUFSIZ);
  #else
      send_data(fin, dout, BUFSIZ);
  #endif
+ #endif
      (void) fclose(dout);
  
    dolog:
***************
*** 2331,2336 ****
--- 2357,2366 ----
  #ifdef IPTOS_LOWDELAY
      int tos;
  #endif
+ #ifdef THROUGHPUT
+     int bps;
+     double bpsmult;
+ #endif
  
      file_size = size;
      byte_count = 0;
***************
*** 2391,2396 ****
--- 2421,2436 ----
                            sizeof(int));
  
  #endif
+ 
+ #ifdef THROUGHPUT
+         throughput_calc(name, &bps, &bpsmult);
+         if (bps != -1) {
+             lreply(150, "Opening %s mode data connection for %s%s.",
+                    type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
+             reply(150, "Restricting network throughput to %d bytes/s.", bps);
+         }
+         else
+ #endif
          reply(150, "Opening %s mode data connection for %s%s.",
                type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
          return (fdopen(pdata, mode));
***************
*** 2428,2433 ****
--- 2468,2482 ----
          data = -1;
          return (NULL);
      }
+ #ifdef THROUGHPUT
+     throughput_calc(name, &bps, &bpsmult);
+     if (bps != -1) {
+         lreply(150, "Opening %s mode data connection for %s%s.",
+                type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
+         reply(150, "Restricting network throughput to %d bytes/s.", bps);
+     }
+     else 
+ #endif
      reply(150, "Opening %s mode data connection for %s%s.",
            type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
      return (file);
***************
*** 2440,2448 ****
--- 2489,2506 ----
  
  void
  #ifdef __STDC__
+ #ifdef THROUGHPUT
+ send_data(char *name, FILE *instr, FILE *outstr, off_t blksize)
+ #else
  send_data(FILE *instr, FILE *outstr, off_t blksize)
+ #endif
+ #else
+ #ifdef THROUGHPUT
+ send_data(name,instr,outstr,blksize)
+ char *name;
  #else
  send_data(instr,outstr,blksize)
+ #endif
  FILE *instr;
  FILE *outstr;
  off_t blksize;
***************
*** 2453,2458 ****
--- 2511,2525 ----
      static char *buf;
      int netfd,
        filefd;
+ #ifdef THROUGHPUT
+     int bps;
+     double bpsmult;
+     time_t t1, t2;
+ #endif
+ 
+ #ifdef THROUGHPUT
+     throughput_calc(name, &bps, &bpsmult);
+ #endif
  
      buf = NULL;
      if (setjmp(urgcatch)) {
***************
*** 2475,2480 ****
--- 2542,2550 ----
                  (void) putc('\r', outstr);
              }
              (void) putc(c, outstr);
+ #ifdef TRAFFIC_COUNT
+             byte_count_total++;
+ #endif
          }
  	alarm(0);
          fflush(outstr);
***************
*** 2488,2493 ****
--- 2558,2567 ----
  
      case TYPE_I:
      case TYPE_L:
+ #ifdef THROUGHPUT
+         if (bps != -1)
+             blksize = bps;
+ #endif
          if ((buf = (char *) malloc((u_int) blksize)) == NULL) {
              transflag = 0;
              perror_reply(451, "Local resource failure: malloc");
***************
*** 2496,2507 ****
--- 2570,2601 ----
          netfd = fileno(outstr);
          filefd = fileno(instr);
  	alarm((unsigned)timeout);
+ #ifdef THROUGHPUT
+         if (bps != -1)
+             t1 = time(NULL);
+ #endif
          while ((cnt = read(filefd, buf, (u_int) blksize)) > 0 &&
                 write(netfd, buf, cnt) == cnt){
  	  alarm((unsigned)timeout);
  	  byte_count += cnt;
+ #ifdef TRAFFIC_COUNT
+           data_count_total += cnt;
+           byte_count_total += cnt;
+ #endif
+ #ifdef THROUGHPUT
+           if (bps != -1) {
+               t2 = time(NULL);
+               if (t2 == t1)
+                   sleep(1);
+               t1 = time(NULL);
+           }
+ #endif
  	}
  	alarm(0);
+ #ifdef THROUGHPUT
+         if (bps != -1)
+             throughput_adjust(name);
+ #endif
          transflag = 0;
          (void) free(buf);
          if (cnt != 0) {
***************
*** 2572,2577 ****
--- 2666,2675 ----
          while ((cnt = read(netfd, buf, BUFSIZ)) > 0 &&
                 write(filefd, buf, cnt) == cnt){
              byte_count += cnt;
+ #ifdef TRAFFIC_COUNT
+             data_count_total += cnt;
+             byte_count_total += cnt;
+ #endif
  	    alarm((unsigned)timeout);
  	}
  	alarm(0);
***************
*** 2607,2612 ****
--- 2705,2713 ----
                  }
              }
              (void) putc(c, outstr);
+ #ifdef TRAFFIC_COUNT
+             data_count_total++;
+ #endif
            contin2:;
          }
  	alarm(0);
***************
*** 2822,2827 ****
--- 2923,2931 ----
     */
  
    printf("%s\r\n", buf); /* and send it to the client */
+ #ifdef TRAFFIC_COUNT
+   byte_count_total += strlen(buf);
+ #endif
    fflush(stdout);
  }
  
***************
*** 3559,3564 ****
--- 3663,3671 ----
              fprintf(dout, "%s%s\n", dirname,
                      type == TYPE_A ? "\r" : "");
              byte_count += strlen(dirname) + 1;
+ #ifdef TRAFFIC_COUNT
+             data_count_total += strlen(dirname) + 1;
+ #endif
              continue;
          } else if ((st.st_mode & S_IFMT) != S_IFDIR)
              continue;
***************
*** 3606,3611 ****
--- 3713,3721 ----
                      fprintf(dout, "%s%s\n", nbuf,
                              type == TYPE_A ? "\r" : "");
                  byte_count += strlen(nbuf) + 1;
+ #ifdef TRAFFIC_COUNT
+                 data_count_total += strlen(nbuf) + 1;
+ #endif
              }
          }
          (void) closedir(dirp);