Subject: [VR10] More enhancements and bug fixes for beta-18

From: Gregory A Lundbeg <[email-masked]>
Date: Sun, 1 Nov 1998 12:00:00 -0500 (EST)

The VR10 patch set for WU-FTPD 2.4.2 (beta-18) is now available. 

SECURITY-UPDATE: This set includes the correction of a buffer overlow
problem in the realpath() function discussed recently on the BUGTRAQ
mailing list.  The error in realpath() exists in all prior versions of
WU-FTPD including 2.4, all Academ version 2.4.1 and 2.4.2 betas, all
versions of NEWVIRT, BeroFTPD prior to version 1.2.0, and any packages
derived from any of the above.  There are no known exploits for this error. 
Users of all versions of WU-FTPD are strongly advised to upgrade. 

This set also includes additional features requested over the years by the
user community and includes a number of bug fixes for both the base
(beta-18) release and earlier VR patch sets. 

These are available as both patches and pre-patched tarballs at my ftp
site: 

  ftp://ftp.vr.net/pub/wu-ftpd/

If you take just the patch files, please remember: they are cumulative. 
you cannot apply fixes from one set without earlier sets already having
been applied.  The first set for BETA-18 is VR3; VR1 and VR2 were for
BETA-17 only. 

Several pre-compiled binaries for VR9 are also available.  These include: 

  Sun/SunOS
  ---------
  sunos41x-ftpbin.tar.gz  (FTP support executables, ls etc.)
  wu-ftpd-2.4.2-beta-18-vr10-SunOS-4.1.3_U1.tar.gz

  Sun/Solaris
  -----------
  FTP242b18.wu-ftpd.2.4.2-beta18-VR10.SPARC.ULTRASparc.2.5.1.2.5.pkg.tar.Z
  FTP242b18.wu-ftpd.2.4.2-beta18-VR10.SPARC.ULTRASparc.2.5.1.2.5.pkg.tar.gz
  wu-ftpd-2.4.2-beta-18-vr10-Solaris-2.6.tar.gz

  Sun/NetBSD
  ----------
  wu-ftpd-2.4.2-beta-18-vr10-NetBSD-sparc-1.3.2.tar.gz

  Sun/Linux
  ---------
  wu-ftpd-2.4.2-beta-18-vr10-linux-sparc.tar.gz

  SGI/IRIX
  --------
  irix62-ftpbin.tar.gz  (FTP support executables, ls etc.)
  wu-ftpd-2.4.2-beta-18-vr10-IRIX-6.2.tar.gz

  IBM/AIX
  -------
  wu-ftpd-2.4.2-beta-18-vr10-AIX.3.2.5.tar.gz

  DEC/Unix
  --------
  wu-ftpd-2.4.2-beta-18-vr10-OSF1-3.2-C2.tar.gz

  Intel/BSDI
  ----------
  wu-ftpd-2.4.2-beta-18-vr10-BSDI-2.1.tar.gz
  wu-ftpd-2.4.2-beta-18-vr10-BSDI-3.1.tar.gz

  Intel/Linux
  -----------
  wu-ftpd-2.4.2-beta-18-vr10.linux.i386.tar.gz

Thanks to all those who helped with debugging and built the pre-compiled
binaries. 

This is a list of fixes to BETA 18 with VR9 applied from [email-masked]
---------------------------------------------------------------------------
Wolfram Schmidt <[email-masked]> pointed out on July 22, 1994,
the daemon does not use the correct method to choose the port for the data
connection in PORT mode.  More recently, Bernhard Rosenkraenzer
<[email-masked]> added a -p option to BeroFTPD 1.0.12 which
allows the port to be specified for the control connection.  With this
patch the daemon will look up the data port in /etc/services.  Command-line
options are also provided to allow both the data and control port numbers
to be specified. 

Recent discussions have pointed out the need for some high-volume sites to
bypass PID file processing.  Testing the daemon as a normal user also
points out the need for this.  This patch adds the -Q command-line option
to suppress access to the PID files.  NOTE: Without PID files, the limit
ftpaccess clause cannot determine the number of users in the given class. 

AUTH (ident) the remote user during login.  Record the results in the
syslog.  Originally requested, with a suggested patch send to the mailing
list by [email-masked] on Aug 24, 1997.  See next patch. 

