Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 9 Jul 2001 07:10:20 +0900
From:      "Eugene M. Kim" <ab@astralblue.net>
To:        FreeBSD-current Mailing List <current@freebsd.org>
Subject:   Request for change: Disabling filename globbing by ftpd(8)
Message-ID:  <20010709071020.A42364@the-7.net>

next in thread | raw e-mail | index | archive | help

--h31gzZEtNLTqOjlF
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Greetings,

* Conclusion and suggestion first:
Csh-style filename globbing in ftpd(8) is *evil*.  Please apply the
attached patch to make it an option and disable it in the default
configuration.

* What the patch does:
It makes the filename globbing an optional feature, controlled by the
new flag -g.  -g none disables the globbing entirely (default),
-g tildeonly enables home expansion (aka tilde expansion) only, and
-g all enables all expansions using glob(3).  The current behavior can
be kept by using -g all.

* My reason to it:
Many FTP clients, especially automated mirroring tools and GUI-based
ones, and most notably the `mget' command commonly found on standard
FTP clients, do one thing in common: they obtain the name of the remote
repository using NLST command then subsequently use some or all of the
returned names as the argument to other commands such as RETR.

In order for this approach to succeed, arguments to the RETR command
must not be parsed in any special way but they must be considered as
literal filenames.  However, this is not the case with the stock ftpd(8)
shipped with FreeBSD; it has a `feature' that expands the argument to
RETR/CWD/STOR/... commands in a Csh-like way (i.e. filename globbing,
tilde/brace/bracket/ampersand expansions).

This changes the semantics of the on-the-wire protocol.  RFC 959 does
not specify any special handling of pathname arguments, so the change
breaks compatibility with any potential client which legitimately
assumes no special tweaks to pathnames are necessary.

Moreover, commands such as RETR, CWD and STOR only expect an argument
that designates a single file or directory; it is impossible to fetch
multiple files using RETR, or chdir into multiple directories at once
:-).  In this context, globbing by ftpd is nothing more than an useful
shorthand (e.g. we can say "cd abc*" instead of "cd abcdefghijklmnopq"),
which is much better to be done on the client side.

Example: the remote directory contains two files, `A.jpg' and `A{3}.jpg'
and the client tries to `mget A*.jpg'.
Step 1. The client sends "NLST" command.
Step 2. The server returns a full listing of the remote directory.
Step 3. The client searches through the list and picks up the two files.
Step 4. The client performs `RETR A.jpg', which succeeds.
Step 5. The client performs `RETR A{3}.jpg', which fails because the
server performs brace expansion on the name and tries to send `A3.jpg'.

Any comments and suggestions are welcomed.  Thank you.

Best Regards,
Eugene

--h31gzZEtNLTqOjlF
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="ftpd.diff"

diff -urN ftpd/ftpcmd.y ftpd.new/ftpcmd.y
--- ftpd/ftpcmd.y	Wed Apr 18 03:03:52 2001
+++ ftpd.new/ftpcmd.y	Mon Jul  9 01:34:29 2001
@@ -71,6 +71,7 @@
 #include <libutil.h>
 
 #include "extern.h"
+#include "types.h"
 
 extern	union sockunion data_dest, his_addr;
 extern	int logged_in;
@@ -92,6 +93,8 @@
 extern  char tmpline[];
 extern	int readonly;
 extern	int noepsv;
+extern	globbing_t globbing;
+extern	char curname[MAXLOGNAME];
 
 off_t	restart_point;
 
@@ -924,7 +927,7 @@
 			 * processing, but only gives a 550 error reply.
 			 * This is a valid reply in some cases but not in others.
 			 */
