Subject: Fixes for VR1 patches

From: Gregory A Lundberg <[email-masked]>
Date: Wed, 3 Jun 1998 23:55:19 -0400 (EDT)

Found some problems with my earlier patches.  The first is something
that's been niggling at me for a while; just didn't realize what was wrong
until _after_ I'd posted.  The second is just plain dumb, sorry.

For people's convenience, I've put both sets of patches in my ftp site:
  ftp://ftp.vr.net/pub/wu-ftpd-2.4.2-beta-17-vr1.patch
and
  ftp://ftp.vr.net/pub/wu-ftpd-2.4.2-beta-17-vr2.patch

Anyone who's applied either of the CD patches posted to the mailing list
last year will definitely want to take a look at my vr2 set. 

I'm planning on doing a vr3 in a week or so, the items I've identified so
far (from digging backward in the mailing list archives, or my own
testing) include:

  - Limit the number of concurrent logins for a given user.  From a
    posting.  How draconian, but I guess it's a good idea.

  - Limit the range of passive ports so firewalls are easier to manage.
    From a posting.  Actually, I'm gonna think on this one; sounds like
    a good idea, though.

  - Allow _both_ syslog and xferlog, specify the syslog LOG_FACILITY.
    Personal wish .. the more places you log the more work a hacker has
    to go through to wipe his footsteps.

  - Specify the location of xferlog and pid file(s).  From a posted
    request.  The responce, "Hack the source" was lame, and it's a good
    idea.

  - Someone said something about not being able to 'deny all' then
    specifically allow certain users or hosts in /etc/ftphosts, if
    this is true I'll see if I can fix it.  Don't recall seeing a patch
    or even a response to his request/observation.

There are two class of patches I won't make from the mailing list
archives: ratios and quotas.  Quotas are better handled outside the daemon
by the file system.  Upload/download ratios are a BBS thing and this is
the 'net.  I pay for my access; don't tell me I gotta waste bandwidth
uploading to you so I can download one of your files or I'll just send you
a couple gigs from /dev/random and see if you have quotas set too .. maybe
I'll even get lucky and you'll be running your entire box on a single file
system .. muhahaha!!!

People have asked so I'll tell you my policy: right now I'm taking patches
from the mailing list archives, going back though the mailing list
archives for ideas/problems, and trying to roll them all into a single set
of patches.  If you send patches directly to me they may get lost anywhere
between my inbox and my keyboard; if you like your patch, share it, I'll
find it in the archives.  I will, of course, pay attention to problems
with my patches.

What Stan does with these patches is up to him; they may, or may not, make
it into the next BETA.  Don't ask me what his plans are.  If my patches
don't make it into the next BETA, I'll just roll them forward against
whatever Stan releases. 

Anyway, here's the FIXES file for vr2:
----

This is a list of fixes to BETA 17 from [email-masked]
These fixes require VR1 fixes to have been installed.
---------------------------------------------------------------------------
The fix for CD ~ broke the upload and noretrieve access-control statements
and changed what was written to xferlog and the syslog. Well, actually, it
didn't break the noretrieve statement, but the man page says '/' means the
name is an 'absolute path specification' and I take that to mean relative
to the _real_ filesystem, not the chroot'd one.  Discovered when set live
on my main server; I really should'a tested with more than one guestgroup.

Drat.  Left a debugging statement in for syslogmsg in VR1 patches.

----

