On Wed, Oct 25, 2017 at 04:54:01PM +0000, Jeremie Courreges-Anglas wrote: > 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@
I tested this diff. It looks fine for me. Thanks, Jan > 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