On Tue, Oct 24 2017, Jeremie Courreges-Anglas <j...@wxcvbn.org> wrote: > On Mon, Oct 23 2017, Jan Klemkow <j.klem...@wemelug.de> wrote: >> On Sun, Oct 22, 2017 at 09:32:54PM +0000, Jeremie Courreges-Anglas wrote: >>> On Sat, Oct 21 2017, Jan Klemkow <j.klem...@wemelug.de> wrote: >>> > On Fri, Oct 20, 2017 at 12:04:41PM +0000, Jeremie Courreges-Anglas wrote: >>> >> On Fri, Oct 20 2017, Sebastien Marie <sema...@online.fr> wrote: >>> >> > On Thu, Oct 19, 2017 at 08:58:12PM +0200, Jan Klemkow wrote: >>> >> >> + char nfilename[PATH_MAX]; >>> >> >> + >>> >> >> + snprintf(nfilename, sizeof nfilename, "%s/%s", >>> >> >> + getip(&client->ss), filename); >>> >> > >>> >> > - filename has PATH_MAX length >>> >> > - getip(&client->ss) could have NI_MAXHOST length >>> >> >>> >> INET6_ADDRSTRLEN since getip() calls getnameinfo(NI_NUMERICHOST), but >>> >> your point stands. >>> >> >>> >> > so nfilename could be larger than PATH_MAX (sizeof nfilename). >>> >> > >>> >> > I assume the return of snprintf() need to be checked. if truncation >>> >> > occured, a warning should be issued and nfilename discarded (just >>> >> > calling tftp_open(client, filename)) ? >>> >> >>> >> I think we should refuse the request if truncated. >>> > >>> > done >>> > >>> >> >> + if (access(nfilename, R_OK) == 0) >>> >> >> + tftp_open(client, nfilename); >>> >> >> + else >>> >> >> + tftp_open(client, filename); >>> >> >> + } >>> >> >>> >> Here we look up the same file in both the client-specific subdirectory >>> >> and the default directory. Should we instead look only in the >>> >> client-specific directory if the latter exist? >>> > >>> > Common files should be found in the default directory. But, host >>> > specific files could be overwritten if they exist in the subdirectory. >>> >>> I think it would be better to perform those access tests in >>> validate_access(); logic in a single place, and a less quirky handling >>> of SEEDPATH. Also the test done should probably depend on the type >>> (read, write) of the request. Retrying with the default directory may >>> make sense in read mode, but maybe not in write (and -c, create) mode? >>> >>> The updated diff below implements such semantics, but in >>> validate_access(). While here, >>> - improve diagnostic if both -i and -r are given; usage() doesn't show >>> the conflict >>> - also test snprintf return value against -1, as spotted by semarie@ >>> >>> Maybe we should add a mention in the manpage that the client can >>> "escape" its client-specific directory? (ie /../192.0.2.1/file) >>> >>> The logic is more complicated but I hope it's for good. >> >> I successfully testes jca's diff in my setup and add two lines about >> directory escaping to the manpage. > > I don't think there is a need to expand on security and machines > changing their IP address, especially when you're using TFTP, an > insecure protocol. I just wanted to stress that no enforcement was > done. > > Here's an alternate take at documenting -i, addressing a few issues. It > moves the "no path enforcement" sentence to CAVEATS. I hope you agree > with this move.
At least jmc@ thinks that the -i flag description is a better place. > While here: > - kill .Tn > - the content of the previous BUGS section doesn't look like a TFTP bug, > so CAVEATS looks more appropriate to me I've kept those changes (to be committed seperately). > Feedback & oks welcome. New diff after feedback from jmc@ Index: tftpd.8 =================================================================== RCS file: /d/cvs/src/usr.sbin/tftpd/tftpd.8,v retrieving revision 1.5 diff -u -p -r1.5 tftpd.8 --- tftpd.8 18 Jul 2015 05:32:56 -0000 1.5 +++ tftpd.8 25 Oct 2017 16:48:32 -0000 @@ -37,16 +37,14 @@ .Nd DARPA Trivial File Transfer Protocol daemon .Sh SYNOPSIS .Nm tftpd -.Op Fl 46cdv +.Op Fl 46cdiv .Op Fl l Ar address .Op Fl p Ar port .Op Fl r Ar socket .Ar directory .Sh DESCRIPTION .Nm -is a server which implements the -.Tn DARPA -Trivial File Transfer Protocol. +is a server which implements the DARPA Trivial File Transfer Protocol. .Pp The use of .Xr tftp 1 @@ -100,6 +98,15 @@ If this option is specified, .Nm will run in the foreground and log the client IP, type of request, and filename to stderr. +.It Fl i +Look up the requested path in the subdirectory named after the +client's IP address. +For read requests, if the file is not found, +.Nm +falls back on the requested path. +Note that no attempt is made to limit the client to its subdirectory. +This option cannot be combined with +.Fl r . .It Fl l Ar address Listen on the specified address. By default @@ -126,6 +133,8 @@ before the TFTP request will continue. By default .Nm does not use filename rewriting. +This option cannot be combined with +.Fl i . .It Fl v Log the client IP, type of request, and filename. .It Ar directory @@ -151,6 +160,6 @@ and appeared in It was rewritten for .Ox 5.2 as a persistent non-blocking daemon. -.Sh BUGS +.Sh CAVEATS Many TFTP clients will not transfer files over 16744448 octets .Pq 32767 blocks . Index: tftpd.c =================================================================== RCS file: /d/cvs/src/usr.sbin/tftpd/tftpd.c,v retrieving revision 1.39 diff -u -p -r1.39 tftpd.c --- tftpd.c 26 May 2017 17:38:46 -0000 1.39 +++ tftpd.c 24 Oct 2017 17:37:46 -0000 @@ -282,7 +282,7 @@ __dead void usage(void) { extern char *__progname; - fprintf(stderr, "usage: %s [-46cdv] [-l address] [-p port] [-r socket]" + fprintf(stderr, "usage: %s [-46cdiv] [-l address] [-p port] [-r socket]" " directory\n", __progname); exit(1); } @@ -290,6 +290,7 @@ usage(void) int cancreate = 0; int verbose = 0; int debug = 0; +int iflag = 0; int main(int argc, char *argv[]) @@ -307,7 +308,7 @@ main(int argc, char *argv[]) int family = AF_UNSPEC; int devnull = -1; - while ((c = getopt(argc, argv, "46cdl:p:r:v")) != -1) { + while ((c = getopt(argc, argv, "46cdil:p:r:v")) != -1) { switch (c) { case '4': family = AF_INET; @@ -321,6 +322,11 @@ main(int argc, char *argv[]) case 'd': verbose = debug = 1; break; + case 'i': + if (rewrite != NULL) + errx(1, "options -i and -r are incompatible"); + iflag = 1; + break; case 'l': addr = optarg; break; @@ -328,6 +334,8 @@ main(int argc, char *argv[]) port = optarg; break; case 'r': + if (iflag == 1) + errx(1, "options -i and -r are incompatible"); rewrite = optarg; break; case 'v': @@ -949,15 +957,16 @@ error: * given as we have no login directory. */ int -validate_access(struct tftp_client *client, const char *filename) +validate_access(struct tftp_client *client, const char *requested) { int mode = client->opcode; struct opt_client *options = client->options; struct stat stbuf; int fd, wmode; - const char *errstr; + const char *errstr, *filename; + char rewritten[PATH_MAX]; - if (strcmp(filename, SEEDPATH) == 0) { + if (strcmp(requested, SEEDPATH) == 0) { char *buf; if (mode != RRQ) return (EACCESS); @@ -971,15 +980,39 @@ validate_access(struct tftp_client *clie return (0); } + if (iflag) { + int ret; + + /* + * In -i mode, look in the directory named after the + * client address. + */ + ret = snprintf(rewritten, sizeof(rewritten), "%s/%s", + getip(&client->ss), requested); + if (ret == -1 || ret >= sizeof(rewritten)) + return (ENAMETOOLONG + 100); + filename = rewritten; + } else { +retryread: + filename = requested; + } + /* * We use a different permissions scheme if `cancreate' is * set. */ wmode = O_TRUNC; if (stat(filename, &stbuf) < 0) { - if (!cancreate) + if (!cancreate) { + /* + * In -i mode, retry failed read requests from + * the root directory. + */ + if (mode == RRQ && errno == ENOENT && + filename == rewritten) + goto retryread; return (errno == ENOENT ? ENOTFOUND : EACCESS); - else { + } else { if ((errno == ENOENT) && (mode != RRQ)) wmode |= O_CREAT; else -- jca | PGP : 0x1524E7EE / 5135 92C1 AD36 5293 2BDF DDCC 0DFA 74AE 1524 E7EE