-			if (logged_in && $1) {
+			if (logged_in && globbing == GLOBBING_ALL && $1) {
 				glob_t gl;
 				int flags =
 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
@@ -944,6 +947,37 @@
 				}
 				globfree(&gl);
 				free($1);
+			} else if (globbing == GLOBBING_TILDEONLY &&
+			    $1[0] == '~') {
+				/* do tilde expansion by ourselves */
+				char *dir, *newdir, *afteruser, afteruser_ch;
+				struct passwd *pw;
+				$$ = $1;
+				do {
+					dir = strdup($1);
+					if (dir == NULL)
+						break;
+					afteruser = strchr(dir, '/');
+					if (afteruser == NULL)
+						afteruser = strchr(dir, '\0');
+					afteruser_ch = *afteruser;
+					*afteruser = '\0';
+					pw = getpwnam((dir[1] != '\0')
+					    ? dir + 1 : curname);
+					*afteruser = afteruser_ch;
+					if (pw == NULL || pw->pw_dir == NULL)
+						break;
+					asprintf(&newdir, "%s%s",
+					    pw->pw_dir, afteruser);
+					/* XXX LEAK: who should free newdir? */
+					if (newdir == NULL)
+						break;
+					free($1);
+					$1 = newdir;
+					$$ = $1;
+				} while (0);
+				if (dir != NULL)
+					free(dir);
 			} else
 				$$ = $1;
 		}
diff -urN ftpd/ftpd.8 ftpd.new/ftpd.8
--- ftpd/ftpd.8	Mon Mar  5 20:14:50 2001
+++ ftpd.new/ftpd.8	Mon Jul  9 01:44:13 2001
@@ -161,6 +161,23 @@
 .It Fl E
 Disable the EPSV command.
 This is useful for servers behind older firewalls.
+.It Fl g
+Perform specified
+.Ar type
+of globbing on the pathnames given by FTP clients.
+.Ar type
+can be one of:
+.Bl -tag -width indent
+.It Li none
+Disable the globbing entirely (default).
+.It Li tildeonly
+Enable the tilde (home directory) expansion only.
+.It Li all
+Enable all
+.Xr csh 1
+style globbing, including wildcard, bracket, brace, ampersand and tilde
+expansions.
+.El
 .El
 .Pp
 The file
@@ -265,14 +282,6 @@
 STAT
 command is received during a data transfer, preceded by a Telnet IP
 and Synch, transfer status will be returned.
-.Pp
-.Nm Ftpd
-interprets file names according to the
-.Dq globbing
-conventions used by
-.Xr csh 1 .
-This allows users to utilize the metacharacters
-.Dq Li \&*?[]{}~ .
 .Pp
 .Nm Ftpd
 authenticates users according to six rules. 
diff -urN ftpd/ftpd.c ftpd.new/ftpd.c
--- ftpd/ftpd.c	Wed Mar 21 23:40:36 2001
+++ ftpd.new/ftpd.c	Mon Jul  9 01:23:08 2001
@@ -101,6 +101,7 @@
 
 #include "pathnames.h"
 #include "extern.h"
+#include "types.h"
 
 #if __STDC__
 #include <stdarg.h>
@@ -150,6 +151,8 @@
 int	pdata = -1;		/* for passive mode */
 int	readonly=0;		/* Server is in readonly mode.	*/
 int	noepsv=0;		/* EPSV command is disabled.	*/
+globbing_t globbing = GLOBBING_NONE; /* perform filename globbing.	*/
+char	curname[MAXLOGNAME];	/* current USER name */
 sig_atomic_t transflag;
 off_t	file_size;
 off_t	byte_count;
@@ -299,7 +302,7 @@
 #endif /* OLD_SETPROCTITLE */
 
 
