xref: /openbsd-src/usr.sbin/tftpd/tftpd.c (revision 479c151d3429b7cfa6228ee428d945620629789d)
1*479c151dSjsg /*	$OpenBSD: tftpd.c,v 1.52 2024/09/20 02:00:46 jsg Exp $	*/
28871c557Sdlg 
38871c557Sdlg /*
48871c557Sdlg  * Copyright (c) 2012 David Gwynne <dlg@uq.edu.au>
58871c557Sdlg  *
68871c557Sdlg  * Permission to use, copy, modify, and distribute this software for any
78871c557Sdlg  * purpose with or without fee is hereby granted, provided that the above
88871c557Sdlg  * copyright notice and this permission notice appear in all copies.
98871c557Sdlg  *
108871c557Sdlg  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
118871c557Sdlg  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
128871c557Sdlg  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
138871c557Sdlg  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
148871c557Sdlg  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
158871c557Sdlg  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
168871c557Sdlg  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
178871c557Sdlg  */
188871c557Sdlg 
198871c557Sdlg /*
208871c557Sdlg  * Copyright (c) 1983 Regents of the University of California.
218871c557Sdlg  * All rights reserved.
228871c557Sdlg  *
238871c557Sdlg  * Redistribution and use in source and binary forms, with or without
248871c557Sdlg  * modification, are permitted provided that the following conditions
258871c557Sdlg  * are met:
268871c557Sdlg  * 1. Redistributions of source code must retain the above copyright
278871c557Sdlg  *    notice, this list of conditions and the following disclaimer.
288871c557Sdlg  * 2. Redistributions in binary form must reproduce the above copyright
298871c557Sdlg  *    notice, this list of conditions and the following disclaimer in the
308871c557Sdlg  *    documentation and/or other materials provided with the distribution.
318871c557Sdlg  * 3. Neither the name of the University nor the names of its contributors
328871c557Sdlg  *    may be used to endorse or promote products derived from this software
338871c557Sdlg  *    without specific prior written permission.
348871c557Sdlg  *
358871c557Sdlg  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
368871c557Sdlg  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
378871c557Sdlg  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
388871c557Sdlg  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
398871c557Sdlg  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
408871c557Sdlg  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
418871c557Sdlg  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
428871c557Sdlg  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
438871c557Sdlg  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
448871c557Sdlg  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
458871c557Sdlg  * SUCH DAMAGE.
468871c557Sdlg  */
478871c557Sdlg 
488871c557Sdlg /*
498871c557Sdlg  * Trivial file transfer protocol server.
508871c557Sdlg  *
518871c557Sdlg  * This version is based on src/libexec/tftpd which includes many
528871c557Sdlg  * modifications by Jim Guyton <guyton@rand-unix>.
538871c557Sdlg  *
548871c557Sdlg  * It was restructured to be a persistent event driven daemon
558871c557Sdlg  * supporting concurrent connections by dlg for use at the University
568871c557Sdlg  * of Queensland in the Faculty of Engineering Architecture and
578871c557Sdlg  * Information Technology.
588871c557Sdlg  */
598871c557Sdlg 
6015a98d3dSderaadt #include <sys/types.h>
611faad7bfSnicm #include <sys/queue.h>
628871c557Sdlg #include <sys/socket.h>
638871c557Sdlg #include <sys/stat.h>
648871c557Sdlg #include <sys/uio.h>
658871c557Sdlg #include <sys/un.h>
668871c557Sdlg 
678871c557Sdlg #include <netinet/in.h>
688871c557Sdlg #include <arpa/inet.h>
698871c557Sdlg #include <arpa/tftp.h>
708871c557Sdlg #include <netdb.h>
718871c557Sdlg 
728871c557Sdlg #include <err.h>
738871c557Sdlg #include <ctype.h>
748871c557Sdlg #include <errno.h>
758871c557Sdlg #include <event.h>
768871c557Sdlg #include <fcntl.h>
77eaf6a821Sjca #include <paths.h>
788871c557Sdlg #include <poll.h>
798871c557Sdlg #include <pwd.h>
808871c557Sdlg #include <stdio.h>
818871c557Sdlg #include <stdlib.h>
828871c557Sdlg #include <string.h>
838871c557Sdlg #include <stdarg.h>
848871c557Sdlg #include <syslog.h>
858871c557Sdlg #include <unistd.h>
86b9fc9a72Sderaadt #include <limits.h>
878871c557Sdlg #include <vis.h>
888871c557Sdlg 
898871c557Sdlg #define TIMEOUT		5		/* packet rexmt timeout */
908871c557Sdlg #define TIMEOUT_MIN	1		/* minimal packet rexmt timeout */
918871c557Sdlg #define TIMEOUT_MAX	255		/* maximal packet rexmt timeout */
928871c557Sdlg 
938871c557Sdlg #define RETRIES		5
948871c557Sdlg 
954a5fd927Smcbride #define SEEDPATH	"/etc/random.seed"
964a5fd927Smcbride 
978871c557Sdlg struct formats;
988871c557Sdlg 
998871c557Sdlg enum opt_enum {
1008871c557Sdlg 	OPT_TSIZE = 0,
1018871c557Sdlg 	OPT_TIMEOUT,
1028871c557Sdlg 	OPT_BLKSIZE,
1038871c557Sdlg 	NOPT
1048871c557Sdlg };
1058871c557Sdlg 
1068871c557Sdlg static char *opt_names[] = {
1078871c557Sdlg 	"tsize",
1088871c557Sdlg 	"timeout",
1098871c557Sdlg 	"blksize"
1108871c557Sdlg };
1118871c557Sdlg 
1128871c557Sdlg struct opt_client {
1138871c557Sdlg 	char *o_request;
1148871c557Sdlg 	long long o_reply;
1158871c557Sdlg };
1168871c557Sdlg 
1178871c557Sdlg 
1188871c557Sdlg struct tftp_server {
1198871c557Sdlg 	struct event ev;
1208871c557Sdlg 	TAILQ_ENTRY(tftp_server) entry;
1218871c557Sdlg 	int s;
1228871c557Sdlg };
1238871c557Sdlg 
1248871c557Sdlg TAILQ_HEAD(, tftp_server) tftp_servers;
1258871c557Sdlg 
1268871c557Sdlg struct tftp_client {
1278871c557Sdlg 	char buf[SEGSIZE_MAX + 4];
1288871c557Sdlg 	struct event sev;
1298871c557Sdlg 	struct sockaddr_storage ss;
1308871c557Sdlg 
1318871c557Sdlg 	struct timeval tv;
1328871c557Sdlg 
1338871c557Sdlg 	TAILQ_ENTRY(tftp_client) entry;
1348871c557Sdlg 
1358871c557Sdlg 	struct opt_client *options;
1368871c557Sdlg 
1378871c557Sdlg 	size_t segment_size;
1388871c557Sdlg 	size_t packet_size;
1398871c557Sdlg 	size_t buflen;
1408871c557Sdlg 
1418871c557Sdlg 	FILE *file;
1428871c557Sdlg 	int (*fgetc)(struct tftp_client *);
1438871c557Sdlg 	int (*fputc)(struct tftp_client *, int);
1448871c557Sdlg 
1458871c557Sdlg 	u_int retries;
1468871c557Sdlg 	u_int16_t block;
1478871c557Sdlg 
1488871c557Sdlg 	int opcode;
1498871c557Sdlg 	int newline;
1508871c557Sdlg 
1518871c557Sdlg 	int sock;
1528871c557Sdlg };
1538871c557Sdlg 
1548871c557Sdlg __dead void	usage(void);
1558871c557Sdlg const char	*getip(void *);
1568972ba64Sjca int		rdaemon(int);
1578871c557Sdlg 
1588871c557Sdlg void		rewrite_connect(const char *);
1598871c557Sdlg void		rewrite_events(void);
1608871c557Sdlg void		rewrite_map(struct tftp_client *, const char *);
1618871c557Sdlg void		rewrite_req(int, short, void *);
1628871c557Sdlg void		rewrite_res(int, short, void *);
1638871c557Sdlg 
1648871c557Sdlg int		tftpd_listen(const char *, const char *, int);
1658871c557Sdlg void		tftpd_events(void);
1668871c557Sdlg void		tftpd_recv(int, short, void *);
1678871c557Sdlg int		retry(struct tftp_client *);
1681e449da8Sdlg int		tftp_flush(struct tftp_client *);
1698871c557Sdlg 
1708871c557Sdlg void		tftp(struct tftp_client *, struct tftphdr *, size_t);
1718871c557Sdlg void		tftp_open(struct tftp_client *, const char *);
1728871c557Sdlg void		nak(struct tftp_client *, int);
173eca1b59cSdlg int		oack(struct tftp_client *);
1748871c557Sdlg void		oack_done(int, short, void *);
1758871c557Sdlg 
1768871c557Sdlg void		sendfile(struct tftp_client *);
1778871c557Sdlg void		recvfile(struct tftp_client *);
1788871c557Sdlg int		fget_octet(struct tftp_client *);
1798871c557Sdlg int		fput_octet(struct tftp_client *, int);
1808871c557Sdlg int		fget_netascii(struct tftp_client *);
1818871c557Sdlg int		fput_netascii(struct tftp_client *, int);
1828871c557Sdlg void		file_read(struct tftp_client *);
18380a17660Sdlg void		tftp_send(struct tftp_client *);
1848871c557Sdlg int		tftp_wrq_ack_packet(struct tftp_client *);
1858871c557Sdlg void		tftp_rrq_ack(int, short, void *);
1868871c557Sdlg void		tftp_wrq_ack(struct tftp_client *client);
1878871c557Sdlg void		tftp_wrq(int, short, void *);
1886ac42c1aSdlg void		tftp_wrq_end(int, short, void *);
1898871c557Sdlg 
1908871c557Sdlg int		parse_options(struct tftp_client *, char *, size_t,
1918871c557Sdlg 		    struct opt_client *);
1928871c557Sdlg int		validate_access(struct tftp_client *, const char *);
1938871c557Sdlg 
194d2c62849Sderaadt struct tftp_client *
195d2c62849Sderaadt 		client_alloc(void);
196d2c62849Sderaadt void		client_free(struct tftp_client *client);
197d2c62849Sderaadt 
1988871c557Sdlg struct formats {
1998871c557Sdlg 	const char	*f_mode;
2008871c557Sdlg 	int (*f_getc)(struct tftp_client *);
2018871c557Sdlg 	int (*f_putc)(struct tftp_client *, int);
2028871c557Sdlg } formats[] = {
2038871c557Sdlg 	{ "octet",	fget_octet,	fput_octet },
2048871c557Sdlg 	{ "netascii",	fget_netascii,	fput_netascii },
2058871c557Sdlg 	{ NULL,		NULL }
2068871c557Sdlg };
2078871c557Sdlg 
2088871c557Sdlg struct errmsg {
2098871c557Sdlg 	int		 e_code;
2108871c557Sdlg 	const char	*e_msg;
2118871c557Sdlg } errmsgs[] = {
2128871c557Sdlg 	{ EUNDEF,	"Undefined error code" },
2138871c557Sdlg 	{ ENOTFOUND,	"File not found" },
2148871c557Sdlg 	{ EACCESS,	"Access violation" },
2158871c557Sdlg 	{ ENOSPACE,	"Disk full or allocation exceeded" },
2168871c557Sdlg 	{ EBADOP,	"Illegal TFTP operation" },
2178871c557Sdlg 	{ EBADID,	"Unknown transfer ID" },
2188871c557Sdlg 	{ EEXISTS,	"File already exists" },
2198871c557Sdlg 	{ ENOUSER,	"No such user" },
2208871c557Sdlg 	{ EOPTNEG,	"Option negotiation failed" },
2218871c557Sdlg 	{ -1,		NULL }
2228871c557Sdlg };
2238871c557Sdlg 
2248871c557Sdlg struct loggers {
2250e7810bdSflorian 	__dead void (*err)(int, const char *, ...)
2260e7810bdSflorian 	    __attribute__((__format__ (printf, 2, 3)));
2270e7810bdSflorian 	__dead void (*errx)(int, const char *, ...)
2280e7810bdSflorian 	    __attribute__((__format__ (printf, 2, 3)));
2290e7810bdSflorian 	void (*warn)(const char *, ...)
2300e7810bdSflorian 	    __attribute__((__format__ (printf, 1, 2)));
2310e7810bdSflorian 	void (*warnx)(const char *, ...)
2320e7810bdSflorian 	    __attribute__((__format__ (printf, 1, 2)));
2330e7810bdSflorian 	void (*info)(const char *, ...)
2340e7810bdSflorian 	    __attribute__((__format__ (printf, 1, 2)));
2350e7810bdSflorian 	void (*debug)(const char *, ...)
2360e7810bdSflorian 	    __attribute__((__format__ (printf, 1, 2)));
2378871c557Sdlg };
2388871c557Sdlg 
2398871c557Sdlg const struct loggers conslogger = {
2408871c557Sdlg 	err,
2418871c557Sdlg 	errx,
2428871c557Sdlg 	warn,
2438871c557Sdlg 	warnx,
2440e7810bdSflorian 	warnx, /* info */
2450e7810bdSflorian 	warnx /* debug */
2468871c557Sdlg };
2478871c557Sdlg 
2480e7810bdSflorian __dead void	syslog_err(int, const char *, ...)
2490e7810bdSflorian 		    __attribute__((__format__ (printf, 2, 3)));
2500e7810bdSflorian __dead void	syslog_errx(int, const char *, ...)
2510e7810bdSflorian 		    __attribute__((__format__ (printf, 2, 3)));
2520e7810bdSflorian void		syslog_warn(const char *, ...)
2530e7810bdSflorian 		    __attribute__((__format__ (printf, 1, 2)));
2540e7810bdSflorian void		syslog_warnx(const char *, ...)
2550e7810bdSflorian 		    __attribute__((__format__ (printf, 1, 2)));
2560e7810bdSflorian void		syslog_info(const char *, ...)
2570e7810bdSflorian 		    __attribute__((__format__ (printf, 1, 2)));
2580e7810bdSflorian void		syslog_debug(const char *, ...)
2590e7810bdSflorian 		    __attribute__((__format__ (printf, 1, 2)));
2600e7810bdSflorian void		syslog_vstrerror(int, int, const char *, va_list)
2610e7810bdSflorian 		    __attribute__((__format__ (printf, 3, 0)));
2628871c557Sdlg 
2638871c557Sdlg const struct loggers syslogger = {
2648871c557Sdlg 	syslog_err,
2658871c557Sdlg 	syslog_errx,
2668871c557Sdlg 	syslog_warn,
2678871c557Sdlg 	syslog_warnx,
2688871c557Sdlg 	syslog_info,
2690e7810bdSflorian 	syslog_debug
2708871c557Sdlg };
2718871c557Sdlg 
2728871c557Sdlg const struct loggers *logger = &conslogger;
2738871c557Sdlg 
2748871c557Sdlg #define lerr(_e, _f...) logger->err((_e), _f)
2758871c557Sdlg #define lerrx(_e, _f...) logger->errx((_e), _f)
2768871c557Sdlg #define lwarn(_f...) logger->warn(_f)
2778871c557Sdlg #define lwarnx(_f...) logger->warnx(_f)
2788871c557Sdlg #define linfo(_f...) logger->info(_f)
2790e7810bdSflorian #define ldebug(_f...) logger->debug(_f)
2808871c557Sdlg 
2818871c557Sdlg __dead void
2828871c557Sdlg usage(void)
2838871c557Sdlg {
2848871c557Sdlg 	extern char *__progname;
285a5c6d06aSkn 	fprintf(stderr, "usage: %s [-46cdivw] [-l address] [-p port] [-r socket]"
2868871c557Sdlg 	    " directory\n", __progname);
2878871c557Sdlg 	exit(1);
2888871c557Sdlg }
2898871c557Sdlg 
2908871c557Sdlg int		  cancreate = 0;
291a5c6d06aSkn int		  canwrite = 0;
2928871c557Sdlg int		  verbose = 0;
2930e7810bdSflorian int		  debug = 0;
294f923cd8eSjca int		  iflag = 0;
2958871c557Sdlg 
2968871c557Sdlg int
2978871c557Sdlg main(int argc, char *argv[])
2988871c557Sdlg {
2998871c557Sdlg 	extern char *__progname;
3008871c557Sdlg 
301769bdeccSdlg 	int		 c;
3028871c557Sdlg 	struct passwd	*pw;
3038871c557Sdlg 
3048871c557Sdlg 	char *dir = NULL;
3058871c557Sdlg 	char *rewrite = NULL;
3068871c557Sdlg 
3078871c557Sdlg 	char *addr = NULL;
3088871c557Sdlg 	char *port = "tftp";
3098871c557Sdlg 	int family = AF_UNSPEC;
310eaf6a821Sjca 	int devnull = -1;
3118871c557Sdlg 
312a5c6d06aSkn 	while ((c = getopt(argc, argv, "46cdil:p:r:vw")) != -1) {
3138871c557Sdlg 		switch (c) {
3148871c557Sdlg 		case '4':
3158871c557Sdlg 			family = AF_INET;
3168871c557Sdlg 			break;
3178871c557Sdlg 		case '6':
3188871c557Sdlg 			family = AF_INET6;
3198871c557Sdlg 			break;
3208871c557Sdlg 		case 'c':
321a5c6d06aSkn 			canwrite = cancreate = 1;
3228871c557Sdlg 			break;
3238871c557Sdlg 		case 'd':
3248871c557Sdlg 			verbose = debug = 1;
3258871c557Sdlg 			break;
326f923cd8eSjca 		case 'i':
327f923cd8eSjca 			if (rewrite != NULL)
328f923cd8eSjca 				errx(1, "options -i and -r are incompatible");
329f923cd8eSjca 			iflag = 1;
330f923cd8eSjca 			break;
3318871c557Sdlg 		case 'l':
3328871c557Sdlg 			addr = optarg;
3338871c557Sdlg 			break;
3348871c557Sdlg 		case 'p':
3358871c557Sdlg 			port = optarg;
3368871c557Sdlg 			break;
3378871c557Sdlg 		case 'r':
338f923cd8eSjca 			if (iflag == 1)
339f923cd8eSjca 				errx(1, "options -i and -r are incompatible");
3408871c557Sdlg 			rewrite = optarg;
3418871c557Sdlg 			break;
3428871c557Sdlg 		case 'v':
3438871c557Sdlg 			verbose = 1;
3448871c557Sdlg 			break;
345a5c6d06aSkn 		case 'w':
346a5c6d06aSkn 			canwrite = 1;
347a5c6d06aSkn 			break;
3488871c557Sdlg 		default:
3498871c557Sdlg 			usage();
3508871c557Sdlg 			/* NOTREACHED */
3518871c557Sdlg 		}
3528871c557Sdlg 	}
3538871c557Sdlg 
3548871c557Sdlg 	argc -= optind;
3558871c557Sdlg 	argv += optind;
3568871c557Sdlg 
3578871c557Sdlg 	if (argc != 1)
3588871c557Sdlg 		usage();
3598871c557Sdlg 
3608871c557Sdlg 	dir = argv[0];
3618871c557Sdlg 
3628871c557Sdlg 	if (geteuid() != 0)
3638871c557Sdlg 		errx(1, "need root privileges");
3648871c557Sdlg 
3658871c557Sdlg 	pw = getpwnam("_tftpd");
3668871c557Sdlg 	if (pw == NULL)
3670146b59bSdlg 		errx(1, "no _tftpd user");
3688871c557Sdlg 
3698871c557Sdlg 	if (!debug) {
3708871c557Sdlg 		openlog(__progname, LOG_PID|LOG_NDELAY, LOG_DAEMON);
3718871c557Sdlg 		tzset();
3728871c557Sdlg 		logger = &syslogger;
373b7041c07Sderaadt 		devnull = open(_PATH_DEVNULL, O_RDWR);
374eaf6a821Sjca 		if (devnull == -1)
375eaf6a821Sjca 			err(1, "open %s", _PATH_DEVNULL);
3768871c557Sdlg 	}
3778871c557Sdlg 
3788871c557Sdlg 	if (rewrite != NULL)
3798871c557Sdlg 		rewrite_connect(rewrite);
3808871c557Sdlg 
3818871c557Sdlg 	tftpd_listen(addr, port, family);
3828871c557Sdlg 
3838871c557Sdlg 	if (chroot(dir))
3848871c557Sdlg 		err(1, "chroot %s", dir);
3858871c557Sdlg 	if (chdir("/"))
3868871c557Sdlg 		err(1, "chdir %s", dir);
3878871c557Sdlg 
3888871c557Sdlg 	/* drop privs */
3898871c557Sdlg 	if (setgroups(1, &pw->pw_gid) ||
3908871c557Sdlg 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
3918871c557Sdlg 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
3928871c557Sdlg 		errx(1, "can't drop privileges");
3938871c557Sdlg 
394eaf6a821Sjca 	if (!debug && rdaemon(devnull) == -1)
395eaf6a821Sjca 		err(1, "unable to daemonize");
396eaf6a821Sjca 
397704f9ac0Skn 	if (cancreate) {
3983b6a0738Sderaadt 		if (pledge("stdio rpath wpath cpath fattr dns inet", NULL) == -1)
399eaf6a821Sjca 			lerr(1, "pledge");
400a5c6d06aSkn 	} else if (canwrite) {
401ad0375c2Skn 		if (pledge("stdio rpath wpath dns inet", NULL) == -1)
402704f9ac0Skn 			lerr(1, "pledge");
403a5c6d06aSkn 	} else {
404a5c6d06aSkn 		if (pledge("stdio rpath dns inet", NULL) == -1)
405a5c6d06aSkn 			lerr(1, "pledge");
406704f9ac0Skn 	}
407a1840542Sderaadt 
4088871c557Sdlg 	event_init();
4098871c557Sdlg 
4108871c557Sdlg 	if (rewrite != NULL)
4118871c557Sdlg 		rewrite_events();
4128871c557Sdlg 
4138871c557Sdlg 	tftpd_events();
4148871c557Sdlg 
4158871c557Sdlg 	event_dispatch();
4168871c557Sdlg 
4178871c557Sdlg 	exit(0);
4188871c557Sdlg }
4198871c557Sdlg 
4208871c557Sdlg struct rewritemap {
4218871c557Sdlg 	struct event wrev;
4228871c557Sdlg 	struct event rdev;
4238871c557Sdlg 	struct evbuffer *wrbuf;
4248871c557Sdlg 	struct evbuffer *rdbuf;
4258871c557Sdlg 
4268871c557Sdlg 	TAILQ_HEAD(, tftp_client) clients;
4278871c557Sdlg 
4288871c557Sdlg 	int s;
4298871c557Sdlg };
4308871c557Sdlg 
4318871c557Sdlg struct rewritemap *rwmap = NULL;
4328871c557Sdlg 
4338871c557Sdlg void
4348871c557Sdlg rewrite_connect(const char *path)
4358871c557Sdlg {
4368871c557Sdlg 	int s;
4378871c557Sdlg 	struct sockaddr_un remote;
4388871c557Sdlg 	size_t len;
4398871c557Sdlg 
4408871c557Sdlg 	rwmap = malloc(sizeof(*rwmap));
4418871c557Sdlg 	if (rwmap == NULL)
4428871c557Sdlg 		err(1, "rewrite event malloc");
4438871c557Sdlg 
4448871c557Sdlg 	rwmap->wrbuf = evbuffer_new();
4458871c557Sdlg 	if (rwmap->wrbuf == NULL)
4468871c557Sdlg 		err(1, "rewrite wrbuf");
4478871c557Sdlg 
4488871c557Sdlg 	rwmap->rdbuf = evbuffer_new();
4498871c557Sdlg 	if (rwmap->rdbuf == NULL)
4508871c557Sdlg 		err(1, "rewrite rdbuf");
4518871c557Sdlg 
4528871c557Sdlg 	TAILQ_INIT(&rwmap->clients);
4538871c557Sdlg 
454e22aa732Sdlg 	s = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
4558871c557Sdlg 	if (s == -1)
4568871c557Sdlg 		err(1, "rewrite socket");
4578871c557Sdlg 
4588871c557Sdlg 	remote.sun_family = AF_UNIX;
4598871c557Sdlg 	len = strlcpy(remote.sun_path, path, sizeof(remote.sun_path));
4608871c557Sdlg 	if (len >= sizeof(remote.sun_path))
4618871c557Sdlg 		errx(1, "rewrite socket path is too long");
4628871c557Sdlg 
4638871c557Sdlg 	len += sizeof(remote.sun_family) + 1;
4648871c557Sdlg 	if (connect(s, (struct sockaddr *)&remote, len) == -1)
4658871c557Sdlg 		err(1, "%s", path);
4668871c557Sdlg 
4678871c557Sdlg 	rwmap->s = s;
4688871c557Sdlg }
4698871c557Sdlg 
4708871c557Sdlg void
4718871c557Sdlg rewrite_events(void)
4728871c557Sdlg {
4738871c557Sdlg 	event_set(&rwmap->wrev, rwmap->s, EV_WRITE, rewrite_req, NULL);
4748871c557Sdlg 	event_set(&rwmap->rdev, rwmap->s, EV_READ | EV_PERSIST, rewrite_res, NULL);
4758871c557Sdlg 	event_add(&rwmap->rdev, NULL);
4768871c557Sdlg }
4778871c557Sdlg 
4788871c557Sdlg void
4798871c557Sdlg rewrite_map(struct tftp_client *client, const char *filename)
4808871c557Sdlg {
4810d71831bSdlg 	char *nicebuf;
4828871c557Sdlg 
4830d71831bSdlg 	if (stravis(&nicebuf, filename, VIS_SAFE|VIS_OCTAL) == -1)
4840d71831bSdlg 		lerr(1, "rwmap stravis");
4858871c557Sdlg 
4868871c557Sdlg 	if (evbuffer_add_printf(rwmap->wrbuf, "%s %s %s\n", getip(&client->ss),
4878871c557Sdlg 	    client->opcode == WRQ ? "write" : "read", nicebuf) == -1)
4888871c557Sdlg 		lerr(1, "rwmap printf");
4898871c557Sdlg 
4900d71831bSdlg 	free(nicebuf);
4910d71831bSdlg 
4928871c557Sdlg 	TAILQ_INSERT_TAIL(&rwmap->clients, client, entry);
4938871c557Sdlg 
4948871c557Sdlg 	event_add(&rwmap->wrev, NULL);
4958871c557Sdlg }
4968871c557Sdlg 
4978871c557Sdlg void
4988871c557Sdlg rewrite_req(int fd, short events, void *arg)
4998871c557Sdlg {
5004f801cb6Sdlg 	if (evbuffer_write(rwmap->wrbuf, fd) == -1) {
5014f801cb6Sdlg 		switch (errno) {
5024f801cb6Sdlg 		case EINTR:
5034f801cb6Sdlg 		case EAGAIN:
5044f801cb6Sdlg 			event_add(&rwmap->wrev, NULL);
5054f801cb6Sdlg 			return;
5064f801cb6Sdlg 		}
5074f801cb6Sdlg 
5084f801cb6Sdlg 		lerr(1, "rewrite socket write");
5094f801cb6Sdlg 	}
5108871c557Sdlg 
5118871c557Sdlg 	if (EVBUFFER_LENGTH(rwmap->wrbuf))
5128871c557Sdlg 		event_add(&rwmap->wrev, NULL);
5138871c557Sdlg }
5148871c557Sdlg 
5158871c557Sdlg void
5168871c557Sdlg rewrite_res(int fd, short events, void *arg)
5178871c557Sdlg {
5188871c557Sdlg 	struct tftp_client *client;
5198871c557Sdlg 	char *filename;
5208871c557Sdlg 	size_t len;
5218871c557Sdlg 
522b9fc9a72Sderaadt 	switch (evbuffer_read(rwmap->rdbuf, fd, PATH_MAX)) {
5234f801cb6Sdlg 	case -1:
5244f801cb6Sdlg 		switch (errno) {
5254f801cb6Sdlg 		case EINTR:
5264f801cb6Sdlg 		case EAGAIN:
5274f801cb6Sdlg 			return;
5284f801cb6Sdlg 		}
5294f801cb6Sdlg 		lerr(1, "rewrite socket read");
5304f801cb6Sdlg 	case 0:
5314f801cb6Sdlg 		lerrx(1, "rewrite socket closed");
5324f801cb6Sdlg 	default:
5334f801cb6Sdlg 		break;
5344f801cb6Sdlg 	}
5358871c557Sdlg 
536769bdeccSdlg 	while ((filename = evbuffer_readln(rwmap->rdbuf, &len,
537769bdeccSdlg 	    EVBUFFER_EOL_LF)) != NULL) {
5388871c557Sdlg 		client = TAILQ_FIRST(&rwmap->clients);
5398871c557Sdlg 		if (client == NULL)
5408871c557Sdlg 			lerrx(1, "unexpected rwmap reply");
5418871c557Sdlg 
5428871c557Sdlg 		TAILQ_REMOVE(&rwmap->clients, client, entry);
5438871c557Sdlg 
5448871c557Sdlg 		tftp_open(client, filename);
5458871c557Sdlg 
5468871c557Sdlg 		free(filename);
547*479c151dSjsg 	}
5488871c557Sdlg }
5498871c557Sdlg 
5508871c557Sdlg int
5518871c557Sdlg tftpd_listen(const char *addr, const char *port, int family)
5528871c557Sdlg {
5538871c557Sdlg 	struct tftp_server *server;
5548871c557Sdlg 
5558871c557Sdlg 	struct addrinfo hints, *res, *res0;
5568871c557Sdlg 	int error;
5578871c557Sdlg 	int s;
5588871c557Sdlg 
559aee3563dSdlg 	int cerrno = EADDRNOTAVAIL;
560aee3563dSdlg 	const char *cause = "getaddrinfo";
5618871c557Sdlg 
562ced7784eSdlg 	int on = 1;
563ced7784eSdlg 
5648871c557Sdlg 	memset(&hints, 0, sizeof(hints));
5658871c557Sdlg 	hints.ai_family = family;
5668871c557Sdlg 	hints.ai_socktype = SOCK_DGRAM;
5678871c557Sdlg 	hints.ai_flags = AI_PASSIVE;
5688871c557Sdlg 
5698871c557Sdlg 	TAILQ_INIT(&tftp_servers);
5708871c557Sdlg 
5718871c557Sdlg 	error = getaddrinfo(addr, port, &hints, &res0);
5728871c557Sdlg 	if (error) {
5738871c557Sdlg 		errx(1, "%s:%s: %s", addr ? addr : "*", port,
5748871c557Sdlg 		    gai_strerror(error));
5758871c557Sdlg 	}
5768871c557Sdlg 
5778871c557Sdlg 	for (res = res0; res != NULL; res = res->ai_next) {
578e22aa732Sdlg 		s = socket(res->ai_family, res->ai_socktype | SOCK_NONBLOCK,
579e22aa732Sdlg 		    res->ai_protocol);
5808871c557Sdlg 		if (s == -1) {
5818871c557Sdlg 			cause = "socket";
582aee3563dSdlg 			cerrno = errno;
5838871c557Sdlg 			continue;
5848871c557Sdlg 		}
5858871c557Sdlg 
5868871c557Sdlg 		if (bind(s, res->ai_addr, res->ai_addrlen) == -1) {
5878871c557Sdlg 			cause = "bind";
588aee3563dSdlg 			cerrno = errno;
5898871c557Sdlg 			close(s);
5908871c557Sdlg 			continue;
5918871c557Sdlg 		}
5928871c557Sdlg 
5938871c557Sdlg 		switch (res->ai_family) {
5948871c557Sdlg 		case AF_INET:
5958871c557Sdlg 			if (setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR,
5968871c557Sdlg 			    &on, sizeof(on)) == -1)
597bca13417Sdlg 				err(1, "setsockopt(IP_RECVDSTADDR)");
5988871c557Sdlg 			break;
5998871c557Sdlg 		case AF_INET6:
6008871c557Sdlg 			if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO,
6018871c557Sdlg 			    &on, sizeof(on)) == -1)
602bca13417Sdlg 				err(1, "setsockopt(IPV6_RECVPKTINFO)");
6038871c557Sdlg 			break;
6048871c557Sdlg 		}
6058871c557Sdlg 
6068871c557Sdlg 		server = malloc(sizeof(*server));
6078871c557Sdlg 		if (server == NULL)
6088871c557Sdlg 			err(1, "malloc");
6098871c557Sdlg 
6108871c557Sdlg 		server->s = s;
6118871c557Sdlg 		TAILQ_INSERT_TAIL(&tftp_servers, server, entry);
6128871c557Sdlg 	}
6138871c557Sdlg 
6148871c557Sdlg 	if (TAILQ_EMPTY(&tftp_servers))
615aee3563dSdlg 		errc(1, cerrno, "%s", cause);
6168871c557Sdlg 
61700d70eefStobias 	freeaddrinfo(res0);
6188871c557Sdlg 	return (0);
6198871c557Sdlg }
6208871c557Sdlg 
6218871c557Sdlg void
6228871c557Sdlg tftpd_events(void)
6238871c557Sdlg {
6248871c557Sdlg 	struct tftp_server *server;
6258871c557Sdlg 	TAILQ_FOREACH(server, &tftp_servers, entry) {
6268871c557Sdlg 		event_set(&server->ev, server->s, EV_READ | EV_PERSIST,
6278871c557Sdlg 		    tftpd_recv, server);
6288871c557Sdlg 		event_add(&server->ev, NULL);
6298871c557Sdlg 	}
6308871c557Sdlg }
6318871c557Sdlg 
6328871c557Sdlg struct tftp_client *
633d2c62849Sderaadt client_alloc(void)
6348871c557Sdlg {
6358871c557Sdlg 	struct tftp_client *client;
6368871c557Sdlg 
637f4e49ee5Sdlg 	client = calloc(1, sizeof(*client));
6388871c557Sdlg 	if (client == NULL)
6398871c557Sdlg 		return (NULL);
6408871c557Sdlg 
6418871c557Sdlg 	client->segment_size = SEGSIZE;
6428871c557Sdlg 	client->packet_size = SEGSIZE + 4;
6438871c557Sdlg 
6448871c557Sdlg 	client->tv.tv_sec = TIMEOUT;
6458871c557Sdlg 	client->tv.tv_usec = 0;
6468871c557Sdlg 
6478871c557Sdlg 	client->sock = -1;
6488871c557Sdlg 	client->file = NULL;
6498871c557Sdlg 	client->newline = 0;
6508871c557Sdlg 
6518871c557Sdlg 	return (client);
6528871c557Sdlg }
6538871c557Sdlg 
6548871c557Sdlg void
6558871c557Sdlg client_free(struct tftp_client *client)
6568871c557Sdlg {
6578871c557Sdlg 	free(client->options);
6588871c557Sdlg 
6598871c557Sdlg 	if (client->file != NULL)
6608871c557Sdlg 		fclose(client->file);
6618871c557Sdlg 
6628871c557Sdlg 	close(client->sock);
6638871c557Sdlg 
6648871c557Sdlg 	free(client);
6658871c557Sdlg }
6668871c557Sdlg 
6678871c557Sdlg void
6688871c557Sdlg tftpd_recv(int fd, short events, void *arg)
6698871c557Sdlg {
6708871c557Sdlg 	union {
6718871c557Sdlg 		struct cmsghdr hdr;
6728871c557Sdlg 		char	buf[CMSG_SPACE(sizeof(struct sockaddr_storage))];
6738871c557Sdlg 	} cmsgbuf;
6748871c557Sdlg 	struct cmsghdr *cmsg;
6758871c557Sdlg 	struct msghdr msg;
6768871c557Sdlg 	struct iovec iov;
6778871c557Sdlg 
6788871c557Sdlg 	ssize_t n;
6798871c557Sdlg 	struct sockaddr_storage s_in;
6808871c557Sdlg 	int dobind = 1;
6818871c557Sdlg 	int on = 1;
6828871c557Sdlg 
6838871c557Sdlg 	struct tftphdr *tp;
6848871c557Sdlg 
6858871c557Sdlg 	struct tftp_client *client;
6868871c557Sdlg 
6878871c557Sdlg 	client = client_alloc();
6888871c557Sdlg 	if (client == NULL) {
6890b75d96cStedu 		char buf[SEGSIZE_MAX + 4];
6908871c557Sdlg 		/* no memory! flush this request... */
6918871c557Sdlg 		recv(fd, buf, SEGSIZE_MAX + 4, 0);
6928871c557Sdlg 		/* dont care if it fails */
6938871c557Sdlg 		return;
6948871c557Sdlg 	}
6958871c557Sdlg 
6968871c557Sdlg 	bzero(&msg, sizeof(msg));
6978871c557Sdlg 	iov.iov_base = client->buf;
6988871c557Sdlg 	iov.iov_len = client->packet_size;
6998871c557Sdlg 	msg.msg_name = &client->ss;
7008871c557Sdlg 	msg.msg_namelen = sizeof(client->ss);
7018871c557Sdlg 	msg.msg_iov = &iov;
7028871c557Sdlg 	msg.msg_iovlen = 1;
7038871c557Sdlg 	msg.msg_control = &cmsgbuf.buf;
7048871c557Sdlg 	msg.msg_controllen = sizeof(cmsgbuf.buf);
7058871c557Sdlg 
7068871c557Sdlg 	n = recvmsg(fd, &msg, 0);
7078871c557Sdlg 	if (n == -1) {
7088871c557Sdlg 		lwarn("recvmsg");
7098871c557Sdlg 		goto err;
7108871c557Sdlg 	}
7118871c557Sdlg 	if (n < 4)
7128871c557Sdlg 		goto err;
7138871c557Sdlg 
714e22aa732Sdlg 	client->sock = socket(client->ss.ss_family,
715e22aa732Sdlg 	    SOCK_DGRAM | SOCK_NONBLOCK, 0);
7168871c557Sdlg 	if (client->sock == -1) {
7178871c557Sdlg 		lwarn("socket");
7188871c557Sdlg 		goto err;
7198871c557Sdlg 	}
7208871c557Sdlg 	memset(&s_in, 0, sizeof(s_in));
7218871c557Sdlg 	s_in.ss_family = client->ss.ss_family;
7228871c557Sdlg 	s_in.ss_len = client->ss.ss_len;
7238871c557Sdlg 
7248871c557Sdlg 	/* get local address if possible */
7258871c557Sdlg 	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
7268871c557Sdlg 	    cmsg = CMSG_NXTHDR(&msg, cmsg)) {
7278871c557Sdlg 		if (cmsg->cmsg_level == IPPROTO_IP &&
7288871c557Sdlg 		    cmsg->cmsg_type == IP_RECVDSTADDR) {
7298871c557Sdlg 			memcpy(&((struct sockaddr_in *)&s_in)->sin_addr,
7308871c557Sdlg 			    CMSG_DATA(cmsg), sizeof(struct in_addr));
7318871c557Sdlg 			if (((struct sockaddr_in *)&s_in)->sin_addr.s_addr ==
7328871c557Sdlg 			    INADDR_BROADCAST)
7338871c557Sdlg 				dobind = 0;
7348871c557Sdlg 			break;
7358871c557Sdlg 		}
7368871c557Sdlg 		if (cmsg->cmsg_level == IPPROTO_IPV6 &&
7378871c557Sdlg 		    cmsg->cmsg_type == IPV6_PKTINFO) {
7388871c557Sdlg 			struct in6_pktinfo *ipi;
7398871c557Sdlg 
7408871c557Sdlg 			ipi = (struct in6_pktinfo *)CMSG_DATA(cmsg);
7418871c557Sdlg 			memcpy(&((struct sockaddr_in6 *)&s_in)->sin6_addr,
7428871c557Sdlg 			    &ipi->ipi6_addr, sizeof(struct in6_addr));
7438871c557Sdlg 			if (IN6_IS_ADDR_LINKLOCAL(&ipi->ipi6_addr))
7448871c557Sdlg 				((struct sockaddr_in6 *)&s_in)->sin6_scope_id =
7458871c557Sdlg 				    ipi->ipi6_ifindex;
7468871c557Sdlg 			break;
7478871c557Sdlg 		}
7488871c557Sdlg 	}
7498871c557Sdlg 
7508871c557Sdlg 	if (dobind) {
7518871c557Sdlg 		setsockopt(client->sock, SOL_SOCKET, SO_REUSEADDR,
7528871c557Sdlg 		    &on, sizeof(on));
7538871c557Sdlg 		setsockopt(client->sock, SOL_SOCKET, SO_REUSEPORT,
7548871c557Sdlg 		    &on, sizeof(on));
7558871c557Sdlg 
7568871c557Sdlg 		if (bind(client->sock, (struct sockaddr *)&s_in,
757df69c215Sderaadt 		    s_in.ss_len) == -1) {
7588871c557Sdlg 			lwarn("bind to %s", getip(&s_in));
7598871c557Sdlg 			goto err;
7608871c557Sdlg 		}
7618871c557Sdlg 	}
7628871c557Sdlg 	if (connect(client->sock, (struct sockaddr *)&client->ss,
7638871c557Sdlg 	    client->ss.ss_len) == -1) {
7648871c557Sdlg 		lwarn("connect to %s", getip(&client->ss));
7658871c557Sdlg 		goto err;
7668871c557Sdlg 	}
7678871c557Sdlg 
7688871c557Sdlg 	tp = (struct tftphdr *)client->buf;
7698871c557Sdlg 	client->opcode = ntohs(tp->th_opcode);
7708871c557Sdlg 	if (client->opcode != RRQ && client->opcode != WRQ) {
7718871c557Sdlg 		/* bad request */
7728871c557Sdlg 		goto err;
7738871c557Sdlg 	}
7748871c557Sdlg 
7758871c557Sdlg 	tftp(client, tp, n);
7768871c557Sdlg 
7778871c557Sdlg 	return;
7788871c557Sdlg 
7798871c557Sdlg err:
7808871c557Sdlg 	client_free(client);
7818871c557Sdlg }
7828871c557Sdlg 
7838871c557Sdlg int
7848871c557Sdlg parse_options(struct tftp_client *client, char *cp, size_t size,
7858871c557Sdlg     struct opt_client *options)
7868871c557Sdlg {
7878871c557Sdlg 	char *option;
7888871c557Sdlg 	char *ccp;
7898871c557Sdlg 	int has_options = 0;
7908871c557Sdlg 	int i;
7918871c557Sdlg 
7928871c557Sdlg 	while (++cp < client->buf + size) {
7938871c557Sdlg 		for (i = 2, ccp = cp; i > 0; ccp++) {
7948871c557Sdlg 			if (ccp >= client->buf + size) {
7958871c557Sdlg 				/*
7968871c557Sdlg 				 * Don't reject the request, just stop trying
7978871c557Sdlg 				 * to parse the option and get on with it.
7988871c557Sdlg 				 * Some Apple OpenFirmware versions have
7998871c557Sdlg 				 * trailing garbage on the end of otherwise
8008871c557Sdlg 				 * valid requests.
8018871c557Sdlg 				 */
8028871c557Sdlg 				return (has_options);
8038871c557Sdlg 			} else if (*ccp == '\0')
8048871c557Sdlg 				i--;
8058871c557Sdlg 		}
8068871c557Sdlg 
8078871c557Sdlg 		for (option = cp; *cp; cp++)
8088d9633d1Sderaadt 			*cp = tolower((unsigned char)*cp);
8098871c557Sdlg 
8108871c557Sdlg 		for (i = 0; i < NOPT; i++) {
8118871c557Sdlg 			if (strcmp(option, opt_names[i]) == 0) {
8128871c557Sdlg 				options[i].o_request = ++cp;
8138871c557Sdlg 				has_options = 1;
8148871c557Sdlg 			}
8158871c557Sdlg 		}
8168871c557Sdlg 		cp = ccp - 1;
8178871c557Sdlg 	}
8188871c557Sdlg 
8198871c557Sdlg 	return (has_options);
8208871c557Sdlg }
8218871c557Sdlg 
8228871c557Sdlg /*
8238871c557Sdlg  * Handle initial connection protocol.
8248871c557Sdlg  */
8258871c557Sdlg void
8268871c557Sdlg tftp(struct tftp_client *client, struct tftphdr *tp, size_t size)
8278871c557Sdlg {
8288871c557Sdlg 	struct opt_client *options;
8298871c557Sdlg 
8308871c557Sdlg 	char		*cp;
8318871c557Sdlg 	int		 i, first = 1, ecode, to;
8328871c557Sdlg 	struct formats	*pf;
833769bdeccSdlg 	char		*mode = NULL;
834b9fc9a72Sderaadt 	char		 filename[PATH_MAX];
8358871c557Sdlg 	const char	*errstr;
8368871c557Sdlg 
8378871c557Sdlg 	if (size < 5) {
8388871c557Sdlg 		ecode = EBADOP;
8398871c557Sdlg 		goto error;
8408871c557Sdlg 	}
8418871c557Sdlg 
8428871c557Sdlg 	cp = tp->th_stuff;
8438871c557Sdlg again:
8448871c557Sdlg 	while (cp < client->buf + size) {
8458871c557Sdlg 		if (*cp == '\0')
8468871c557Sdlg 			break;
8478871c557Sdlg 		cp++;
8488871c557Sdlg 	}
8498871c557Sdlg 	if (*cp != '\0') {
8508871c557Sdlg 		ecode = EBADOP;
8518871c557Sdlg 		goto error;
8528871c557Sdlg 	}
8538871c557Sdlg 	i = cp - tp->th_stuff;
8548871c557Sdlg 	if (i >= sizeof(filename)) {
8558871c557Sdlg 		ecode = EBADOP;
8568871c557Sdlg 		goto error;
8578871c557Sdlg 	}
8588871c557Sdlg 	memcpy(filename, tp->th_stuff, i);
8598871c557Sdlg 	filename[i] = '\0';
8608871c557Sdlg 	if (first) {
8618871c557Sdlg 		mode = ++cp;
8628871c557Sdlg 		first = 0;
8638871c557Sdlg 		goto again;
8648871c557Sdlg 	}
8658871c557Sdlg 	for (cp = mode; *cp; cp++)
8668d9633d1Sderaadt 		*cp = tolower((unsigned char)*cp);
8678871c557Sdlg 
8688871c557Sdlg 	for (pf = formats; pf->f_mode; pf++) {
8698871c557Sdlg 		if (strcmp(pf->f_mode, mode) == 0)
8708871c557Sdlg 			break;
8718871c557Sdlg 	}
8728871c557Sdlg 	if (pf->f_mode == 0) {
8738871c557Sdlg 		ecode = EBADOP;
8748871c557Sdlg 		goto error;
8758871c557Sdlg 	}
8768871c557Sdlg 	client->fgetc = pf->f_getc;
8778871c557Sdlg 	client->fputc = pf->f_putc;
8788871c557Sdlg 
879f4e49ee5Sdlg 	client->options = options = calloc(NOPT, sizeof(*client->options));
8808871c557Sdlg 	if (options == NULL) {
8818871c557Sdlg 		ecode = 100 + ENOMEM;
8828871c557Sdlg 		goto error;
8838871c557Sdlg 	}
8848871c557Sdlg 
8858871c557Sdlg 	if (parse_options(client, cp, size, options)) {
8868871c557Sdlg 		if (options[OPT_TIMEOUT].o_request != NULL) {
8878871c557Sdlg 			to = strtonum(options[OPT_TIMEOUT].o_request,
8888871c557Sdlg 			    TIMEOUT_MIN, TIMEOUT_MAX, &errstr);
8898871c557Sdlg 			if (errstr) {
8908871c557Sdlg 				ecode = EBADOP;
8918871c557Sdlg 				goto error;
8928871c557Sdlg 			}
8938871c557Sdlg 			options[OPT_TIMEOUT].o_reply = client->tv.tv_sec = to;
8948871c557Sdlg 		}
8958871c557Sdlg 
8968871c557Sdlg 		if (options[OPT_BLKSIZE].o_request) {
8978871c557Sdlg 			client->segment_size = strtonum(
8988871c557Sdlg 			    options[OPT_BLKSIZE].o_request,
8998871c557Sdlg 			    SEGSIZE_MIN, SEGSIZE_MAX, &errstr);
9008871c557Sdlg 			if (errstr) {
9018871c557Sdlg 				ecode = EBADOP;
9028871c557Sdlg 				goto error;
9038871c557Sdlg 			}
9048871c557Sdlg 			client->packet_size = client->segment_size + 4;
9058871c557Sdlg 			options[OPT_BLKSIZE].o_reply = client->segment_size;
9068871c557Sdlg 		}
9078871c557Sdlg 	} else {
9088871c557Sdlg 		free(options);
9098871c557Sdlg 		client->options = NULL;
9108871c557Sdlg 	}
9118871c557Sdlg 
9128871c557Sdlg 	if (verbose) {
913b9fc9a72Sderaadt 		char nicebuf[PATH_MAX];
9148871c557Sdlg 
915b9fc9a72Sderaadt 		(void)strnvis(nicebuf, filename, PATH_MAX,
9168871c557Sdlg 		    VIS_SAFE|VIS_OCTAL);
9178871c557Sdlg 
9188871c557Sdlg 		linfo("%s: %s request for '%s'", getip(&client->ss),
9198871c557Sdlg 		    client->opcode == WRQ ? "write" : "read", nicebuf);
9208871c557Sdlg 	}
9218871c557Sdlg 
9228871c557Sdlg 	if (rwmap != NULL)
9238871c557Sdlg 		rewrite_map(client, filename);
9248871c557Sdlg 	else
9258871c557Sdlg 		tftp_open(client, filename);
9268871c557Sdlg 
9278871c557Sdlg 	return;
9288871c557Sdlg 
9298871c557Sdlg error:
9308871c557Sdlg 	nak(client, ecode);
9318871c557Sdlg }
9328871c557Sdlg 
9338871c557Sdlg void
9348871c557Sdlg tftp_open(struct tftp_client *client, const char *filename)
9358871c557Sdlg {
9368871c557Sdlg 	int ecode;
9378871c557Sdlg 
9388871c557Sdlg 	ecode = validate_access(client, filename);
939ba119d57Sdlg 	if (ecode)
940ba119d57Sdlg 		goto error;
9418871c557Sdlg 
9428871c557Sdlg 	if (client->options) {
943eca1b59cSdlg 		if (oack(client) == -1)
944ba119d57Sdlg 			goto error;
9458871c557Sdlg 
9468871c557Sdlg 		free(client->options);
9478871c557Sdlg 		client->options = NULL;
9488871c557Sdlg 	} else if (client->opcode == WRQ) {
9498871c557Sdlg 		recvfile(client);
9508871c557Sdlg 	} else
9518871c557Sdlg 		sendfile(client);
9528871c557Sdlg 
9538871c557Sdlg 	return;
954ba119d57Sdlg error:
955ba119d57Sdlg 	nak(client, ecode);
9568871c557Sdlg }
9578871c557Sdlg 
9588871c557Sdlg /*
9598871c557Sdlg  * Validate file access.  Since we
9608871c557Sdlg  * have no uid or gid, for now require
9618871c557Sdlg  * file to exist and be publicly
9628871c557Sdlg  * readable/writable.
9638871c557Sdlg  * If we were invoked with arguments
9648871c557Sdlg  * from inetd then the file must also be
9658871c557Sdlg  * in one of the given directory prefixes.
9668871c557Sdlg  * Note also, full path name must be
9678871c557Sdlg  * given as we have no login directory.
9688871c557Sdlg  */
9698871c557Sdlg int
970f923cd8eSjca validate_access(struct tftp_client *client, const char *requested)
9718871c557Sdlg {
9728871c557Sdlg 	int		 mode = client->opcode;
9738871c557Sdlg 	struct opt_client *options = client->options;
9748871c557Sdlg 	struct stat	 stbuf;
975769bdeccSdlg 	int		 fd, wmode;
976f923cd8eSjca 	const char	*errstr, *filename;
977f923cd8eSjca 	char		 rewritten[PATH_MAX];
9788871c557Sdlg 
979a5c6d06aSkn 	if (!canwrite && mode != RRQ)
980a5c6d06aSkn 		return (EACCESS);
981a5c6d06aSkn 
982f923cd8eSjca 	if (strcmp(requested, SEEDPATH) == 0) {
9836fbffbdaSdlg 		char *buf;
9844a5fd927Smcbride 		if (mode != RRQ)
9854a5fd927Smcbride 			return (EACCESS);
9866fbffbdaSdlg 
9876fbffbdaSdlg 		buf = client->buf + sizeof(client->buf) - 512;
9886fbffbdaSdlg 		arc4random_buf(buf, 512);
9890c1f8293Snaddy 		if (options != NULL && options[OPT_TSIZE].o_request)
9900c1f8293Snaddy 			options[OPT_TSIZE].o_reply = 512;
9916fbffbdaSdlg 		client->file = fmemopen(buf, 512, "r");
9926fbffbdaSdlg 		if (client->file == NULL)
9936fbffbdaSdlg 			return (errno + 100);
9946fbffbdaSdlg 
9954a5fd927Smcbride 		return (0);
9964a5fd927Smcbride 	}
9974a5fd927Smcbride 
998f923cd8eSjca 	if (iflag) {
999f923cd8eSjca 		int ret;
1000f923cd8eSjca 
1001f923cd8eSjca 		/*
1002f923cd8eSjca 		 * In -i mode, look in the directory named after the
1003f923cd8eSjca 		 * client address.
1004f923cd8eSjca 		 */
1005f923cd8eSjca 		ret = snprintf(rewritten, sizeof(rewritten), "%s/%s",
1006f923cd8eSjca 		    getip(&client->ss), requested);
1007515e489cSderaadt 		if (ret < 0 || ret >= sizeof(rewritten))
1008f923cd8eSjca 			return (ENAMETOOLONG + 100);
1009f923cd8eSjca 		filename = rewritten;
1010f923cd8eSjca 	} else {
1011f923cd8eSjca retryread:
1012f923cd8eSjca 		filename = requested;
1013f923cd8eSjca 	}
1014f923cd8eSjca 
10158871c557Sdlg 	/*
10168871c557Sdlg 	 * We use a different permissions scheme if `cancreate' is
10178871c557Sdlg 	 * set.
10188871c557Sdlg 	 */
10198871c557Sdlg 	wmode = O_TRUNC;
1020df69c215Sderaadt 	if (stat(filename, &stbuf) == -1) {
1021f923cd8eSjca 		if (!cancreate) {
1022f923cd8eSjca 			/*
1023f923cd8eSjca 			 * In -i mode, retry failed read requests from
1024f923cd8eSjca 			 * the root directory.
1025f923cd8eSjca 			 */
1026f923cd8eSjca 			if (mode == RRQ && errno == ENOENT &&
1027f923cd8eSjca 			    filename == rewritten)
1028f923cd8eSjca 				goto retryread;
10298871c557Sdlg 			return (errno == ENOENT ? ENOTFOUND : EACCESS);
1030f923cd8eSjca 		} else {
10318871c557Sdlg 			if ((errno == ENOENT) && (mode != RRQ))
10328871c557Sdlg 				wmode |= O_CREAT;
10338871c557Sdlg 			else
10348871c557Sdlg 				return (EACCESS);
10358871c557Sdlg 		}
10368871c557Sdlg 	} else {
10378871c557Sdlg 		if (mode == RRQ) {
1038d17353c4Sderaadt 			if ((stbuf.st_mode & (S_IRUSR >> 6)) == 0)
10398871c557Sdlg 				return (EACCESS);
10408871c557Sdlg 		} else {
1041d17353c4Sderaadt 			if ((stbuf.st_mode & (S_IWUSR >> 6)) == 0)
10428871c557Sdlg 				return (EACCESS);
10438871c557Sdlg 		}
10448871c557Sdlg 	}
10458871c557Sdlg 
10468871c557Sdlg 	if (options != NULL && options[OPT_TSIZE].o_request) {
10478871c557Sdlg 		if (mode == RRQ)
10488871c557Sdlg 			options[OPT_TSIZE].o_reply = stbuf.st_size;
10498871c557Sdlg 		else {
10508871c557Sdlg 			/* allows writes of 65535 blocks * SEGSIZE_MAX bytes */
10518871c557Sdlg 			options[OPT_TSIZE].o_reply =
10528871c557Sdlg 			    strtonum(options[OPT_TSIZE].o_request,
10538871c557Sdlg 			    1, 65535LL * SEGSIZE_MAX, &errstr);
10548871c557Sdlg 			if (errstr)
10558871c557Sdlg 				return (EOPTNEG);
10568871c557Sdlg 		}
10578871c557Sdlg 	}
10588871c557Sdlg 	fd = open(filename, mode == RRQ ? O_RDONLY : (O_WRONLY|wmode), 0666);
1059df69c215Sderaadt 	if (fd == -1)
10608871c557Sdlg 		return (errno + 100);
10618871c557Sdlg 	/*
10628871c557Sdlg 	 * If the file was created, set default permissions.
10638871c557Sdlg 	 */
1064df69c215Sderaadt 	if ((wmode & O_CREAT) && fchmod(fd, 0666) == -1) {
10658871c557Sdlg 		int serrno = errno;
10668871c557Sdlg 
10678871c557Sdlg 		close(fd);
10688871c557Sdlg 		unlink(filename);
10698871c557Sdlg 
10708871c557Sdlg 		return (serrno + 100);
10718871c557Sdlg 	}
10728871c557Sdlg 	client->file = fdopen(fd, mode == RRQ ? "r" : "w");
10738871c557Sdlg 	if (client->file == NULL) {
10748871c557Sdlg 		close(fd);
10758871c557Sdlg 		return (errno + 100);
10768871c557Sdlg 	}
10778871c557Sdlg 
10788871c557Sdlg 	return (0);
10798871c557Sdlg }
10808871c557Sdlg 
10818871c557Sdlg int
10828871c557Sdlg fget_octet(struct tftp_client *client)
10838871c557Sdlg {
10848871c557Sdlg 	return (getc(client->file));
10858871c557Sdlg }
10868871c557Sdlg 
10878871c557Sdlg int
10888871c557Sdlg fput_octet(struct tftp_client *client, int c)
10898871c557Sdlg {
10908871c557Sdlg 	return (putc(c, client->file));
10918871c557Sdlg }
10928871c557Sdlg 
10938871c557Sdlg int
10948871c557Sdlg fget_netascii(struct tftp_client *client)
10958871c557Sdlg {
1096769bdeccSdlg 	int c = -1;
10978871c557Sdlg 
10988871c557Sdlg 	switch (client->newline) {
10998871c557Sdlg 	case 0:
11008871c557Sdlg 		c = getc(client->file);
11018871c557Sdlg 		if (c == EOF)
11028871c557Sdlg 			break;
11038871c557Sdlg 
11048871c557Sdlg 		if (c == '\n' || c == '\r') {
11058871c557Sdlg 			client->newline = c;
11068871c557Sdlg 			c = '\r';
11078871c557Sdlg 		}
11088871c557Sdlg 		break;
11098871c557Sdlg 	case '\n':
11108871c557Sdlg 		client->newline = 0;
11118871c557Sdlg 		c = '\n';
11128871c557Sdlg 		break;
11138871c557Sdlg 	case '\r':
11148871c557Sdlg 		client->newline = 0;
11158871c557Sdlg 		c = '\0';
11168871c557Sdlg 		break;
11178871c557Sdlg 	}
11188871c557Sdlg 
11198871c557Sdlg 	return (c);
11208871c557Sdlg }
11218871c557Sdlg 
11228871c557Sdlg int
11238871c557Sdlg fput_netascii(struct tftp_client *client, int c)
11248871c557Sdlg {
11258871c557Sdlg 	if (client->newline == '\r') {
11268871c557Sdlg 		client->newline = 0;
11278871c557Sdlg 
11288871c557Sdlg 		if (c == '\0')
11298871c557Sdlg 			c = '\r';
11308871c557Sdlg 
11318871c557Sdlg 	} else if (c == '\r') {
11328871c557Sdlg 		client->newline = c;
11338871c557Sdlg 		return (c);
11348871c557Sdlg 	}
11358871c557Sdlg 
11368871c557Sdlg 	return (putc(c, client->file));
11378871c557Sdlg }
11388871c557Sdlg 
11398871c557Sdlg void
11408871c557Sdlg sendfile(struct tftp_client *client)
11418871c557Sdlg {
11428871c557Sdlg 	event_set(&client->sev, client->sock, EV_READ, tftp_rrq_ack, client);
11438871c557Sdlg 	client->block = 1;
11448871c557Sdlg 
11458871c557Sdlg 	file_read(client);
11468871c557Sdlg }
11478871c557Sdlg 
11488871c557Sdlg void
11498871c557Sdlg file_read(struct tftp_client *client)
11508871c557Sdlg {
11518871c557Sdlg 	u_int8_t *buf;
11528871c557Sdlg 	struct tftphdr *dp;
11538871c557Sdlg 	int i;
11548871c557Sdlg 	int c;
11558871c557Sdlg 
11568871c557Sdlg 	dp = (struct tftphdr *)client->buf;
11578871c557Sdlg 	dp->th_opcode = htons((u_short)DATA);
11588871c557Sdlg 	dp->th_block = htons(client->block);
11598871c557Sdlg 	buf = (u_int8_t *)dp->th_data;
11608871c557Sdlg 
11618871c557Sdlg 	for (i = 0; i < client->segment_size; i++) {
11628871c557Sdlg 		c = client->fgetc(client);
11638871c557Sdlg 		if (c == EOF) {
11648871c557Sdlg 			if (ferror(client->file)) {
11658871c557Sdlg 				nak(client, 100 + EIO);
11668871c557Sdlg 				return;
11678871c557Sdlg 			}
11688871c557Sdlg 
11698871c557Sdlg 			break;
11708871c557Sdlg 		}
11718871c557Sdlg 		buf[i] = c;
11728871c557Sdlg 	}
11738871c557Sdlg 
11748871c557Sdlg 	client->buflen = i + 4;
11758871c557Sdlg 	client->retries = RETRIES;
11768871c557Sdlg 
117780a17660Sdlg 	tftp_send(client);
117880a17660Sdlg }
117980a17660Sdlg 
118080a17660Sdlg void
118180a17660Sdlg tftp_send(struct tftp_client *client)
118280a17660Sdlg {
11838871c557Sdlg 	if (send(client->sock, client->buf, client->buflen, 0) == -1) {
11848871c557Sdlg 		lwarn("send(block)");
11858871c557Sdlg 		client_free(client);
11868871c557Sdlg 		return;
11878871c557Sdlg 	}
11888871c557Sdlg 
11898871c557Sdlg 	event_add(&client->sev, &client->tv);
11908871c557Sdlg }
11918871c557Sdlg 
11928871c557Sdlg void
11938871c557Sdlg tftp_rrq_ack(int fd, short events, void *arg)
11948871c557Sdlg {
11958871c557Sdlg 	struct tftp_client *client = arg;
11968871c557Sdlg 	struct tftphdr *ap; /* ack packet */
11971e449da8Sdlg 	char rbuf[SEGSIZE_MIN];
11988871c557Sdlg 	ssize_t n;
11998871c557Sdlg 
12008871c557Sdlg 	if (events & EV_TIMEOUT) {
12011e449da8Sdlg 		if (retry(client) == -1) {
12021e449da8Sdlg 			lwarn("%s: retry", getip(&client->ss));
12038871c557Sdlg 			goto done;
12041e449da8Sdlg 		}
12058871c557Sdlg 
12068871c557Sdlg 		return;
12078871c557Sdlg 	}
12088871c557Sdlg 
12091e449da8Sdlg 	n = recv(fd, rbuf, sizeof(rbuf), 0);
12108871c557Sdlg 	if (n == -1) {
12118871c557Sdlg 		switch (errno) {
12128871c557Sdlg 		case EINTR:
12138871c557Sdlg 		case EAGAIN:
12148871c557Sdlg 			event_add(&client->sev, &client->tv);
12158871c557Sdlg 			return;
12168871c557Sdlg 
12178871c557Sdlg 		default:
12181e449da8Sdlg 			lwarn("%s: recv", getip(&client->ss));
12198871c557Sdlg 			goto done;
12208871c557Sdlg 		}
12218871c557Sdlg 	}
12228871c557Sdlg 
12231e449da8Sdlg 	ap = (struct tftphdr *)rbuf;
12248871c557Sdlg 	ap->th_opcode = ntohs((u_short)ap->th_opcode);
12258871c557Sdlg 	ap->th_block = ntohs((u_short)ap->th_block);
12268871c557Sdlg 
12271e449da8Sdlg 	switch (ap->th_opcode) {
12281e449da8Sdlg 	case ACK:
12291e449da8Sdlg 		break;
123080a17660Sdlg 	case ERROR:
123180a17660Sdlg 	default: /* assume the worst */
123280a17660Sdlg 		goto done;
12331e449da8Sdlg 	}
12341e449da8Sdlg 
12351e449da8Sdlg 	if (ap->th_block != client->block) {
12361e449da8Sdlg 		if (tftp_flush(client) == -1) {
12371e449da8Sdlg 			lwarnx("%s: flush", getip(&client->ss));
12381e449da8Sdlg 			goto done;
12391e449da8Sdlg 		}
12401e449da8Sdlg 
12411e449da8Sdlg 		if (ap->th_block != (client->block - 1))
12421e449da8Sdlg 			goto done;
12431e449da8Sdlg 
124480a17660Sdlg 		tftp_send(client);
124580a17660Sdlg 		return;
12461e449da8Sdlg 	}
12478871c557Sdlg 
12488871c557Sdlg 	if (client->buflen != client->packet_size) {
12498871c557Sdlg 		/* this was the last packet in the stream */
12508871c557Sdlg 		goto done;
12518871c557Sdlg 	}
12528871c557Sdlg 
12538871c557Sdlg 	client->block++;
12548871c557Sdlg 	file_read(client);
12558871c557Sdlg 	return;
12568871c557Sdlg 
12578871c557Sdlg done:
12588871c557Sdlg 	client_free(client);
12598871c557Sdlg }
12608871c557Sdlg 
12611e449da8Sdlg int
12621e449da8Sdlg tftp_flush(struct tftp_client *client)
12631e449da8Sdlg {
12641e449da8Sdlg 	char rbuf[SEGSIZE_MIN];
12651e449da8Sdlg 	ssize_t n;
12661e449da8Sdlg 
12671e449da8Sdlg 	for (;;) {
12681e449da8Sdlg 		n = recv(client->sock, rbuf, sizeof(rbuf), 0);
12691e449da8Sdlg 		if (n == -1) {
12701e449da8Sdlg 			switch (errno) {
12711e449da8Sdlg 			case EAGAIN:
12721e449da8Sdlg 				return (0);
12731e449da8Sdlg 
12741e449da8Sdlg 			case EINTR:
12751e449da8Sdlg 				break;
12761e449da8Sdlg 
12771e449da8Sdlg 			default:
12781e449da8Sdlg 				return (-1);
12791e449da8Sdlg 			}
12801e449da8Sdlg 		}
12811e449da8Sdlg 	}
12821e449da8Sdlg }
12831e449da8Sdlg 
12848871c557Sdlg void
12858871c557Sdlg recvfile(struct tftp_client *client)
12868871c557Sdlg {
12878871c557Sdlg 	event_set(&client->sev, client->sock, EV_READ, tftp_wrq, client);
12888871c557Sdlg 	tftp_wrq_ack(client);
12898871c557Sdlg }
12908871c557Sdlg 
12918871c557Sdlg int
12928871c557Sdlg tftp_wrq_ack_packet(struct tftp_client *client)
12938871c557Sdlg {
12948871c557Sdlg 	struct tftphdr *ap; /* ack packet */
12958871c557Sdlg 
12968871c557Sdlg 	ap = (struct tftphdr *)client->buf;
12978871c557Sdlg 	ap->th_opcode = htons((u_short)ACK);
12988871c557Sdlg 	ap->th_block = htons(client->block);
12998871c557Sdlg 
13008871c557Sdlg 	client->buflen = 4;
13011e449da8Sdlg 	client->retries = RETRIES;
13028871c557Sdlg 
13038871c557Sdlg 	return (send(client->sock, client->buf, client->buflen, 0) != 4);
13048871c557Sdlg }
13058871c557Sdlg 
13068871c557Sdlg void
13078871c557Sdlg tftp_wrq_ack(struct tftp_client *client)
13088871c557Sdlg {
13098871c557Sdlg 	if (tftp_wrq_ack_packet(client) != 0) {
13108871c557Sdlg 		lwarn("tftp wrq ack");
13118871c557Sdlg 		client_free(client);
13128871c557Sdlg 		return;
13138871c557Sdlg 	}
13148871c557Sdlg 
13158871c557Sdlg 	client->block++;
13168871c557Sdlg 	event_add(&client->sev, &client->tv);
13178871c557Sdlg }
13188871c557Sdlg 
13198871c557Sdlg void
13208871c557Sdlg tftp_wrq(int fd, short events, void *arg)
13218871c557Sdlg {
13221e449da8Sdlg 	char wbuf[SEGSIZE_MAX + 4];
13238871c557Sdlg 	struct tftp_client *client = arg;
13248871c557Sdlg 	struct tftphdr *dp;
13258871c557Sdlg 	ssize_t n;
13268871c557Sdlg 	int i;
13278871c557Sdlg 
13288871c557Sdlg 	if (events & EV_TIMEOUT) {
13291e449da8Sdlg 		if (retry(client) == -1) {
13301e449da8Sdlg 			lwarn("%s", getip(&client->ss));
13318871c557Sdlg 			goto done;
13321e449da8Sdlg 		}
13338871c557Sdlg 
13348871c557Sdlg 		return;
13358871c557Sdlg 	}
13368871c557Sdlg 
13371e449da8Sdlg 	n = recv(fd, wbuf, client->packet_size, 0);
13388871c557Sdlg 	if (n == -1) {
13398871c557Sdlg 		switch (errno) {
13408871c557Sdlg 		case EINTR:
13418871c557Sdlg 		case EAGAIN:
13428871c557Sdlg 			goto retry;
13438871c557Sdlg 
13448871c557Sdlg 		default:
13458871c557Sdlg 			lwarn("tftp_wrq recv");
13468871c557Sdlg 			goto done;
13478871c557Sdlg 		}
13488871c557Sdlg 	}
13498871c557Sdlg 
13508871c557Sdlg 	if (n < 4)
13518871c557Sdlg 		goto done;
13528871c557Sdlg 
13531e449da8Sdlg 	dp = (struct tftphdr *)wbuf;
13548871c557Sdlg 	dp->th_opcode = ntohs((u_short)dp->th_opcode);
13558871c557Sdlg 	dp->th_block = ntohs((u_short)dp->th_block);
13568871c557Sdlg 
13571e449da8Sdlg 	switch (dp->th_opcode) {
13581e449da8Sdlg 	case ERROR:
13591e449da8Sdlg 		goto done;
13601e449da8Sdlg 	case DATA:
13611e449da8Sdlg 		break;
13621e449da8Sdlg 	default:
13631e449da8Sdlg 		goto retry;
13641e449da8Sdlg 	}
13651e449da8Sdlg 
13661e449da8Sdlg 	if (dp->th_block != client->block) {
13671e449da8Sdlg 		if (tftp_flush(client) == -1) {
13681e449da8Sdlg 			lwarnx("%s: flush", getip(&client->ss));
13691e449da8Sdlg 			goto done;
13701e449da8Sdlg 		}
13711e449da8Sdlg 
13721e449da8Sdlg 		if (dp->th_block != (client->block - 1))
13738871c557Sdlg 			goto done;
13748871c557Sdlg 
13751e449da8Sdlg 		goto retry;
13761e449da8Sdlg 	}
13778871c557Sdlg 
13788871c557Sdlg 	for (i = 4; i < n; i++) {
13791e449da8Sdlg 		if (client->fputc(client, wbuf[i]) == EOF) {
13808871c557Sdlg 			lwarn("tftp wrq");
13818871c557Sdlg 			goto done;
13828871c557Sdlg 		}
13838871c557Sdlg 	}
13848871c557Sdlg 
13858871c557Sdlg 	if (n < client->packet_size) {
13868871c557Sdlg 		tftp_wrq_ack_packet(client);
138763ce4e59Sdlg 		fclose(client->file);
138863ce4e59Sdlg 		client->file = NULL;
13896ac42c1aSdlg 		event_set(&client->sev, client->sock, EV_READ,
13906ac42c1aSdlg 		    tftp_wrq_end, client);
13916ac42c1aSdlg 		event_add(&client->sev, &client->tv);
13926ac42c1aSdlg 		return;
13938871c557Sdlg 	}
13948871c557Sdlg 
13958871c557Sdlg 	tftp_wrq_ack(client);
13968871c557Sdlg 	return;
13978871c557Sdlg 
13988871c557Sdlg retry:
13998871c557Sdlg 	event_add(&client->sev, &client->tv);
14008871c557Sdlg 	return;
14018871c557Sdlg done:
14028871c557Sdlg 	client_free(client);
14038871c557Sdlg }
14048871c557Sdlg 
14056ac42c1aSdlg void
14066ac42c1aSdlg tftp_wrq_end(int fd, short events, void *arg)
14076ac42c1aSdlg {
14086ac42c1aSdlg 	char wbuf[SEGSIZE_MAX + 4];
14096ac42c1aSdlg 	struct tftp_client *client = arg;
14106ac42c1aSdlg 	struct tftphdr *dp;
14116ac42c1aSdlg 	ssize_t n;
14126ac42c1aSdlg 
14136ac42c1aSdlg 	if (events & EV_TIMEOUT) {
14146ac42c1aSdlg 		/* this was the last packet, we can clean up */
14156ac42c1aSdlg 		goto done;
14166ac42c1aSdlg 	}
14176ac42c1aSdlg 
14186ac42c1aSdlg 	n = recv(fd, wbuf, client->packet_size, 0);
14196ac42c1aSdlg 	if (n == -1) {
14206ac42c1aSdlg 		switch (errno) {
14216ac42c1aSdlg 		case EINTR:
14226ac42c1aSdlg 		case EAGAIN:
14236ac42c1aSdlg 			goto retry;
14246ac42c1aSdlg 
14256ac42c1aSdlg 		default:
14266ac42c1aSdlg 			lwarn("tftp_wrq_end recv");
14276ac42c1aSdlg 			goto done;
14286ac42c1aSdlg 		}
14296ac42c1aSdlg 	}
14306ac42c1aSdlg 
14316ac42c1aSdlg 	if (n < 4)
14326ac42c1aSdlg 		goto done;
14336ac42c1aSdlg 
14346ac42c1aSdlg 	dp = (struct tftphdr *)wbuf;
14356ac42c1aSdlg 	dp->th_opcode = ntohs((u_short)dp->th_opcode);
14366ac42c1aSdlg 	dp->th_block = ntohs((u_short)dp->th_block);
14376ac42c1aSdlg 
14386ac42c1aSdlg 	switch (dp->th_opcode) {
14396ac42c1aSdlg 	case ERROR:
14406ac42c1aSdlg 		goto done;
14416ac42c1aSdlg 	case DATA:
14426ac42c1aSdlg 		break;
14436ac42c1aSdlg 	default:
14446ac42c1aSdlg 		goto retry;
14456ac42c1aSdlg 	}
14466ac42c1aSdlg 
14476ac42c1aSdlg 	if (dp->th_block != client->block)
14486ac42c1aSdlg 		goto done;
14496ac42c1aSdlg 
14506ac42c1aSdlg retry:
14516ac42c1aSdlg 	if (retry(client) == -1) {
14526ac42c1aSdlg 		lwarn("%s", getip(&client->ss));
14536ac42c1aSdlg 		goto done;
14546ac42c1aSdlg 	}
14556ac42c1aSdlg 	return;
14566ac42c1aSdlg done:
14576ac42c1aSdlg 	client_free(client);
14586ac42c1aSdlg 	return;
14596ac42c1aSdlg }
14606ac42c1aSdlg 
14616ac42c1aSdlg 
14628871c557Sdlg /*
14638871c557Sdlg  * Send a nak packet (error message).
14648871c557Sdlg  * Error code passed in is one of the
14658871c557Sdlg  * standard TFTP codes, or a UNIX errno
14668871c557Sdlg  * offset by 100.
14678871c557Sdlg  */
14688871c557Sdlg void
14698871c557Sdlg nak(struct tftp_client *client, int error)
14708871c557Sdlg {
14718871c557Sdlg 	struct tftphdr	*tp;
14728871c557Sdlg 	struct errmsg	*pe;
14738871c557Sdlg 	size_t		 length;
14744a80bfc2Skrw 	ssize_t		 rslt;
14758871c557Sdlg 
14768871c557Sdlg 	tp = (struct tftphdr *)client->buf;
14778871c557Sdlg 	tp->th_opcode = htons((u_short)ERROR);
14788871c557Sdlg 	tp->th_code = htons((u_short)error);
14798871c557Sdlg 
14808871c557Sdlg 	for (pe = errmsgs; pe->e_code >= 0; pe++) {
14818871c557Sdlg 		if (pe->e_code == error)
14828871c557Sdlg 			break;
14838871c557Sdlg 	}
14848871c557Sdlg 	if (pe->e_code < 0) {
14858871c557Sdlg 		pe->e_msg = strerror(error - 100);
148689df2fcbSdlg 		tp->th_code = htons(EUNDEF);   /* set 'undef' errorcode */
14878871c557Sdlg 	}
14888871c557Sdlg 
14898871c557Sdlg 	length = strlcpy(tp->th_msg, pe->e_msg, client->packet_size - 5) + 5;
14908871c557Sdlg 	if (length > client->packet_size)
14918871c557Sdlg 		length = client->packet_size;
14928871c557Sdlg 
14934a80bfc2Skrw 	linfo("%s: nak: %s", getip(&client->ss), tp->th_msg);
14944a80bfc2Skrw 
14954a80bfc2Skrw 	rslt = send(client->sock, client->buf, length, 0);
14964a80bfc2Skrw 	if (rslt == -1)
14974a80bfc2Skrw 		lwarn("%s: nak", getip(&client->ss));
14984a80bfc2Skrw 	else if ((size_t)rslt != length)
14994a80bfc2Skrw 		lwarnx("%s: nak: sent %zd of %zu bytes", getip(&client->ss),
15004a80bfc2Skrw 		    rslt, length);
15018871c557Sdlg 
15028871c557Sdlg 	client_free(client);
15038871c557Sdlg }
15048871c557Sdlg 
15058871c557Sdlg /*
15068871c557Sdlg  * Send an oack packet (option acknowledgement).
15078871c557Sdlg  */
1508eca1b59cSdlg int
15098871c557Sdlg oack(struct tftp_client *client)
15108871c557Sdlg {
15118871c557Sdlg 	struct opt_client *options = client->options;
15128871c557Sdlg 	struct tftphdr *tp;
15138871c557Sdlg 	char *bp;
15148871c557Sdlg 	int i, n, size;
15158871c557Sdlg 
15168871c557Sdlg 	tp = (struct tftphdr *)client->buf;
15178871c557Sdlg 	bp = (char *)tp->th_stuff;
151831d5426eSdlg 	size = sizeof(client->buf) - 2;
15198871c557Sdlg 
15208871c557Sdlg 	tp->th_opcode = htons((u_short)OACK);
15218871c557Sdlg 	for (i = 0; i < NOPT; i++) {
15228871c557Sdlg 		if (options[i].o_request == NULL)
15238871c557Sdlg 			continue;
15248871c557Sdlg 
15258871c557Sdlg 		n = snprintf(bp, size, "%s%c%lld", opt_names[i], '\0',
15268871c557Sdlg 		    options[i].o_reply);
1527515e489cSderaadt 		if (n < 0 || n >= size) {
152875b3d082Sdlg 			lwarnx("oack: no buffer space");
15298871c557Sdlg 			goto error;
15308871c557Sdlg 		}
15318871c557Sdlg 
15328871c557Sdlg 		bp += n + 1;
15338871c557Sdlg 		size -= n + 1;
15348871c557Sdlg 		if (size < 0) {
153575b3d082Sdlg 			lwarnx("oack: no buffer space");
15368871c557Sdlg 			goto error;
15378871c557Sdlg 		}
15388871c557Sdlg 	}
15398871c557Sdlg 
15408871c557Sdlg 	client->buflen = bp - client->buf;
15418871c557Sdlg 	client->retries = RETRIES;
15428871c557Sdlg 
15438871c557Sdlg 	if (send(client->sock, client->buf, client->buflen, 0) == -1) {
15448871c557Sdlg 		lwarn("oack");
15458871c557Sdlg 		goto error;
15468871c557Sdlg 	}
15478871c557Sdlg 
15488871c557Sdlg 	/* no client ACK for write requests with options */
15498871c557Sdlg 	if (client->opcode == WRQ) {
15508871c557Sdlg 		client->block = 1;
15518871c557Sdlg 		event_set(&client->sev, client->sock, EV_READ,
15528871c557Sdlg 		    tftp_wrq, client);
15538871c557Sdlg 	} else
15548871c557Sdlg 		event_set(&client->sev, client->sock, EV_READ,
15558871c557Sdlg 		    oack_done, client);
15568871c557Sdlg 
15578871c557Sdlg 	event_add(&client->sev, &client->tv);
1558eca1b59cSdlg 	return (0);
15598871c557Sdlg 
15608871c557Sdlg error:
1561eca1b59cSdlg 	return (-1);
15628871c557Sdlg }
15638871c557Sdlg 
15648871c557Sdlg int
15658871c557Sdlg retry(struct tftp_client *client)
15668871c557Sdlg {
15671e449da8Sdlg 	if (--client->retries == 0) {
15681e449da8Sdlg 		errno = ETIMEDOUT;
15698871c557Sdlg 		return (-1);
15701e449da8Sdlg 	}
15718871c557Sdlg 
157280a17660Sdlg 	tftp_send(client);
15738871c557Sdlg 
15748871c557Sdlg 	return (0);
15758871c557Sdlg }
15768871c557Sdlg 
15778871c557Sdlg void
15788871c557Sdlg oack_done(int fd, short events, void *arg)
15798871c557Sdlg {
15808871c557Sdlg 	struct tftp_client *client = arg;
15818871c557Sdlg 	struct tftphdr *ap;
15828871c557Sdlg 	ssize_t n;
15838871c557Sdlg 
15848871c557Sdlg 	if (events & EV_TIMEOUT) {
15851e449da8Sdlg 		if (retry(client) == -1) {
15861e449da8Sdlg 			lwarn("%s", getip(&client->ss));
15878871c557Sdlg 			goto done;
15881e449da8Sdlg 		}
15898871c557Sdlg 
15908871c557Sdlg 		return;
15918871c557Sdlg 	}
15928871c557Sdlg 
15938871c557Sdlg 	n = recv(client->sock, client->buf, client->packet_size, 0);
15948871c557Sdlg 	if (n == -1) {
15958871c557Sdlg 		switch (errno) {
15968871c557Sdlg 		case EINTR:
15978871c557Sdlg 		case EAGAIN:
15988871c557Sdlg 			event_add(&client->sev, &client->tv);
15998871c557Sdlg 			return;
16008871c557Sdlg 
16018871c557Sdlg 		default:
16021e449da8Sdlg 			lwarn("%s: recv", getip(&client->ss));
16038871c557Sdlg 			goto done;
16048871c557Sdlg 		}
16058871c557Sdlg 	}
16068871c557Sdlg 
16078871c557Sdlg 	if (n < 4)
16088871c557Sdlg 		goto done;
16098871c557Sdlg 
16108871c557Sdlg 	ap = (struct tftphdr *)client->buf;
16118871c557Sdlg 	ap->th_opcode = ntohs((u_short)ap->th_opcode);
16128871c557Sdlg 	ap->th_block = ntohs((u_short)ap->th_block);
16138871c557Sdlg 
16148871c557Sdlg 	if (ap->th_opcode != ACK || ap->th_block != 0)
16158871c557Sdlg 		goto done;
16168871c557Sdlg 
16178871c557Sdlg 	sendfile(client);
16188871c557Sdlg 	return;
16198871c557Sdlg 
16208871c557Sdlg done:
16218871c557Sdlg 	client_free(client);
16228871c557Sdlg }
16238871c557Sdlg 
16248871c557Sdlg const char *
16258871c557Sdlg getip(void *s)
16268871c557Sdlg {
16278871c557Sdlg 	struct sockaddr *sa = s;
16288871c557Sdlg 	static char hbuf[NI_MAXHOST];
16298871c557Sdlg 
16308871c557Sdlg 	if (getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf),
16318871c557Sdlg 	    NULL, 0, NI_NUMERICHOST))
16328871c557Sdlg 		strlcpy(hbuf, "0.0.0.0", sizeof(hbuf));
16338871c557Sdlg 
16348871c557Sdlg 	return(hbuf);
16358871c557Sdlg }
16368871c557Sdlg 
1637eaf6a821Sjca /* daemon(3) clone, intended to be used in a "r"estricted environment */
1638eaf6a821Sjca int
1639eaf6a821Sjca rdaemon(int devnull)
1640eaf6a821Sjca {
1641ec8c1742Sjca 	if (devnull == -1) {
1642ec8c1742Sjca 		errno = EBADF;
1643ec8c1742Sjca 		return (-1);
1644ec8c1742Sjca 	}
1645ec8c1742Sjca 	if (fcntl(devnull, F_GETFL) == -1)
1646ec8c1742Sjca 		return (-1);
1647eaf6a821Sjca 
1648eaf6a821Sjca 	switch (fork()) {
1649eaf6a821Sjca 	case -1:
1650eaf6a821Sjca 		return (-1);
1651eaf6a821Sjca 	case 0:
1652eaf6a821Sjca 		break;
1653eaf6a821Sjca 	default:
1654eaf6a821Sjca 		_exit(0);
1655eaf6a821Sjca 	}
1656eaf6a821Sjca 
1657eaf6a821Sjca 	if (setsid() == -1)
1658eaf6a821Sjca 		return (-1);
1659eaf6a821Sjca 
1660eaf6a821Sjca 	(void)dup2(devnull, STDIN_FILENO);
1661eaf6a821Sjca 	(void)dup2(devnull, STDOUT_FILENO);
1662eaf6a821Sjca 	(void)dup2(devnull, STDERR_FILENO);
1663eaf6a821Sjca 	if (devnull > 2)
1664eaf6a821Sjca 		(void)close(devnull);
1665eaf6a821Sjca 
1666eaf6a821Sjca 	return (0);
1667eaf6a821Sjca }
1668eaf6a821Sjca 
16698871c557Sdlg void
16708871c557Sdlg syslog_vstrerror(int e, int priority, const char *fmt, va_list ap)
16718871c557Sdlg {
16728871c557Sdlg 	char *s;
16738871c557Sdlg 
16748871c557Sdlg 	if (vasprintf(&s, fmt, ap) == -1) {
16758871c557Sdlg 		syslog(LOG_EMERG, "unable to alloc in syslog_vstrerror");
16768871c557Sdlg 		exit(1);
16778871c557Sdlg 	}
16788871c557Sdlg 
16798871c557Sdlg 	syslog(priority, "%s: %s", s, strerror(e));
16808871c557Sdlg 
16818871c557Sdlg 	free(s);
16828871c557Sdlg }
16838871c557Sdlg 
16848871c557Sdlg void
16858871c557Sdlg syslog_err(int ecode, const char *fmt, ...)
16868871c557Sdlg {
16878871c557Sdlg 	va_list ap;
16888871c557Sdlg 
16898871c557Sdlg 	va_start(ap, fmt);
1690a52b97d1Sflorian 	syslog_vstrerror(errno, LOG_CRIT, fmt, ap);
16918871c557Sdlg 	va_end(ap);
16928871c557Sdlg 
16938871c557Sdlg 	exit(ecode);
16948871c557Sdlg }
16958871c557Sdlg 
16968871c557Sdlg void
16978871c557Sdlg syslog_errx(int ecode, const char *fmt, ...)
16988871c557Sdlg {
16998871c557Sdlg 	va_list ap;
17008871c557Sdlg 
17018871c557Sdlg 	va_start(ap, fmt);
1702a52b97d1Sflorian 	vsyslog(LOG_CRIT, fmt, ap);
17038871c557Sdlg 	va_end(ap);
17048871c557Sdlg 
17058871c557Sdlg 	exit(ecode);
17068871c557Sdlg }
17078871c557Sdlg 
17088871c557Sdlg void
17098871c557Sdlg syslog_warn(const char *fmt, ...)
17108871c557Sdlg {
17118871c557Sdlg 	va_list ap;
17128871c557Sdlg 
17138871c557Sdlg 	va_start(ap, fmt);
1714a52b97d1Sflorian 	syslog_vstrerror(errno, LOG_ERR, fmt, ap);
17158871c557Sdlg 	va_end(ap);
17168871c557Sdlg }
17178871c557Sdlg 
17188871c557Sdlg void
17198871c557Sdlg syslog_warnx(const char *fmt, ...)
17208871c557Sdlg {
17218871c557Sdlg 	va_list ap;
17228871c557Sdlg 
17238871c557Sdlg 	va_start(ap, fmt);
1724a52b97d1Sflorian 	vsyslog(LOG_ERR, fmt, ap);
17258871c557Sdlg 	va_end(ap);
17268871c557Sdlg }
17278871c557Sdlg 
17288871c557Sdlg void
17298871c557Sdlg syslog_info(const char *fmt, ...)
17308871c557Sdlg {
17318871c557Sdlg 	va_list ap;
17328871c557Sdlg 
17338871c557Sdlg 	va_start(ap, fmt);
17348871c557Sdlg 	vsyslog(LOG_INFO, fmt, ap);
17358871c557Sdlg 	va_end(ap);
17368871c557Sdlg }
17378871c557Sdlg 
17380e7810bdSflorian void
17390e7810bdSflorian syslog_debug(const char *fmt, ...)
17400e7810bdSflorian {
17410e7810bdSflorian 	va_list ap;
17420e7810bdSflorian 
17430e7810bdSflorian 	if (!debug)
17440e7810bdSflorian 		return;
17450e7810bdSflorian 
17460e7810bdSflorian 	va_start(ap, fmt);
17470e7810bdSflorian 	vsyslog(LOG_DEBUG, fmt, ap);
17480e7810bdSflorian 	va_end(ap);
17490e7810bdSflorian }
1750