Subject: bin/19377: In ftpd, pwd will fail in a directory with mode 111.
To: None <gnats-bugs@gnats.netbsd.org>
From: None <manu@netbsd.org>
List: netbsd-bugs
Date: 12/13/2002 19:07:22
>Number: 19377
>Category: bin
>Synopsis: In ftpd, pwd will fail in a directory with mode 111.
>Confidential: no
>Severity: non-critical
>Priority: medium
>Responsible: bin-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Fri Dec 13 10:08:01 PST 2002
>Closed-Date:
>Last-Modified:
>Originator: Emmanuel Dreyfus
>Release: NetBSD 1.6
>Organization:
NetBSD
>Environment:
System: NetBSD melancolie 1.6 NetBSD 1.6 (GENERIC) #0: Sun Sep 8 19:43:40 UTC 2002 autobuild@tgm.daemon.org:/autobuild/i386/OBJ/autobuild/src/sys/arch/i386/compile/GENERIC i386
Architecture: i386
Machine: i386
>Description:
When the user go through a mode 111 directory, there are situations
where ftpd is unable to answer to the pwd request. This confuses
some FTP clients.
This is annoying since other FTP servers, such as WU-ftpd, are able
to answer to pwd in this kind of situations.
>How-To-Repeat:
# mkdir /ftp/pub/shadow
# chmod 111 /ftp/pub/shadow
# mkdir /ftp/pub/shadow/test
# chod 755 /ftp/pub/shadow/test
# ftp ftp://localhost/pub/shadow/test/
(blah blah blah)
ftp> pwd
550 Can't get the current directory: Permission denied.
>Fix:
The problem is that we retreive the path with getcwd(), and that
getcwd needs the +x permission on each directory in the path.
We can keep track of where the user went, and when we are asked about
pwd, return this cached pwd instead of retreiving it if we are unable
to do so. This is what WU-ftpd does.
I attached a patch that does an hybrid between cached path and
getcwd(): if getcwd works, use it, else use a cached path.
I tested it, it works well, except that it has trouble to follow
symlinks correctly when the link crosses mode 111 directories. The
displayed path is still correct, but it goes through the link instead
of going through the target. Example with the preceding situation:
# mkdir /ftp/pub/test
# ln -sf ../shadow/test /ftp/pub/test/a
ftp ftp://localhost/pub/test/
(blah blah)
ftp> cd a
250 CWD command successful.
ftp> pwd
257 "/pub/test/b" is the current directory.
While this fix is good enough to prevent some client from getting
crazy, we may consider a more general fix at the libc level: the
same problem exists in /bin/sh: once in a mode 111 directory, pwd
does not work anymore. /bin/ksh seems to keep track of the path
itself and it is able to display the directory.
Index: cmds.c
===================================================================
RCS file: /cvsroot/basesrc/libexec/ftpd/cmds.c,v
retrieving revision 1.16.2.1
diff -U4 -r1.16.2.1 cmds.c
--- cmds.c 2002/11/01 08:23:39 1.16.2.1
+++ cmds.c 2002/12/13 17:38:47
@@ -170,8 +170,9 @@
};
#define FACTTABSIZE (sizeof(facttab) / sizeof(struct ftpfact))
+static char cached_path[MAXPATHLEN + 1] = "/";
void
cwd(const char *path)
{
@@ -180,8 +181,48 @@
perror_reply(550, path);
else {
show_chdir_messages(250);
ack("CWD");
+ if (getcwd(cached_path, MAXPATHLEN) == NULL) {
+ char tmp_path[MAXPATHLEN + 1] = "";
+ char *cp;
+ char *cq;
+
+ if (path[0] != '/') {
+ (void)strncpy(tmp_path,
+ cached_path, MAXPATHLEN);
+ (void)strncat(tmp_path, "/", MAXPATHLEN);
+ }
+ (void)strncat(tmp_path, path, MAXPATHLEN);
+ (void)strncat(tmp_path, "/", MAXPATHLEN);
+
+ /* Collapse any // into / */
+ while ((cp = strstr(tmp_path, "//")) != NULL)
+ (void)memmove(cp, cp + 1, strlen(cp) - 1 + 1);
+
+ /* Collapse any /./ into / */
+ while ((cp = strstr(tmp_path, "/./")) != NULL)
+ (void)memmove(cp, cp + 2, strlen(cp) - 2 + 1);
+
+ /* Collapse any /foo/../ into /foo/ */
+ while ((cp = strstr(tmp_path, "/../")) != NULL) {
+ /* ^/../foo/ becomes ^/foo/ */
+ if (cp == tmp_path) {
+ (void)memmove(cp, cp + 3,
+ strlen(cp) - 3 + 1);
+ } else {
+ for (cq = cp - 1; *cq != '/'; cq--);
+ (void)memmove(cq, cp + 3,
+ strlen(cp) - 3 + 1);
+ }
+ }
+
+ /* Strip trailing / if path is not just ^/$ */
+ if (strlen(tmp_path) != 1)
+ tmp_path[strlen(tmp_path) - 1] = '\0';
+
+ (void)strncpy(cached_path, tmp_path, MAXPATHLEN);
+ }
}
}
void
@@ -403,13 +444,11 @@
pwd(void)
{
char path[MAXPATHLEN];
- if (getcwd(path, sizeof(path) - 1) == NULL)
- reply(550, "Can't get the current directory: %s.",
- strerror(errno));
- else
- replydirname(path, "is the current directory.");
+ if (getcwd(path, sizeof(path) - 1) == NULL)
+ (void)strncpy(path, cached_path, MAXPATHLEN);
+ replydirname(path, "is the current directory.");
}
void
removedir(const char *name)
>Release-Note:
>Audit-Trail:
>Unformatted: