=================================================================== RCS file: /cvs/mandoc/mandocdb.c,v retrieving revision 1.265 retrieving revision 1.266 diff -u -p -r1.265 -r1.266 --- mandoc/mandocdb.c 2020/01/26 11:16:47 1.265 +++ mandoc/mandocdb.c 2020/01/26 21:25:41 1.266 @@ -1,4 +1,4 @@ -/* $Id: mandocdb.c,v 1.265 2020/01/26 11:16:47 schwarze Exp $ */ +/* $Id: mandocdb.c,v 1.266 2020/01/26 21:25:41 schwarze Exp $ */ /* * Copyright (c) 2011, 2012 Kristaps Dzonsons * Copyright (c) 2011-2020 Ingo Schwarze @@ -778,17 +778,17 @@ treescan(void) * See treescan() for the fts(3) version of this. */ static void -filescan(const char *file) +filescan(const char *infile) { - char buf[PATH_MAX]; struct stat st; struct mlink *mlink; - char *p, *start; + char *linkfile, *p, *realdir, *start, *usefile; + size_t realdir_len; assert(use_all); - if (strncmp(file, "./", 2) == 0) - file += 2; + if (strncmp(infile, "./", 2) == 0) + infile += 2; /* * We have to do lstat(2) before realpath(3) loses @@ -797,13 +797,13 @@ filescan(const char *file) * we want to use the orginal file name, while for * regular files, we want to use the real path. */ - if (lstat(file, &st) == -1) { + if (lstat(infile, &st) == -1) { exitcode = (int)MANDOCLEVEL_BADARG; - say(file, "&lstat"); + say(infile, "&lstat"); return; } else if (S_ISREG(st.st_mode) == 0 && S_ISLNK(st.st_mode) == 0) { exitcode = (int)MANDOCLEVEL_BADARG; - say(file, "Not a regular file"); + say(infile, "Not a regular file"); return; } @@ -811,23 +811,24 @@ filescan(const char *file) * We have to resolve the file name to the real path * in any case for the base directory check. */ - if (realpath(file, buf) == NULL) { + if ((usefile = realpath(infile, NULL)) == NULL) { exitcode = (int)MANDOCLEVEL_BADARG; - say(file, "&realpath"); + say(infile, "&realpath"); return; } if (op == OP_TEST) - start = buf; - else if (strncmp(buf, basedir, basedir_len) == 0) - start = buf + basedir_len; + start = usefile; + else if (strncmp(usefile, basedir, basedir_len) == 0) + start = usefile + basedir_len; #ifdef HOMEBREWDIR - else if (strncmp(buf, HOMEBREWDIR, strlen(HOMEBREWDIR)) == 0) - start = buf; + else if (strncmp(usefile, HOMEBREWDIR, strlen(HOMEBREWDIR)) == 0) + start = usefile; #endif else { exitcode = (int)MANDOCLEVEL_BADARG; - say("", "%s: outside base directory", buf); + say("", "%s: outside base directory", infile); + free(usefile); return; } @@ -835,32 +836,80 @@ filescan(const char *file) * Now we are sure the file is inside our tree. * If it is a symbolic link, ignore the real path * and use the original name. - * This implies passing stuff like "cat1/../man1/foo.1" - * on the command line won't work. So don't do that. - * Note the stat(2) can still fail if the link target - * doesn't exist. */ - if (S_ISLNK(st.st_mode)) { - if (stat(buf, &st) == -1) { + do { + if (S_ISLNK(st.st_mode) == 0) + break; + + /* + * Some implementations of realpath(3) may succeed + * even if the target of the link does not exist, + * so check again for extra safety. + */ + if (stat(usefile, &st) == -1) { exitcode = (int)MANDOCLEVEL_BADARG; - say(file, "&stat"); + say(infile, "&stat"); + free(usefile); return; } - if (strlcpy(buf, file, sizeof(buf)) >= sizeof(buf)) { - say(file, "Filename too long"); - return; + linkfile = mandoc_strdup(infile); + if (op == OP_TEST) { + free(usefile); + start = usefile = linkfile; + break; } - start = buf; - if (op != OP_TEST && strncmp(buf, basedir, basedir_len) == 0) - start += basedir_len; - } + if (strncmp(infile, basedir, basedir_len) == 0) { + free(usefile); + usefile = linkfile; + start = usefile + basedir_len; + break; + } + /* + * This symbolic link points into the basedir + * from the outside. Let's see whether any of + * the parent directories resolve to the basedir. + */ + p = strchr(linkfile, '\0'); + do { + while (*--p != '/') + continue; + *p = '\0'; + if ((realdir = realpath(linkfile, NULL)) == NULL) { + exitcode = (int)MANDOCLEVEL_BADARG; + say(infile, "&realpath"); + free(linkfile); + free(usefile); + return; + } + realdir_len = strlen(realdir) + 1; + free(realdir); + *p = '/'; + } while (realdir_len > basedir_len); + + /* + * If one of the directories resolves to the basedir, + * use the rest of the original name. + * Otherwise, the best we can do + * is to use the filename pointed to. + */ + if (realdir_len == basedir_len) { + free(usefile); + usefile = linkfile; + start = p + 1; + } else { + free(linkfile); + start = usefile + basedir_len; + } + } while (/* CONSTCOND */ 0); + mlink = mandoc_calloc(1, sizeof(struct mlink)); mlink->dform = FORM_NONE; if (strlcpy(mlink->file, start, sizeof(mlink->file)) >= sizeof(mlink->file)) { say(start, "Filename too long"); free(mlink); + free(usefile); return; } @@ -869,13 +918,13 @@ filescan(const char *file) * but outside our tree, guess the base directory. */ - if (op == OP_TEST || (start == buf && *start == '/')) { - if (strncmp(buf, "man/", 4) == 0) - start = buf + 4; - else if ((start = strstr(buf, "/man/")) != NULL) + if (op == OP_TEST || (start == usefile && *start == '/')) { + if (strncmp(usefile, "man/", 4) == 0) + start = usefile + 4; + else if ((start = strstr(usefile, "/man/")) != NULL) start += 5; else - start = buf; + start = usefile; } /* @@ -925,6 +974,7 @@ filescan(const char *file) *p = '\0'; } mlink_add(mlink, &st); + free(usefile); } static void