Nick Maclaren <[email-masked]> sent a private email to wu-ftpd-bugs,
Bernard and me on Friday, October 16, 1998.  He had made a set of patches
to the base, beta-18, release which include a few bugfixes and some new
features: 

  - RFC-931 (AUTH/IDENT) was finished up.  The log messages now show the
    RFC-931 user if one is known. 

  - Support for some Hitachi flavors of Unix was added. 

  - Major cleanup of build and the makefiles.  This was long overdue.  I
    have received several complaints that changing headers does not cause
    the code to recompile; he fixed that.  He missed checking for changes
    in the support library, so I added that.  Also, his changes presumed
    the user compiling the code was 'root' so I cleaned up so non-root
    can compile the daemon (this was mainly because I've cleaned up the
    file permissions in the release tarballs). 

  - A number of minor fixes, mainly having to do with differences between
    ANSI/ISO and K&R C.  His comments about C9X (the next C standard) are
    bogus but his changes either were good ideas or pointed out where the
    code needed a bit of work. 

I've added comments to his FIXES file where I've departed from his work. 

Stan's TODO item 21 calls for access control by remote username if this was
authenticated using RFC-931.  Results from RFC-931 should not be used for
authentication.  Cancelling this item. 

Stan's TODO item 3 calls for adding additional logging.  This was done in
easlier VR patch sets.  There is no reason to change to another log file. 
Marking this item completed. 

BeroFTPD has 'ls' implemented internally.  Marking Stan's TODO item 25
complete. 

[email-masked] pointed out the Perl xferstats wasn't updated to match the
new xferlog format with the new completion-code field on the end. 

A recent discussion on BUGTRAQ pointed out a buffer-overrun in the realpath
function.  Bernard imported the FreeBSD realpath() function to correct this
error.  This closes Stan's TODO item 1. 

-- 

Gregory A Lundberg		Senior Partner, VRnet Company
1441 Elmdale Drive              [email-masked]
Kettering, OH 45409-1615 USA    1-800-809-2195

---

From: "Denis N. Antonioli" <[email-masked]>
Date: Thu, 26 Nov 1998 17:05:07 +0100 (MET)

-----BEGIN PGP SIGNED MESSAGE-----

Hi,

