From 32adf030a4ada4209e1124af00a41bfdc2e06cc8 Mon Sep 17 00:00:00 2001 From: manu Date: Wed, 8 Jan 2003 18:07:31 +0000 Subject: 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. --- libexec/ftpd/cmds.c | 146 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 139 insertions(+), 7 deletions(-) (limited to 'libexec') diff --git a/libexec/ftpd/cmds.c b/libexec/ftpd/cmds.c index c6254b4f4f6..7a287ea5927 100644 --- a/libexec/ftpd/cmds.c +++ b/libexec/ftpd/cmds.c @@ -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 #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 @@ -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; +} + -- cgit