-	while ((ch = getopt(argc, argv, "AdlDESURrt:T:u:va:p:46")) != -1) {
+	while ((ch = getopt(argc, argv, "AdlDESURrt:T:u:va:p:46g:")) != -1) {
 		switch (ch) {
 		case 'D':
 			daemon_mode++;
@@ -382,6 +385,17 @@
 			family = AF_INET6;
 			break;
 
+		case 'g':
+			if (strcmp(optarg, "none") == 0)
+				globbing = GLOBBING_NONE;
+			else if (strcmp(optarg, "tildeonly") == 0)
+				globbing = GLOBBING_TILDEONLY;
+			else if (strcmp(optarg, "all") == 0)
+				globbing = GLOBBING_ALL;
+			else
+				warnx("bad value \"%s\" for -g", optarg);
+			break;
+
 		default:
 			warnx("unknown flag -%c ignored", optopt);
 			break;
@@ -883,7 +897,6 @@
 
 static int login_attempts;	/* number of failed login attempts */
 static int askpasswd;		/* had user command, ask for passwd */
-static char curname[MAXLOGNAME];	/* current USER name */
 
 /*
  * USER command.
@@ -2650,29 +2663,51 @@
 	DIR *dirp = NULL;
 	struct dirent *dir;
 	FILE *dout = NULL;
-	char **dirlist, *dirname;
+	char **dirlist, *dirname, *whichdir = whichf, *afteruser, afteruser_ch;
+	char *newwhichdir;
 	int simple = 0;
-	int freeglob = 0;
+	int freeglob = 0, freewhichdir = 0;
 	glob_t gl;
+	struct passwd *pw;
 
-	if (strpbrk(whichf, "~{[*?") != NULL) {
+	if (globbing == GLOBBING_TILDEONLY && whichdir[0] == '~') {
+		/* do tilde expansion by ourselves */
+		do {
+			afteruser = strchr(whichdir, '/');
+			if (afteruser == NULL)
+				afteruser = strchr(whichdir, '\0');
+			afteruser_ch = *afteruser;
+			*afteruser = '\0';
+			pw = getpwnam((whichdir[1] != '\0')
+			    ? whichdir + 1 : curname);
+			*afteruser = afteruser_ch;
+			if (pw == NULL || pw->pw_dir == NULL)
+				break;
+			asprintf(&newwhichdir, "%s%s", pw->pw_dir, afteruser);
+			if (newwhichdir == NULL)
+				break;
+			whichdir = newwhichdir;
+			freewhichdir = 1;
+		} while (0);
+	}
+	if (globbing == GLOBBING_ALL && strpbrk(whichdir, "~{[*?") != NULL) {
 		int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
 
 		memset(&gl, 0, sizeof(gl));
 		gl.gl_matchc = MAXGLOBARGS;
 		flags |= GLOB_MAXPATH;
 		freeglob = 1;
-		if (glob(whichf, flags, 0, &gl)) {
+		if (glob(whichdir, flags, 0, &gl)) {
 			reply(550, "not found");
 			goto out;
 		} else if (gl.gl_pathc == 0) {
 			errno = ENOENT;
-			perror_reply(550, whichf);
+			perror_reply(550, whichdir);
 			goto out;
 		}
 		dirlist = gl.gl_pathv;
 	} else {
-		onefile[0] = whichf;
+		onefile[0] = whichdir;
 		dirlist = onefile;
 		simple = 1;
 	}
@@ -2772,6 +2807,10 @@
 	if (freeglob) {
 		freeglob = 0;
 		globfree(&gl);
+	}
+	if (freewhichdir) {
+		freewhichdir = 0;
+		free(whichdir);
 	}
 }
 
diff -urN ftpd/types.h ftpd.new/types.h
--- ftpd/types.h	Thu Jan  1 09:00:00 1970
+++ ftpd.new/types.h	Mon Jul  9 00:21:33 2001
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 1992, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)types.h	8.2 (Berkeley) 4/4/94
+ * $FreeBSD$
+ */
+
+#ifndef TYPES_H__
+#define TYPES_H__
+
+typedef enum {
+    GLOBBING_NONE,
+    GLOBBING_TILDEONLY,
+    GLOBBING_ALL
+} globbing_t;
+
+#endif /* TYPES_H__ */

--h31gzZEtNLTqOjlF--

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-current" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20010709071020.A42364>