/* $NetBSD: cmds.c,v 1.26 2008/06/09 00:33:39 lukem Exp $ */ /* * Copyright (c) 1999-2004 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Luke Mewburn. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /* * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994 * 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. 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. */ /* * Copyright (C) 1997 and 1998 WIDE Project. * 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. Neither the name of the project 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 PROJECT 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 PROJECT 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. */ #include #ifndef lint __RCSID("$NetBSD: cmds.c,v 1.26 2008/06/09 00:33:39 lukem Exp $"); #endif /* not lint */ #include #include #include #include #include #include #include #include #include #include #include #ifdef KERBEROS5 #include #endif #include "extern.h" typedef enum { FE_MLSD = 1<<0, /* if op is MLSD (MLST otherwise ) */ FE_ISCURDIR = 1<<1, /* if name is the current directory */ } factflag_t; typedef struct { const char *path; /* full pathname */ const char *display; /* name to display */ struct stat *stat; /* stat of path */ struct stat *pdirstat; /* stat of path's parent dir */ factflag_t flags; /* flags */ } factelem; static void ack(const char *); static void base64_encode(const char *, size_t, char *, int); static void fact_type(const char *, FILE *, factelem *); static void fact_size(const char *, FILE *, factelem *); static void fact_modify(const char *, FILE *, factelem *); static void fact_perm(const char *, FILE *, factelem *); static void fact_unique(const char *, FILE *, factelem *); static int matchgroup(gid_t); static void mlsname(FILE *, factelem *); static void replydirname(const char *, const char *); struct ftpfact { const char *name; /* name of fact */ int enabled; /* if fact is enabled */ void (*display)(const char *, FILE *, factelem *); /* function to display fact */ }; struct ftpfact facttab[] = { { "Type", 1, fact_type }, #define FACT_TYPE 0 { "Size", 1, fact_size }, { "Modify", 1, fact_modify }, { "Perm", 1, fact_perm }, { "Unique", 1, fact_unique }, /* "Create" */ /* "Lang" */ /* "Media-Type" */ /* "CharSet" */ }; #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) { if (chdir(path) < 0) perror_reply(550, path); else { show_chdir_messages(250); ack("CWD"); if (getcwd(cached_path, MAXPATHLEN) == NULL) { discover_path(cached_path, path); } } } void delete(const char *name) { char *p = NULL; if (remove(name) < 0) { p = strerror(errno); perror_reply(550, name); } else ack("DELE"); logxfer("delete", -1, name, NULL, NULL, p); } void feat(void) { int i; reply(-211, "Features supported"); cprintf(stdout, " MDTM\r\n"); cprintf(stdout, " MLST "); for (i = 0; i < FACTTABSIZE; i++) cprintf(stdout, "%s%s;", facttab[i].name, facttab[i].enabled ? "*" : ""); cprintf(stdout, "\r\n"); cprintf(stdout, " REST STREAM\r\n"); cprintf(stdout, " SIZE\r\n"); cprintf(stdout, " TVFS\r\n"); reply(211, "End"); } void makedir(const char *name) { char *p = NULL; if (mkdir(name, 0777) < 0) { p = strerror(errno); perror_reply(550, name); } else replydirname(name, "directory created."); logxfer("mkdir", -1, name, NULL, NULL, p); } void mlsd(const char *path) { struct dirent *dp; struct stat sb, pdirstat; factelem f; FILE *dout; DIR *dirp; char name[MAXPATHLEN]; int hastypefact; hastypefact = facttab[FACT_TYPE].enabled; if (path == NULL) path = "."; if (stat(path, &pdirstat) == -1) { mlsdperror: perror_reply(550, path); return; } if (! S_ISDIR(pdirstat.st_mode)) { errno = ENOTDIR; perror_reply(501, path); return; } if ((dirp = opendir(path)) == NULL) goto mlsdperror; dout = dataconn("MLSD", (off_t)-1, "w"); if (dout == NULL) return; memset(&f, 0, sizeof(f)); f.stat = &sb; f.flags |= FE_MLSD; while ((dp = readdir(dirp)) != NULL) { snprintf(name, sizeof(name), "%s/%s", path, dp->d_name); if (ISDOTDIR(dp->d_name)) { /* special case curdir: */ if (! hastypefact) continue; f.pdirstat = NULL; /* require stat of parent */ f.display = path; /* set name to real name */ f.flags |= FE_ISCURDIR; /* flag name is curdir */ } else { if (ISDOTDOTDIR(dp->d_name)) { if (! hastypefact) continue; f.pdirstat = NULL; } else f.pdirstat = &pdirstat; /* cache parent stat */ f.display = dp->d_name; f.flags &= ~FE_ISCURDIR; } if (stat(name, &sb) == -1) continue; f.path = name; mlsname(dout, &f); } (void)closedir(dirp); if (ferror(dout) != 0) perror_reply(550, "Data connection"); else reply(226, "MLSD complete."); closedataconn(dout); total_xfers_out++; total_xfers++; } void mlst(const char *path) { struct stat sb; factelem f; if (path == NULL) path = "."; if (stat(path, &sb) == -1) { perror_reply(550, path); return; } reply(-250, "MLST %s", path); memset(&f, 0, sizeof(f)); f.path = path; f.display = path; f.stat = &sb; f.pdirstat = NULL; CPUTC(' ', stdout); mlsname(stdout, &f); reply(250, "End"); } void opts(const char *command) { struct tab *c; char *ep; if ((ep = strchr(command, ' ')) != NULL) *ep++ = '\0'; c = lookup(cmdtab, command); if (c == NULL) { reply(502, "Unknown command '%s'.", command); return; } if (! CMD_IMPLEMENTED(c)) { reply(502, "%s command not implemented.", c->name); return; } if (! CMD_HAS_OPTIONS(c)) { reply(501, "%s command does not support persistent options.", c->name); return; } /* special case: MLST */ if (strcasecmp(command, "MLST") == 0) { int enabled[FACTTABSIZE]; int i, onedone; size_t len; char *p; for (i = 0; i < sizeof(enabled) / sizeof(int); i++) enabled[i] = 0; if (ep == NULL || *ep == '\0') goto displaymlstopts; /* don't like spaces, and need trailing ; */ len = strlen(ep); if (strchr(ep, ' ') != NULL || ep[len - 1] != ';') { badmlstopt: reply(501, "Invalid MLST options"); return; } ep[len - 1] = '\0'; while ((p = strsep(&ep, ";")) != NULL) { if (*p == '\0') goto badmlstopt; for (i = 0; i < FACTTABSIZE; i++) if (strcasecmp(p, facttab[i].name) == 0) { enabled[i] = 1; break; } } displaymlstopts: for (i = 0; i < FACTTABSIZE; i++) facttab[i].enabled = enabled[i]; cprintf(stdout, "200 MLST OPTS"); for (i = onedone = 0; i < FACTTABSIZE; i++) { if (facttab[i].enabled) { cprintf(stdout, "%s%s;", onedone ? "" : " ", facttab[i].name); onedone++; } } cprintf(stdout, "\r\n"); fflush(stdout); return; } /* default cases */ if (ep != NULL && *ep != '\0') REASSIGN(c->options, ftpd_strdup(ep)); if (c->options != NULL) reply(200, "Options for %s are '%s'.", c->name, c->options); else reply(200, "No options defined for %s.", c->name); } void pwd(void) { char path[MAXPATHLEN]; 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 removedir(const char *name) { char *p = NULL; if (rmdir(name) < 0) { p = strerror(errno); perror_reply(550, name); } else ack("RMD"); logxfer("rmdir", -1, name, NULL, NULL, p); } char * renamefrom(const char *name) { struct stat st; if (stat(name, &st) < 0) { perror_reply(550, name); return (NULL); } reply(350, "File exists, ready for destination name"); return (ftpd_strdup(name)); } void renamecmd(const char *from, const char *to) { char *p = NULL; if (rename(from, to) < 0) { p = strerror(errno); perror_reply(550, "rename"); } else ack("RNTO"); logxfer("rename", -1, from, to, NULL, p); } void sizecmd(const char *filename) { switch (type) { case TYPE_L: case TYPE_I: { struct stat stbuf; if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) reply(550, "%s: not a plain file.", filename); else reply(213, ULLF, (ULLT)stbuf.st_size); break; } case TYPE_A: { FILE *fin; int c; off_t count; struct stat stbuf; fin = fopen(filename, "r"); if (fin == NULL) { perror_reply(550, filename); return; } if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) { reply(550, "%s: not a plain file.", filename); (void) fclose(fin); return; } if (stbuf.st_size > 10240) { reply(550, "%s: file too large for SIZE.", filename); (void) fclose(fin); return; } count = 0; while((c = getc(fin)) != EOF) { if (c == '\n') /* will get expanded to \r\n */ count++; count++; } (void) fclose(fin); reply(213, LLF, (LLT)count); break; } default: reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]); } } void statfilecmd(const char *filename) { FILE *fin; int c; int atstart; char *argv[] = { INTERNAL_LS, "-lgA", "", NULL }; argv[2] = (char *)filename; fin = ftpd_popen(argv, "r", STDOUT_FILENO); reply(-211, "status of %s:", filename); /* XXX: use fgetln() or fparseln() here? */ atstart = 1; while ((c = getc(fin)) != EOF) { if (c == '\n') { if (ferror(stdout)){ perror_reply(421, "control connection"); (void) ftpd_pclose(fin); dologout(1); /* NOTREACHED */ } if (ferror(fin)) { perror_reply(551, filename); (void) ftpd_pclose(fin); return; } CPUTC('\r', stdout); } if (atstart && isdigit(c)) CPUTC(' ', stdout); CPUTC(c, stdout); atstart = (c == '\n'); } (void) ftpd_pclose(fin); reply(211, "End of Status"); } /* -- */ static void ack(const char *s) { reply(250, "%s command successful.", s); } /* * Encode len bytes starting at clear using base64 encoding into encoded, * which should be at least ((len + 2) * 4 / 3 + 1) in size. * If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary * with `='. */ static void base64_encode(const char *clear, size_t len, char *encoded, int nulterm) { static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const char *c; char *e, termchar; int i; /* determine whether to pad with '=' or NUL terminate */ termchar = nulterm ? '\0' : '='; c = clear; e = encoded; /* convert all but last 2 bytes */ for (i = len; i > 2; i -= 3, c += 3) { *e++ = base64[(c[0] >> 2) & 0x3f]; *e++ = base64[((c[0] << 4) & 0x30) | ((c[1] >> 4) & 0x0f)]; *e++ = base64[((c[1] << 2) & 0x3c) | ((c[2] >> 6) & 0x03)]; *e++ = base64[(c[2]) & 0x3f]; } /* handle slop at end */ if (i > 0) { *e++ = base64[(c[0] >> 2) & 0x3f]; *e++ = base64[((c[0] << 4) & 0x30) | (i > 1 ? ((c[1] >> 4) & 0x0f) : 0)]; *e++ = (i > 1) ? base64[(c[1] << 2) & 0x3c] : termchar; *e++ = termchar; } *e = '\0'; } static void fact_modify(const char *fact, FILE *fd, factelem *fe) { struct tm *t; t = gmtime(&(fe->stat->st_mtime)); cprintf(fd, "%s=%04d%02d%02d%02d%02d%02d;", fact, TM_YEAR_BASE + t->tm_year, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); } static void fact_perm(const char *fact, FILE *fd, factelem *fe) { int rok, wok, xok, pdirwok; struct stat *pdir; if (fe->stat->st_uid == geteuid()) { rok = ((fe->stat->st_mode & S_IRUSR) != 0); wok = ((fe->stat->st_mode & S_IWUSR) != 0); xok = ((fe->stat->st_mode & S_IXUSR) != 0); } else if (matchgroup(fe->stat->st_gid)) { rok = ((fe->stat->st_mode & S_IRGRP) != 0); wok = ((fe->stat->st_mode & S_IWGRP) != 0); xok = ((fe->stat->st_mode & S_IXGRP) != 0); } else { rok = ((fe->stat->st_mode & S_IROTH) != 0); wok = ((fe->stat->st_mode & S_IWOTH) != 0); xok = ((fe->stat->st_mode & S_IXOTH) != 0); } cprintf(fd, "%s=", fact); /* * if parent info not provided, look it up, but * only if the current class has modify rights, * since we only need this info in such a case. */ pdir = fe->pdirstat; if (pdir == NULL && CURCLASS_FLAGS_ISSET(modify)) { size_t len; char realdir[MAXPATHLEN], *p; struct stat dir; len = strlcpy(realdir, fe->path, sizeof(realdir)); if (len < sizeof(realdir) - 4) { if (S_ISDIR(fe->stat->st_mode)) strlcat(realdir, "/..", sizeof(realdir)); else { /* if has a /, move back to it */ /* otherwise use '..' */ if ((p = strrchr(realdir, '/')) != NULL) { if (p == realdir) p++; *p = '\0'; } else strlcpy(realdir, "..", sizeof(realdir)); } if (stat(realdir, &dir) == 0) pdir = &dir; } } pdirwok = 0; if (pdir != NULL) { if (pdir->st_uid == geteuid()) pdirwok = ((pdir->st_mode & S_IWUSR) != 0); else if (matchgroup(pdir->st_gid)) pdirwok = ((pdir->st_mode & S_IWGRP) != 0); else pdirwok = ((pdir->st_mode & S_IWOTH) != 0); } /* 'a': can APPE to file */ if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode)) CPUTC('a', fd); /* 'c': can create or append to files in directory */ if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode)) CPUTC('c', fd); /* 'd': can delete file or directory */ if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) { int candel; candel = 1; if (S_ISDIR(fe->stat->st_mode)) { DIR *dirp; struct dirent *dp; if ((dirp = opendir(fe->display)) == NULL) candel = 0; else { while ((dp = readdir(dirp)) != NULL) { if (ISDOTDIR(dp->d_name) || ISDOTDOTDIR(dp->d_name)) continue; candel = 0; break; } closedir(dirp); } } if (candel) CPUTC('d', fd); } /* 'e': can enter directory */ if (xok && S_ISDIR(fe->stat->st_mode)) CPUTC('e', fd); /* 'f': can rename file or directory */ if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) CPUTC('f', fd); /* 'l': can list directory */ if (rok && xok && S_ISDIR(fe->stat->st_mode)) CPUTC('l', fd); /* 'm': can create directory */ if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode)) CPUTC('m', fd); /* 'p': can remove files in directory */ if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode)) CPUTC('p', fd); /* 'r': can RETR file */ if (rok && S_ISREG(fe->stat->st_mode)) CPUTC('r', fd); /* 'w': can STOR file */ if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode)) CPUTC('w', fd); CPUTC(';', fd); } static void fact_size(const char *fact, FILE *fd, factelem *fe) { if (S_ISREG(fe->stat->st_mode)) cprintf(fd, "%s=" LLF ";", fact, (LLT)fe->stat->st_size); } static void fact_type(const char *fact, FILE *fd, factelem *fe) { cprintf(fd, "%s=", fact); switch (fe->stat->st_mode & S_IFMT) { case S_IFDIR: if (fe->flags & FE_MLSD) { if ((fe->flags & FE_ISCURDIR) || ISDOTDIR(fe->display)) cprintf(fd, "cdir"); else if (ISDOTDOTDIR(fe->display)) cprintf(fd, "pdir"); else cprintf(fd, "dir"); } else { cprintf(fd, "dir"); } break; case S_IFREG: cprintf(fd, "file"); break; case S_IFIFO: cprintf(fd, "OS.unix=fifo"); break; case S_IFLNK: /* XXX: probably a NO-OP with stat() */ cprintf(fd, "OS.unix=slink"); break; case S_IFSOCK: cprintf(fd, "OS.unix=socket"); break; case S_IFBLK: case S_IFCHR: cprintf(fd, "OS.unix=%s-%d/%d", S_ISBLK(fe->stat->st_mode) ? "blk" : "chr", major(fe->stat->st_rdev), minor(fe->stat->st_rdev)); break; default: cprintf(fd, "OS.unix=UNKNOWN(0%o)", fe->stat->st_mode & S_IFMT); break; } CPUTC(';', fd); } static void fact_unique(const char *fact, FILE *fd, factelem *fe) { char obuf[(sizeof(dev_t) + sizeof(ino_t) + 2) * 4 / 3 + 2]; char tbuf[sizeof(dev_t) + sizeof(ino_t)]; memcpy(tbuf, (char *)&(fe->stat->st_dev), sizeof(dev_t)); memcpy(tbuf + sizeof(dev_t), (char *)&(fe->stat->st_ino), sizeof(ino_t)); base64_encode(tbuf, sizeof(dev_t) + sizeof(ino_t), obuf, 1); cprintf(fd, "%s=%s;", fact, obuf); } static int matchgroup(gid_t gid) { int i; for (i = 0; i < gidcount; i++) if (gid == gidlist[i]) return(1); return (0); } static void mlsname(FILE *fp, factelem *fe) { char realfile[MAXPATHLEN]; int i, userf = 0; for (i = 0; i < FACTTABSIZE; i++) { if (facttab[i].enabled) (facttab[i].display)(facttab[i].name, fp, fe); } if ((fe->flags & FE_MLSD) && !(fe->flags & FE_ISCURDIR) && !ISDOTDIR(fe->display)) { /* if MLSD and not "." entry, display as-is */ userf = 0; } else { /* if MLST, or MLSD and "." entry, realpath(3) it */ if (realpath(fe->display, realfile) != NULL) userf = 1; } cprintf(fp, " %s\r\n", userf ? realfile : fe->display); } static void replydirname(const char *name, const char *message) { char *p, *ep; char npath[MAXPATHLEN * 2]; p = npath; ep = &npath[sizeof(npath) - 1]; while (*name) { if (*name == '"') { if (ep - p < 2) break; *p++ = *name++; *p++ = '"'; } else { if (ep - p < 1) break; *p++ = *name++; } } *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 = (unsigned long)cp - (unsigned 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) - ((unsigned long)cq - (unsigned 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; }