Gregory A Lundberg		Senior Partner, VRnet Company
1441 Elmdale Drive              [email-masked]
Kettering, OH 45409-1615 USA    1-800-809-2195
*** FIXES-2.4.2-BETA-17-VR2.orig	Wed Jun  3 20:59:19 1998
--- FIXES-2.4.2-BETA-17-VR2	Wed Jun  3 23:00:43 1998
***************
*** 0 ****
--- 1,12 ----
+ This is a list of fixes to BETA 17 from [email-masked]
+ These fixes require VR1 fixes to have been installed.
+ ---------------------------------------------------------------------------
+ The fix for CD ~ broke the upload and noretrieve access-control statements
+ and changed what was written to xferlog and the syslog. Well, actually, it
+ didn't break the noretrieve statement, but the man page says '/' means the
+ name is an 'absolute path specification' and I take that to mean relative
+ to the _real_ filesystem, not the chroot'd one.  Discovered when set live
+ on my main server; I really should'a tested with more than one guestgroup.
+ 
+ Drat.  Left a debugging statement in for syslogmsg in VR1 patches.
+ 
*** vr2.sh.orig	Wed Jun  3 20:59:19 1998
--- vr2.sh	Wed Jun  3 21:06:31 1998
***************
*** 0 ****
--- 1,5 ----
+ diff -c FIXES-2.4.2-BETA-17-VR2.orig	FIXES-2.4.2-BETA-17-VR2		>  ../wu-ftpd-2.4.2-beta-17-vr2.patch
+ diff -c vr2.sh.orig			vr2.sh				>> ../wu-ftpd-2.4.2-beta-17-vr2.patch
+ diff -c src/ftpd.c.vr1			src/ftpd.c			>> ../wu-ftpd-2.4.2-beta-17-vr2.patch
+ diff -c src/extensions.c.vr1		src/extensions.c		>> ../wu-ftpd-2.4.2-beta-17-vr2.patch
+ diff -c src/realpath.c.orig		src/realpath.c			>> ../wu-ftpd-2.4.2-beta-17-vr2.patch
*** src/ftpd.c.vr1	Wed Jun  3 20:55:31 1998
--- src/ftpd.c	Wed Jun  3 21:39:05 1998
***************
*** 192,198 ****
  extern int ftpd_pclose(FILE *iop),
    fclose(FILE *);
  extern char *wu_getline(),
!  *realpath(const char *pathname, char *result);
  #else
  extern char *ctime();
  #ifndef NO_CRYPT_PROTO
--- 192,198 ----
  extern int ftpd_pclose(FILE *iop),
    fclose(FILE *);
  extern char *wu_getline(),
!  *realpath(const char *pathname, char *result, char* chroot_path);
  #else
  extern char *ctime();
  #ifndef NO_CRYPT_PROTO
***************
*** 229,234 ****
--- 229,235 ----
    urgcatch;
  int logged_in = 0;
  struct passwd *pw;
