Add the -k option to provide a chdir() capability to this directory instead of a chroot(). It permits following symlinks above current tree, while still preventing paths containing '/../'. diff -ur tftp-hpa-0.48.orig/tftpd/tftpd.c tftp-hpa-0.48/tftpd/tftpd.c --- tftp-hpa-0.48.orig/tftpd/tftpd.c 2007-01-31 00:51:05 +0100 +++ tftp-hpa-0.48/tftpd/tftpd.c 2007-02-14 15:23:06 +0100 @@ -103,6 +103,7 @@ const char **dirs; int secure = 0; +int keep_dir = 0; int cancreate = 0; int unixperms = 0; int portrange = 0; @@ -307,7 +308,7 @@ srand(time(NULL) ^ getpid()); - while ((c = getopt(argc, argv, "cspvVlLa:B:u:U:r:t:T:R:m:")) != -1) + while ((c = getopt(argc, argv, "cspvVlLk:a:B:u:U:r:t:T:R:m:")) != -1) switch (c) { case 'c': cancreate = 1; @@ -331,6 +332,13 @@ case 't': waittime = atoi(optarg); break; + case 'k': + keep_dir = 1; + if (chdir(optarg)) { + syslog(LOG_ERR, "%s: %m", dirs[0]); + exit(EX_NOINPUT); + } + break; case 'B': { char *vp; @@ -409,6 +417,14 @@ break; } + if (keep_dir && optind > argc) { + if (chdir(dirs[optind])) { + syslog(LOG_ERR, "%s: %m", dirs[0]); + exit(EX_NOINPUT); + } + optind++; + } + dirs = xmalloc((argc-optind+1)*sizeof(char *)); for ( ndirs = 0 ; optind != argc ; optind++ ) dirs[ndirs++] = argv[optind]; @@ -504,7 +520,7 @@ /* Daemonize this process */ /* Note: when running in secure mode (-s), we must not chroot, since we are already in the proper directory. */ - if (!nodaemon && daemon(secure, 0) < 0) { + if (!nodaemon && daemon(secure || keep_dir, 0) < 0) { syslog(LOG_ERR, "cannot daemonize: %m"); exit(EX_OSERR); } @@ -1086,7 +1102,7 @@ *errmsg = NULL; if (!secure) { - if (*filename != '/') { + if ((keep_dir && *filename == '.') || (!keep_dir && *filename != '/')) { *errmsg = "Only absolute filenames allowed"; return (EACCESS); } @@ -1104,12 +1120,14 @@ } } - for (dirp = dirs; *dirp; dirp++) - if (strncmp(filename, *dirp, strlen(*dirp)) == 0) - break; - if (*dirp==0 && dirp!=dirs) { - *errmsg = "Forbidden directory"; - return (EACCESS); + if (!keep_dir) { + for (dirp = dirs; *dirp; dirp++) + if (strncmp(filename, *dirp, strlen(*dirp)) == 0) + break; + if (*dirp==0 && dirp!=dirs) { + *errmsg = "Forbidden directory"; + return (EACCESS); + } } }