diff options
| author | manu <manu@NetBSD.org> | 2003-01-08 18:07:31 +0000 |
|---|---|---|
| committer | manu <manu@NetBSD.org> | 2003-01-08 18:07:31 +0000 |
| commit | 32adf030a4ada4209e1124af00a41bfdc2e06cc8 (patch) | |
| tree | 474608d625a306cc5a83739a7cb25fc614044244 /libexec/ftpd | |
| parent | f63279a9b673473610d1bc94f128544a2517d595 (diff) | |
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.
Diffstat (limited to 'libexec/ftpd')
| -rw-r--r-- | libexec/ftpd/cmds.c | 146 |
1 files changed, 139 insertions, 7 deletions
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 <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; +} + |