+ char chroot_path[MAXPATHLEN];
  int debug;
  int timeout = 900;              /* timeout after 15 minutes of inactivity */
  int maxtimeout = 7200;          /* don't allow idle time to be set beyond 2
***************
*** 1604,1609 ****
--- 1605,1612 ----
          /* determine root and home directory */
  
          if ((sp = strstr(pw->pw_dir, "/./")) == NULL) {
+             (void) strncpy (chroot_path, pw->pw_dir, sizeof (chroot_path));
+             chroot_path[sizeof(chroot_path)-1]='\0';
              if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
                  reply(530, "Can't set guest privileges.");
                  goto bad;
***************
*** 1619,1624 ****
--- 1622,1629 ----
              }
          } else{
  	  *sp++ = '\0';
+           (void) strncpy (chroot_path, pw->pw_dir, sizeof (chroot_path));
+           chroot_path[sizeof(chroot_path)-1]='\0';
  	  if (chroot(pw->pw_dir) < 0 || chdir(++sp) < 0) {
  	    reply(550, "Can't set guest privileges.");
  	    goto bad;
***************
*** 1946,1952 ****
  
          if (!xfertime)
              xfertime++;
!         realpath((logname != NULL) ? logname : name, &namebuf[0]); 
          for (loop = 0; namebuf[loop]; loop++)
              if (isspace(namebuf[loop]) || iscntrl(namebuf[loop]))
                  namebuf[loop] = '_';
--- 1951,1957 ----
  
          if (!xfertime)
              xfertime++;
!         realpath((logname != NULL) ? logname : name, &namebuf[0], chroot_path); 
          for (loop = 0; namebuf[loop]; loop++)
              if (isspace(namebuf[loop]) || iscntrl(namebuf[loop]))
                  namebuf[loop] = '_';
***************
*** 2193,2199 ****
  
          if (!xfertime)
              xfertime++;
!         realpath(name, namebuf);
          for (loop = 0; namebuf[loop]; loop++)
              if (isspace(namebuf[loop]) || iscntrl(namebuf[loop]))
                  namebuf[loop] = '_';
--- 2198,2204 ----
  
          if (!xfertime)
              xfertime++;
!         realpath(name, namebuf, chroot_path);
          for (loop = 0; namebuf[loop]; loop++)
              if (isspace(namebuf[loop]) || iscntrl(namebuf[loop]))
                  namebuf[loop] = '_';
***************
*** 2249,2255 ****
                  authenticated ? authuser : "*"
              );
  #endif /* STUPID_SPRINTF */
- syslog (LOG_ERR, "syslogmsg: %i", syslogmsg);
        if (!syslogmsg)
          write(xferlog, msg, strlen(msg));
        else
--- 2254,2259 ----
***************
*** 3003,3009 ****
      {
          char path[MAXPATHLEN];
  
!         realpath(name, path);
  
          if (anonymous) {
              syslog(LOG_NOTICE, "%s of %s [%s] deleted %s", guestpw, remotehost,
--- 3007,3013 ----
      {
          char path[MAXPATHLEN];
  
!         realpath(name, path, chroot_path);
  
          if (anonymous) {
              syslog(LOG_NOTICE, "%s of %s [%s] deleted %s", guestpw, remotehost,
***************
*** 3174,3180 ****
     be undefined, why are you trying to PRINT it?!  _H*
          reply(550, "%s.", path); */
      {
! 	realpath (".", path);	/* realpath_on_steroids can deal */
      }
      reply(257, "\"%s\" is current directory.", path);
  }
--- 3178,3184 ----
     be undefined, why are you trying to PRINT it?!  _H*
          reply(550, "%s.", path); */
      {
! 	realpath (".", path, NULL);	/* realpath_on_steroids can deal */
      }
      reply(257, "\"%s\" is current directory.", path);
  }
*** src/extensions.c.vr1	Wed Jun  3 20:55:31 1998
--- src/extensions.c	Wed Jun  3 22:09:44 1998
***************
*** 98,103 ****
--- 98,104 ----
    remotehost[],
    hostname[],
    authuser[],
+   chroot_path[],
   *autospout,
    Shutdown[];
  
***************
*** 106,112 ****
    disctime[30];
  
  #ifdef __STDC__
! extern char *realpath(const char *pathname, char *result);
  #else
  extern char *realpath();
  #endif
--- 107,113 ----
    disctime[30];
  
  #ifdef __STDC__
! extern char *realpath(const char *pathname, char *result, char* chroot_path);
  #else
  extern char *realpath();
  #endif
***************
*** 329,335 ****
      struct dirlist *curptr;
      char cwd[MAXPATHLEN];
  
!     (void) realpath(".", cwd);
  
      for (curptr = head; curptr != NULL; curptr = curptr->next)
          if (strcmp(curptr->dirname, cwd) == 0) {
--- 330,336 ----
      struct dirlist *curptr;
      char cwd[MAXPATHLEN];
  
!     (void) realpath(".", cwd, NULL);
  
      for (curptr = head; curptr != NULL; curptr = curptr->next)
          if (strcmp(curptr->dirname, cwd) == 0) {
***************
*** 991,996 ****
--- 992,998 ----
         *ap6 = NULL,
         *ap7 = NULL;
    char cwdir[BUFSIZ];
+   char pwdir[BUFSIZ];
    char path[BUFSIZ];
    char *sp;
    extern struct passwd *pw;
***************
*** 1002,1013 ****
    if (sp = strrchr(path, '/'))  *sp = '\0';
    else strcpy(path, ".");
  
!   if ((realpath(path, cwdir)) == NULL) {
      perror_reply(553, "Could not determine cwdir");
      return(-1);
    }
    while (getaclentry("upload", &entry) && ARG0 && ARG1 && ARG2 != NULL) {
!       if ( (!strcmp(ARG0, "*") || !strcmp(ARG0, pw->pw_dir)) &&
             ((i = path_compare(ARG1, cwdir)) >= match_value) ) {
            match_value = i;
            ap2 = ARG2;
--- 1004,1018 ----
    if (sp = strrchr(path, '/'))  *sp = '\0';
    else strcpy(path, ".");
  
!   if ((realpath(path, cwdir, NULL)) == NULL) {
      perror_reply(553, "Could not determine cwdir");
      return(-1);
    }
+ 
+   realpath(pw->pw_dir, pwdir, chroot_path);
+ 
    while (getaclentry("upload", &entry) && ARG0 && ARG1 && ARG2 != NULL) {
!       if ( (!strcmp(ARG0, "*") || !strcmp(ARG0, pwdir)) &&
             ((i = path_compare(ARG1, cwdir)) >= match_value) ) {
            match_value = i;
            ap2 = ARG2;
***************
*** 1062,1067 ****
--- 1067,1073 ----
  {
    int  match_value = -1;
    char cwdir[BUFSIZ];
+   char pwdir[BUFSIZ];
    char path[BUFSIZ];
    char *sp;
    int  i;
***************
*** 1083,1098 ****
        if (sp = strrchr(path, '/'))  *sp = '\0';
        else strcpy(path, ".");
  
!       if ((realpath(path, cwdir)) == NULL) {
            perror_reply(553, "Could not determine cwdir");
            return(-1);
        }
  
        /* we are doing a "best match"... ..so we keep track of what "match
         * value" we have received so far... */
  
        while (getaclentry("upload", &entry) && ARG0 && ARG1 && ARG2 != NULL) {
!           if ( (!strcmp(ARG0, pw->pw_dir)) &&
  		       ((i = path_compare(ARG1, cwdir)) >= match_value) ) {
                match_value = i;
                ap1 = ARG1;
--- 1089,1106 ----
        if (sp = strrchr(path, '/'))  *sp = '\0';
        else strcpy(path, ".");
  
!       if ((realpath(path, cwdir, NULL)) == NULL) {
            perror_reply(553, "Could not determine cwdir");
            return(-1);
        }
  
+       realpath (pw->pw_dir, pwdir, chroot_path);
+ 
        /* we are doing a "best match"... ..so we keep track of what "match
         * value" we have received so far... */
  
        while (getaclentry("upload", &entry) && ARG0 && ARG1 && ARG2 != NULL) {
!           if ( (!strcmp(ARG0, pwdir)) &&
  		       ((i = path_compare(ARG1, cwdir)) >= match_value) ) {
                match_value = i;
                ap1 = ARG1;
***************
*** 1176,1196 ****
  char *name;
  #endif
  {
!   char cwd[MAXPATHLEN+1], realwd[MAXPATHLEN+1], realname[MAXPATHLEN+1];
    int i;
    struct aclmember *entry = NULL;
  
    if (name == (char *)NULL || *name == '\0')
      return 0; 
  
! #ifdef HAVE_GETCWD
!   (void)getcwd (cwd, MAXPATHLEN);
! #else
!   (void)getwd (cwd);
! #endif
! 
!   realpath (cwd, realwd);
!   realpath (name, realname);
  
     while (getaclentry("noretrieve", &entry)) {
          if (ARG0 == (char *)NULL)
--- 1184,1197 ----
  char *name;
  #endif
  {
!   char realname[MAXPATHLEN+1];
    int i;
    struct aclmember *entry = NULL;
  
    if (name == (char *)NULL || *name == '\0')
      return 0; 
  
!   realpath (name, realname, chroot_path);
  
     while (getaclentry("noretrieve", &entry)) {
          if (ARG0 == (char *)NULL)
*** src/realpath.c.orig	Sun Dec 21 18:25:09 1997
--- src/realpath.c	Wed Jun  3 22:15:24 1998
***************
*** 47,57 ****
  
  char *
  #ifdef __STDC__
! realpath(const char *pathname, char *result)
  #else
! realpath(pathname,result)
  char *pathname;
  char *result;
  #endif
  {
      struct stat sbuf;
--- 47,58 ----
  
  char *
  #ifdef __STDC__
! realpath(const char *pathname, char *result, char* chroot_path)
  #else
! realpath(pathname,result,chroot_path)
  char *pathname;
  char *result;
+ char *chroot_path;
  #endif
  {
      struct stat sbuf;
***************
*** 175,186 ****
--- 176,199 ----
              continue;
          }
          if (*where) {
+ if (chroot_path == NULL)
              strcpy(result, namebuf);
+ else {
+     strcpy(result, chroot_path);
+     if (!((namebuf[0]=='/') && (namebuf[1]=='\0')))
+         strcat(result, namebuf);
+ }
              return (NULL);      /* path/notadir/morepath */
          } else
              strcpy(workpath, namebuf);
      }
+ if (chroot_path == NULL)
      strcpy(result, workpath);
+ else {
+     strcpy(result, chroot_path);
+     if (!((workpath[0]=='/') && (workpath[1]=='\0')))
+         strcat(result, workpath);
+ }
      return (result);
  }

---

From: [email-masked] (Stan Barber)
Date: Wed, 3 Jun 1998 23:01:06 CDT

> don't make it into the next BETA, I'll just roll them forward against

My policy has been basically the same for the last several releases:
Anything that fixes bugs in the software will be evaluated in the context 
of any other bug fixes and if the bug fixes play well together, the result
will be integrated into future releases. Anything that adds new features or 
deletes old features will be tabled for future consideration.

-- 
Stan   | Director of Technology, VERIO     | Work Email: [email-masked]
Olan   | Phone: +1-713-793-0222            | Personal Email: [email-masked]
Barber | Fax:   +1-713-793-0221            | Opinions expressed are only mine.