I've found a bug in the host control: the sscanf has 9 arguments
but the format specification has only 8 fields :-(

Here is a patch:

# wu-ftpd-2.4.2-beta-18-vr10
# src/access.c
231c231
<     else if (sscanf(addr,"%d.%d.%d.%d:%d.%d.%d.%d",a,a+1,a+2,a+3,a+4,m,m+1,m+2,m+3) == 8) {
- ---
>     else if (sscanf(addr,"%d.%d.%d.%d:%d.%d.%d.%d",a,a+1,a+2,a+3,m,m+1,m+2,m+3) == 8) {

Greetings,
	dna

- -- 
  A Perl module would prefer that you stayed out of its living room
  because you weren't invited, not because it has a shotgun.
    -- Larry Wall & Tom Christiansen in the Camel

---

From: Gregory A Lundberg <[email-masked]>
Date: Thu, 26 Nov 1998 14:09:32 -0500 (EST)

> >     else if (sscanf(addr,"%d.%d.%d.%d:%d.%d.%d.%d",a,a+1,a+2,a+3,m,m+1,m+2,m+3) == 8) {

Ah.  OK.  I'll roll this into the next version: VR12, VR11 has been tagged
and you're a day too late to make it into it.

-- 

Gregory A Lundberg		Senior Partner, VRnet Company
1441 Elmdale Drive              [email-masked]
Kettering, OH 45409-1615 USA    1-800-809-2195

---

From: "Denis N. Antonioli" <[email-masked]>
Date: Fri, 27 Nov 1998 11:06:33 +0100 (MET)

-----BEGIN PGP SIGNED MESSAGE-----

Hi,

I think I've found another problem with our beloved ftp server, this time
in realpath.c.

The function fb_realpath changes to euid 0 (root) for every disk access,
in order to be able to walk the directory structure without having to
follow the access rights.

This is ok as long as ftpd reads directories on the local machine, but
fails for mounted volumes, because each server has its own super user,
at least by us. ftpd has to access mounted volumes when real user logs
in, as their homes are distributed over different servers.

Maybe ftpd could seteuid(0) only if a normal access failed?

	A /* the disk access, as plain user */
	if (EACCES == errno) {
		uid_t userid = geteuid();
		delay_signaling(); /* we can't allow any signals while euid==0: kinch */
		seteuid(0);
		A /* the disk access, as super user */
		seteuid(userid);
		enable_signaling(); /* we can allow signals once again: kinch */
	}

I'll be glad to hear what more experimented users will think of this!

Greetings,
	dna

- -- 
  The C Programming Language -- A language which combines the flexibility
  of assembly language with the power of assembly language.

---

From: Gregory A Lundberg <[email-masked]>
Date: Fri, 27 Nov 1998 10:09:03 -0500 (EST)

> Maybe ftpd could seteuid(0) only if a normal access failed?

Sounds fine.  The seteuid(0) stuff is there mainly to support people
who've revoked read access on the common directory containing the users'
home directories as in:

d--x--x--x root root /home
drwx------ greg greg /home/greg

Where 'greg' is a guest user chroot'd to /home/./greg and we want him to
be able to cd, pwd, etc., but not see the other users in /home

--

---

From: "Denis N. Antonioli" <[email-masked]>
Date: Sun, 29 Nov 1998 15:23:52 +0100 (MET)

-----BEGIN PGP SIGNED MESSAGE-----

> be able to cd, pwd, etc., but not see the other users in /home

Ok. Here then is a patch for realpath.c.

Happy programming,
	Denis N. Antonioli

- -- 
  Common sense is the collection of prejudices acquired by age 18.
    -- Einstein

*** realpath.c.orig	Fri Nov 27 09:33:28 1998
- --- realpath.c	Sun Nov 29 15:13:23 1998
***************
*** 108,114 ****
  	int fd, n, rootd, serrno;
  	char *p, *q, wbuf[MAXPATHLEN];
        int symlinks = 0;
- - 	uid_t userid;
  	int resultcode;
  #ifdef HAS_NO_FCHDIR
  /* AIX Has no fchdir() so we hope the getcwd() call doesn't overrun the buffer! */
- --- 108,113 ----
***************
*** 117,123 ****
  #endif
  
  	/* Save the starting point. */
! 	userid = geteuid();
  	delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  	seteuid(0);
  #ifdef HAS_NO_FCHDIR
- --- 116,128 ----
  #endif
  
  	/* Save the starting point. */
! #ifdef HAS_NO_FCHDIR
! 	pcwd = getcwd(cwd, sizeof (cwd));
! #else
! 	fd = open(".", O_RDONLY);
! #endif
! 	if (EACCES == errno) {
! 	  uid_t userid = geteuid();
  	  delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  	  seteuid(0);
  #ifdef HAS_NO_FCHDIR
***************
*** 127,132 ****
- --- 132,139 ----
  #endif
  	  seteuid(userid);
  	  enable_signaling(); /* we can allow signals once again: kinch */
+ 	}
+ 
  #ifdef HAS_NO_FCHDIR
  	if (pcwd == NULL) {
  #else
***************
*** 159,170 ****
  			q[1] = '\0';
  			q = resolved;
  		}
! 		userid = geteuid();
  		delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  		seteuid(0);
  		resultcode = chdir(q);
  		seteuid(userid);
  		enable_signaling(); /* we can allow signals once again: kinch */
  		if (resultcode < 0)
  			goto err1;
  	} else
- --- 166,180 ----
  			q[1] = '\0';
  			q = resolved;
  		}
! 		resultcode = chdir(q);
! 		if (EACCES == errno) {
! 		  uid_t userid = geteuid();
  		  delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  		  seteuid(0);
  		  resultcode = chdir(q);
  		  seteuid(userid);
  		  enable_signaling(); /* we can allow signals once again: kinch */
+ 		}
  		if (resultcode < 0)
  			goto err1;
  	} else
***************
*** 172,207 ****
  
  	/* Deal with the last component. */
  	if (*p != '\0') {
! 	    userid = geteuid();
  	    delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  	    seteuid(0);
  	    resultcode = lstat(p, &sb);
  	    seteuid(userid);
  	    enable_signaling(); /* we can allow signals once again: kinch */
  	    if (resultcode == 0) {
  		if (S_ISLNK(sb.st_mode)) {
                        if (++symlinks > MAXSYMLINKS) {
                                errno = ELOOP;
                                goto err1;
                        }
! 			userid = geteuid();
  			delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  			seteuid(0);
  			n = readlink(p, resolved, MAXPATHLEN);
  			seteuid(userid);
  			enable_signaling(); /* we can allow signals once again: kinch */
  			if (n < 0)
  				goto err1;
  			resolved[n] = '\0';
  			goto loop;
  		}
  		if (S_ISDIR(sb.st_mode)) {
! 			userid = geteuid();
  			delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  			seteuid(0);
  			resultcode = chdir(p);
  			seteuid(userid);
  			enable_signaling(); /* we can allow signals once again: kinch */
  			if (resultcode < 0)
  				goto err1;
  			p = "";
- --- 182,226 ----
  
  	/* Deal with the last component. */
  	if (*p != '\0') {
! 	  resultcode = lstat(p, &sb);
! 	  if (EACCES == errno) {
! 		uid_t userid = geteuid();
  	    delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  	    seteuid(0);
  	    resultcode = lstat(p, &sb);
  	    seteuid(userid);
  	    enable_signaling(); /* we can allow signals once again: kinch */
+ 	  }
  	  if (resultcode == 0) {
  		if (S_ISLNK(sb.st_mode)) {
  		  if (++symlinks > MAXSYMLINKS) {
  			errno = ELOOP;
  			goto err1;
  		  }
! 		  n = readlink(p, resolved, MAXPATHLEN);
! 		  if (EACCES == errno) {
! 			uid_t userid = geteuid();
  			delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  			seteuid(0);
  			n = readlink(p, resolved, MAXPATHLEN);
  			seteuid(userid);
  			enable_signaling(); /* we can allow signals once again: kinch */
+ 		  }
  		  if (n < 0)
  			goto err1;
  		  resolved[n] = '\0';
  		  goto loop;
  		}
  		if (S_ISDIR(sb.st_mode)) {
! 		  resultcode = chdir(p);
! 		  if (EACCES == errno) {
! 			uid_t userid = geteuid();
  			delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  			seteuid(0);
  			resultcode = chdir(p);
  			seteuid(userid);
  			enable_signaling(); /* we can allow signals once again: kinch */
+ 		  }
  		  if (resultcode < 0)
  			goto err1;
  		  p = "";
***************
*** 214,225 ****
  	 * the current directory.
  	 */
  	(void)strcpy(wbuf, p);
! 	userid = geteuid();
  	delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  	seteuid(0);
  	resultcode = getcwd(resolved, MAXPATHLEN) == NULL ? 0 : 1;
  	seteuid(userid);
  	enable_signaling(); /* we can allow signals once again: kinch */
  	if (resultcode == 0)
  		goto err1;
  
- --- 233,247 ----
  	 * the current directory.
  	 */
  	(void)strcpy(wbuf, p);
! 	resultcode = getcwd(resolved, MAXPATHLEN) == NULL ? 0 : 1;
! 	if (EACCES == errno) {
! 	  uid_t userid = geteuid();
  	  delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  	  seteuid(0);
  	  resultcode = getcwd(resolved, MAXPATHLEN) == NULL ? 0 : 1;
  	  seteuid(userid);
  	  enable_signaling(); /* we can allow signals once again: kinch */
+ 	}
  	if (resultcode == 0)
  		goto err1;
  
***************
*** 243,249 ****
  	}
  
  	/* Go back to where we came from. */
! 	userid = geteuid();
  	delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  	seteuid(0);
  #ifdef HAS_NO_FCHDIR
- --- 265,277 ----
  	}
  
  	/* Go back to where we came from. */
! #ifdef HAS_NO_FCHDIR
! 	resultcode = chdir(cwd);
! #else
! 	resultcode = fchdir(fd);
! #endif
! 	if (EACCES == errno) {
! 	  uid_t userid = geteuid();
  	  delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  	  seteuid(0);
  #ifdef HAS_NO_FCHDIR
***************
*** 253,258 ****
- --- 281,287 ----
  #endif
  	  seteuid(userid);
  	  enable_signaling(); /* we can allow signals once again: kinch */
+ 	}
  	if (resultcode < 0) {
  		serrno = errno;
  		goto err2;
***************
*** 265,271 ****
  	return (resolved);
  
  err1:	serrno = errno;
! 	userid = geteuid();
  #ifdef HAS_NO_FCHDIR
- --- 294,306 ----
  	return (resolved);
  
  err1:	serrno = errno;
! #ifdef HAS_NO_FCHDIR
! 	(void)chdir(cwd);
! #else
! 	(void)fchdir(fd);
! #endif
! 	if (EACCES == errno) {
! 	  uid_t userid = geteuid();
  	  delay_signaling(); /* we can't allow any signals while euid==0: kinch */
  	  seteuid(0);
  #ifdef HAS_NO_FCHDIR
***************
*** 275,280 ****
- --- 310,316 ----
  #endif
  	  seteuid(userid);
  	  enable_signaling(); /* we can allow signals once again: kinch */
+ 	}
  #ifdef HAS_NO_FCHDIR
  err2:	errno = serrno;
  #else