ftpd was unable to service the pwd request once you entered a directory

without search permission. This confused some ftp clients.

We fix this problem by maitaining a cached path when getcwd() does not work.
The symbolic links and ../ are resolved in the cached path, and it is finnally
checked for accuracy by comparing ./ and the cached path with stat (device
and inode comparison). If the comparison fails, pwd fails as it did before,
and if the comparison succeeds, the cached path is displayed.

If paths are too long, we should just compare ./ with a truncated path and
fail, thus making pwd displaying an error as it did before.
This commit is contained in:
manu 2003-01-08 18:07:31 +00:00
parent f63279a9b6
commit 32adf030a4

View File

@ -1,4 +1,4 @@
/* $NetBSD: cmds.c,v 1.19 2002/10/25 01:45:37 itojun Exp $ */
/* $NetBSD: cmds.c,v 1.20 2003/01/08 18:07:31 manu Exp $ */
/*
* Copyright (c) 1999-2001 The NetBSD Foundation, Inc.
@ -101,7 +101,7 @@
#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: cmds.c,v 1.19 2002/10/25 01:45:37 itojun Exp $");
__RCSID("$NetBSD: cmds.c,v 1.20 2003/01/08 18:07:31 manu Exp $");
#endif /* not lint */
#include <sys/param.h>
@ -171,6 +171,8 @@ struct ftpfact facttab[] = {
#define FACTTABSIZE (sizeof(facttab) / sizeof(struct ftpfact))
static char cached_path[MAXPATHLEN + 1] = "/";
static void discover_path(char *, const char *);
void
cwd(const char *path)
@ -181,6 +183,9 @@ cwd(const char *path)
else {
show_chdir_messages(250);
ack("CWD");
if (getcwd(cached_path, MAXPATHLEN) == NULL) {
discover_path(cached_path, path);
}
}
}
@ -404,11 +409,15 @@ 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) {
if (chdir(cached_path) < 0) {
reply(550, "Can't get the current directory: %s.",
strerror(errno));
return;
}
(void)strlcpy(path, cached_path, MAXPATHLEN);
}
replydirname(path, "is the current directory.");
}
void
@ -844,3 +853,126 @@ replydirname(const char *name, const char *message)
*p = '\0';
reply(257, "\"%s\" %s", npath, message);
}
static void
discover_path(last_path, new_path)
char *last_path;
const char *new_path;
{
char tp[MAXPATHLEN + 1] = "";
char tq[MAXPATHLEN + 1] = "";
char *cp;
char *cq;
int sz1, sz2;
int nomorelink;
struct stat st1, st2;
if (new_path[0] != '/') {
(void)strlcpy(tp, last_path, MAXPATHLEN);
(void)strlcat(tp, "/", MAXPATHLEN);
}
(void)strlcat(tp, new_path, MAXPATHLEN);
(void)strlcat(tp, "/", MAXPATHLEN);
/*
* resolve symlinks. A symlink may introduce another symlink, so we
* loop trying to resolve symlinks until we don't find any of them.
*/
do {
/* Collapse any // into / */
while ((cp = strstr(tp, "//")) != NULL)
(void)memmove(cp, cp + 1, strlen(cp) - 1 + 1);
/* Collapse any /./ into / */
while ((cp = strstr(tp, "/./")) != NULL)
(void)memmove(cp, cp + 2, strlen(cp) - 2 + 1);
cp = tp;
nomorelink = 1;
while ((cp = strstr(++cp, "/")) != NULL) {
sz1 = (u_long)cp - (u_long)tp;
if (sz1 > MAXPATHLEN)
goto bad;
*cp = 0;
sz2 = readlink(tp, tq, MAXPATHLEN);
*cp = '/';
/* If this is not a symlink, move to next / */
if (sz2 <= 0)
continue;
/*
* We found a symlink, so we will have to
* do one more pass to check there is no
* more symlink in the path
*/
nomorelink = 0;
/*
* Null terminate the string and remove trailing /
*/
tq[sz2] = 0;
sz2 = strlen(tq);
if (tq[sz2 - 1] == '/')
tq[--sz2] = 0;
/*
* Is this an absolute link or a relative link?
*/
if (tq[0] == '/') {
/* absolute link */
if (strlen(cp) + sz2 > MAXPATHLEN)
goto bad;
memmove(tp + sz2, cp, strlen(cp) + 1);
memcpy(tp, tq, sz2);
} else {
/* relative link */
for (cq = cp - 1; *cq != '/'; cq--);
if (strlen(tp) - ((u_long)cq - (u_long)cp)
+ 1 + sz2 > MAXPATHLEN)
goto bad;
(void)memmove(cq + 1 + sz2,
cp, strlen(cp) + 1);
(void)memcpy(cq + 1, tq, sz2);
}
/*
* start over, looking for new symlinks
*/
break;
}
} while (nomorelink == 0);
/* Collapse any /foo/../ into /foo/ */
while ((cp = strstr(tp, "/../")) != NULL) {
/* ^/../foo/ becomes ^/foo/ */
if (cp == tp) {
(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 strailing / */
if (strlen(tp) != 1)
tp[strlen(tp) - 1] = '\0';
/* check that the path is correct */
stat(tp, &st1);
stat(".", &st2);
if ((st1.st_dev != st2.st_dev) || (st1.st_ino != st2.st_ino))
goto bad;
(void)strlcpy(last_path, tp, MAXPATHLEN);
return;
bad:
(void)strlcat(last_path, "/", MAXPATHLEN);
(void)strlcat(last_path, new_path, MAXPATHLEN);
return;
}