tftpd.c revision 241848
1239268Sgonzo/*
2244469Scognet * Copyright (c) 1983, 1993
3239268Sgonzo *	The Regents of the University of California.  All rights reserved.
4239268Sgonzo *
5239268Sgonzo * Redistribution and use in source and binary forms, with or without
6239268Sgonzo * modification, are permitted provided that the following conditions
7239268Sgonzo * are met:
8239268Sgonzo * 1. Redistributions of source code must retain the above copyright
9239268Sgonzo *    notice, this list of conditions and the following disclaimer.
10239268Sgonzo * 2. Redistributions in binary form must reproduce the above copyright
11239268Sgonzo *    notice, this list of conditions and the following disclaimer in the
12239268Sgonzo *    documentation and/or other materials provided with the distribution.
13239268Sgonzo * 3. All advertising materials mentioning features or use of this software
14239268Sgonzo *    must display the following acknowledgement:
15239268Sgonzo *	This product includes software developed by the University of
16239268Sgonzo *	California, Berkeley and its contributors.
17239268Sgonzo * 4. Neither the name of the University nor the names of its contributors
18239268Sgonzo *    may be used to endorse or promote products derived from this software
19239268Sgonzo *    without specific prior written permission.
20239268Sgonzo *
21239268Sgonzo * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22239268Sgonzo * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23239268Sgonzo * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24239268Sgonzo * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25239268Sgonzo * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26239268Sgonzo * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27239268Sgonzo * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28239268Sgonzo * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29239268Sgonzo * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30239268Sgonzo * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31239268Sgonzo * SUCH DAMAGE.
32239268Sgonzo */
33239268Sgonzo
34239268Sgonzo#ifndef lint
35239268Sgonzostatic const char copyright[] =
36239268Sgonzo"@(#) Copyright (c) 1983, 1993\n\
37239268Sgonzo	The Regents of the University of California.  All rights reserved.\n";
38239268Sgonzo#endif /* not lint */
39239268Sgonzo
40239268Sgonzo#ifndef lint
41239268Sgonzo#if 0
42239268Sgonzostatic char sccsid[] = "@(#)tftpd.c	8.1 (Berkeley) 6/4/93";
43239268Sgonzo#endif
44244469Scognet#endif /* not lint */
45239268Sgonzo#include <sys/cdefs.h>
46239268Sgonzo__FBSDID("$FreeBSD: head/libexec/tftpd/tftpd.c 241848 2012-10-22 03:07:05Z eadler $");
47239268Sgonzo
48239268Sgonzo/*
49239268Sgonzo * Trivial file transfer protocol server.
50239268Sgonzo *
51239268Sgonzo * This version includes many modifications by Jim Guyton
52239268Sgonzo * <guyton@rand-unix>.
53239268Sgonzo */
54239268Sgonzo
55239268Sgonzo#include <sys/param.h>
56239268Sgonzo#include <sys/ioctl.h>
57239268Sgonzo#include <sys/stat.h>
58244469Scognet#include <sys/socket.h>
59244469Scognet
60239268Sgonzo#include <netinet/in.h>
61239268Sgonzo#include <arpa/tftp.h>
62239268Sgonzo
63239268Sgonzo#include <ctype.h>
64239268Sgonzo#include <errno.h>
65239268Sgonzo#include <fcntl.h>
66239268Sgonzo#include <netdb.h>
67239268Sgonzo#include <pwd.h>
68239268Sgonzo#include <stdio.h>
69239268Sgonzo#include <stdlib.h>
70239268Sgonzo#include <string.h>
71239268Sgonzo#include <syslog.h>
72239268Sgonzo#include <tcpd.h>
73239268Sgonzo#include <unistd.h>
74239268Sgonzo
75239268Sgonzo#include "tftp-file.h"
76239268Sgonzo#include "tftp-io.h"
77239268Sgonzo#include "tftp-utils.h"
78239268Sgonzo#include "tftp-transfer.h"
79239268Sgonzo#include "tftp-options.h"
80239268Sgonzo
81239268Sgonzostatic void	tftp_wrq(int peer, char *, ssize_t);
82239268Sgonzostatic void	tftp_rrq(int peer, char *, ssize_t);
83239268Sgonzo
84239268Sgonzo/*
85239268Sgonzo * Null-terminated directory prefix list for absolute pathname requests and
86239268Sgonzo * search list for relative pathname requests.
87239268Sgonzo *
88239268Sgonzo * MAXDIRS should be at least as large as the number of arguments that
89239268Sgonzo * inetd allows (currently 20).
90239268Sgonzo */
91239268Sgonzo#define MAXDIRS	20
92239268Sgonzostatic struct dirlist {
93239268Sgonzo	const char	*name;
94239268Sgonzo	int	len;
95239268Sgonzo} dirs[MAXDIRS+1];
96239268Sgonzostatic int	suppress_naks;
97239268Sgonzostatic int	logging;
98239268Sgonzostatic int	ipchroot;
99244469Scognetstatic int	create_new = 0;
100244469Scognetstatic const char *newfile_format = "%Y%m%d";
101244469Scognetstatic int	increase_name = 0;
102244469Scognetstatic mode_t	mask = S_IWGRP | S_IWOTH;
103244469Scognet
104244469Scognetstruct formats;
105244469Scognetstatic void	tftp_recvfile(int peer, const char *mode);
106239268Sgonzostatic void	tftp_xmitfile(int peer, const char *mode);
107244469Scognetstatic int	validate_access(int peer, char **, int);
108239268Sgonzostatic char	peername[NI_MAXHOST];
109239268Sgonzo
110239268Sgonzostatic FILE *file;
111239268Sgonzo
112239268Sgonzostatic struct formats {
113239268Sgonzo	const char	*f_mode;
114239268Sgonzo	int	f_convert;
115239268Sgonzo} formats[] = {
116239268Sgonzo	{ "netascii",	1 },
117239268Sgonzo	{ "octet",	0 },
118239268Sgonzo	{ NULL,		0 }
119239268Sgonzo};
120239268Sgonzo
121239268Sgonzoint
122239268Sgonzomain(int argc, char *argv[])
123239268Sgonzo{
124239268Sgonzo	struct tftphdr *tp;
125239268Sgonzo	int		peer;
126239268Sgonzo	socklen_t	peerlen, len;
127239268Sgonzo	ssize_t		n;
128239268Sgonzo	int		ch;
129239268Sgonzo	char		*chroot_dir = NULL;
130239268Sgonzo	struct passwd	*nobody;
131239268Sgonzo	const char	*chuser = "nobody";
132239268Sgonzo	char		recvbuffer[MAXPKTSIZE];
133239268Sgonzo	int		allow_ro = 1, allow_wo = 1;
134239268Sgonzo
135239268Sgonzo	tzset();			/* syslog in localtime */
136239268Sgonzo	acting_as_client = 0;
137239268Sgonzo
138239268Sgonzo	tftp_openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
139239268Sgonzo	while ((ch = getopt(argc, argv, "cCd:F:lnoOp:s:u:U:wW")) != -1) {
140239268Sgonzo		switch (ch) {
141239268Sgonzo		case 'c':
142239268Sgonzo			ipchroot = 1;
143239268Sgonzo			break;
144239268Sgonzo		case 'C':
145239268Sgonzo			ipchroot = 2;
146239268Sgonzo			break;
147239268Sgonzo		case 'd':
148239268Sgonzo			if (atoi(optarg) != 0)
149239268Sgonzo				debug += atoi(optarg);
150239268Sgonzo			else
151239268Sgonzo				debug |= debug_finds(optarg);
152239268Sgonzo			break;
153239268Sgonzo		case 'F':
154239268Sgonzo			newfile_format = optarg;
155239268Sgonzo			break;
156239268Sgonzo		case 'l':
157239268Sgonzo			logging = 1;
158239268Sgonzo			break;
159239268Sgonzo		case 'n':
160239268Sgonzo			suppress_naks = 1;
161239268Sgonzo			break;
162239268Sgonzo		case 'o':
163239268Sgonzo			options_rfc_enabled = 0;
164244469Scognet			break;
165244469Scognet		case 'O':
166239268Sgonzo			options_extra_enabled = 0;
167239268Sgonzo			break;
168239268Sgonzo		case 'p':
169239268Sgonzo			packetdroppercentage = atoi(optarg);
170239268Sgonzo			tftp_log(LOG_INFO,
171239268Sgonzo			    "Randomly dropping %d out of 100 packets",
172239268Sgonzo			    packetdroppercentage);
173239268Sgonzo			break;
174239268Sgonzo		case 's':
175239268Sgonzo			chroot_dir = optarg;
176239268Sgonzo			break;
177239268Sgonzo		case 'u':
178239268Sgonzo			chuser = optarg;
179239268Sgonzo			break;
180239268Sgonzo		case 'U':
181239268Sgonzo			mask = strtol(optarg, NULL, 0);
182239268Sgonzo			break;
183239268Sgonzo		case 'w':
184239268Sgonzo			create_new = 1;
185244469Scognet			break;
186244469Scognet		case 'W':
187244469Scognet			create_new = 1;
188244469Scognet			increase_name = 1;
189244469Scognet			break;
190244469Scognet		default:
191244469Scognet			tftp_log(LOG_WARNING,
192244469Scognet				"ignoring unknown option -%c", ch);
193244469Scognet		}
194244469Scognet	}
195244469Scognet	if (optind < argc) {
196244469Scognet		struct dirlist *dirp;
197244469Scognet
198244469Scognet		/* Get list of directory prefixes. Skip relative pathnames. */
199244469Scognet		for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS];
200244469Scognet		     optind++) {
201244469Scognet			if (argv[optind][0] == '/') {
202244469Scognet				dirp->name = argv[optind];
203244469Scognet				dirp->len  = strlen(dirp->name);
204244469Scognet				dirp++;
205244469Scognet			}
206244469Scognet		}
207244469Scognet	}
208244469Scognet	else if (chroot_dir) {
209244469Scognet		dirs->name = "/";
210244469Scognet		dirs->len = 1;
211244469Scognet	}
212244469Scognet	if (ipchroot > 0 && chroot_dir == NULL) {
213244469Scognet		tftp_log(LOG_ERR, "-c requires -s");
214244469Scognet		exit(1);
215244469Scognet	}
216244469Scognet
217239268Sgonzo	umask(mask);
218239268Sgonzo
219239268Sgonzo	{
220239268Sgonzo		int on = 1;
221239268Sgonzo		if (ioctl(0, FIONBIO, &on) < 0) {
222239268Sgonzo			tftp_log(LOG_ERR, "ioctl(FIONBIO): %s", strerror(errno));
223239268Sgonzo			exit(1);
224239268Sgonzo		}
225239268Sgonzo	}
226239268Sgonzo
227239268Sgonzo	/* Find out who we are talking to and what we are going to do */
228239268Sgonzo	peerlen = sizeof(peer_sock);
229239268Sgonzo	n = recvfrom(0, recvbuffer, MAXPKTSIZE, 0,
230239268Sgonzo	    (struct sockaddr *)&peer_sock, &peerlen);
231239268Sgonzo	if (n < 0) {
232239268Sgonzo		tftp_log(LOG_ERR, "recvfrom: %s", strerror(errno));
233239268Sgonzo		exit(1);
234239268Sgonzo	}
235239268Sgonzo	getnameinfo((struct sockaddr *)&peer_sock, peer_sock.ss_len,
236239268Sgonzo	    peername, sizeof(peername), NULL, 0, NI_NUMERICHOST);
237239268Sgonzo
238239268Sgonzo	/*
239239268Sgonzo	 * Now that we have read the message out of the UDP
240239268Sgonzo	 * socket, we fork and exit.  Thus, inetd will go back
241239268Sgonzo	 * to listening to the tftp port, and the next request
242239268Sgonzo	 * to come in will start up a new instance of tftpd.
243239268Sgonzo	 *
244239268Sgonzo	 * We do this so that inetd can run tftpd in "wait" mode.
245239268Sgonzo	 * The problem with tftpd running in "nowait" mode is that
246239268Sgonzo	 * inetd may get one or more successful "selects" on the
247239268Sgonzo	 * tftp port before we do our receive, so more than one
248239268Sgonzo	 * instance of tftpd may be started up.  Worse, if tftpd
249239268Sgonzo	 * break before doing the above "recvfrom", inetd would
250239268Sgonzo	 * spawn endless instances, clogging the system.
251239268Sgonzo	 */
252239268Sgonzo	{
253239268Sgonzo		int i, pid;
254239268Sgonzo
255239268Sgonzo		for (i = 1; i < 20; i++) {
256239268Sgonzo		    pid = fork();
257239268Sgonzo		    if (pid < 0) {
258239268Sgonzo				sleep(i);
259239268Sgonzo				/*
260239268Sgonzo				 * flush out to most recently sent request.
261239268Sgonzo				 *
262239268Sgonzo				 * This may drop some request, but those
263239268Sgonzo				 * will be resent by the clients when
264239268Sgonzo				 * they timeout.  The positive effect of
265239268Sgonzo				 * this flush is to (try to) prevent more
266239268Sgonzo				 * than one tftpd being started up to service
267239268Sgonzo				 * a single request from a single client.
268239268Sgonzo				 */
269239268Sgonzo				peerlen = sizeof peer_sock;
270239268Sgonzo				i = recvfrom(0, recvbuffer, MAXPKTSIZE, 0,
271239268Sgonzo				    (struct sockaddr *)&peer_sock, &peerlen);
272239268Sgonzo				if (i > 0) {
273239268Sgonzo					n = i;
274239268Sgonzo				}
275239268Sgonzo		    } else {
276239268Sgonzo				break;
277239268Sgonzo		    }
278239268Sgonzo		}
279239268Sgonzo		if (pid < 0) {
280239268Sgonzo			tftp_log(LOG_ERR, "fork: %s", strerror(errno));
281239268Sgonzo			exit(1);
282239268Sgonzo		} else if (pid != 0) {
283239268Sgonzo			exit(0);
284239268Sgonzo		}
285239268Sgonzo	}
286239268Sgonzo
287239268Sgonzo	/*
288239268Sgonzo	 * See if the client is allowed to talk to me.
289239268Sgonzo	 * (This needs to be done before the chroot())
290239268Sgonzo	 */
291239268Sgonzo	{
292239268Sgonzo		struct request_info req;
293239268Sgonzo
294239268Sgonzo		request_init(&req, RQ_CLIENT_ADDR, peername, 0);
295239268Sgonzo		request_set(&req, RQ_DAEMON, "tftpd", 0);
296239268Sgonzo
297239268Sgonzo		if (hosts_access(&req) == 0) {
298239268Sgonzo			if (debug&DEBUG_ACCESS)
299239268Sgonzo				tftp_log(LOG_WARNING,
300239268Sgonzo				    "Access denied by 'tftpd' entry "
301239268Sgonzo				    "in /etc/hosts.allow");
302239268Sgonzo
303239268Sgonzo			/*
304239268Sgonzo			 * Full access might be disabled, but maybe the
305239268Sgonzo			 * client is allowed to do read-only access.
306239268Sgonzo			 */
307239268Sgonzo			request_set(&req, RQ_DAEMON, "tftpd-ro", 0);
308239268Sgonzo			allow_ro = hosts_access(&req);
309239268Sgonzo
310239268Sgonzo			request_set(&req, RQ_DAEMON, "tftpd-wo", 0);
311239268Sgonzo			allow_wo = hosts_access(&req);
312239268Sgonzo
313239268Sgonzo			if (allow_ro == 0 && allow_wo == 0) {
314239268Sgonzo				tftp_log(LOG_WARNING,
315239268Sgonzo				    "Unauthorized access from %s", peername);
316239268Sgonzo				exit(1);
317239268Sgonzo			}
318239268Sgonzo
319239268Sgonzo			if (debug&DEBUG_ACCESS) {
320239268Sgonzo				if (allow_ro)
321239268Sgonzo					tftp_log(LOG_WARNING,
322239268Sgonzo					    "But allowed readonly access "
323239268Sgonzo					    "via 'tftpd-ro' entry");
324239268Sgonzo				if (allow_wo)
325239268Sgonzo					tftp_log(LOG_WARNING,
326239268Sgonzo					    "But allowed writeonly access "
327239268Sgonzo					    "via 'tftpd-wo' entry");
328239268Sgonzo			}
329239268Sgonzo		} else
330239268Sgonzo			if (debug&DEBUG_ACCESS)
331239268Sgonzo				tftp_log(LOG_WARNING,
332239268Sgonzo				    "Full access allowed"
333239268Sgonzo				    "in /etc/hosts.allow");
334239268Sgonzo	}
335239268Sgonzo
336239268Sgonzo	/*
337239268Sgonzo	 * Since we exit here, we should do that only after the above
338239268Sgonzo	 * recvfrom to keep inetd from constantly forking should there
339239268Sgonzo	 * be a problem.  See the above comment about system clogging.
340239268Sgonzo	 */
341239268Sgonzo	if (chroot_dir) {
342239268Sgonzo		if (ipchroot > 0) {
343239268Sgonzo			char *tempchroot;
344239268Sgonzo			struct stat sb;
345239268Sgonzo			int statret;
346239268Sgonzo			struct sockaddr_storage ss;
347239268Sgonzo			char hbuf[NI_MAXHOST];
348239268Sgonzo
349239268Sgonzo			statret = -1;
350239268Sgonzo			memcpy(&ss, &peer_sock, peer_sock.ss_len);
351239268Sgonzo			unmappedaddr((struct sockaddr_in6 *)&ss);
352239268Sgonzo			getnameinfo((struct sockaddr *)&ss, ss.ss_len,
353239268Sgonzo				    hbuf, sizeof(hbuf), NULL, 0,
354239268Sgonzo				    NI_NUMERICHOST);
355239268Sgonzo			asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf);
356239268Sgonzo			if (ipchroot == 2)
357239268Sgonzo				statret = stat(tempchroot, &sb);
358239268Sgonzo			if (ipchroot == 1 ||
359239268Sgonzo			    (statret == 0 && (sb.st_mode & S_IFDIR)))
360239268Sgonzo				chroot_dir = tempchroot;
361239268Sgonzo		}
362239268Sgonzo		/* Must get this before chroot because /etc might go away */
363239268Sgonzo		if ((nobody = getpwnam(chuser)) == NULL) {
364239268Sgonzo			tftp_log(LOG_ERR, "%s: no such user", chuser);
365239268Sgonzo			exit(1);
366239268Sgonzo		}
367239268Sgonzo		if (chroot(chroot_dir)) {
368239268Sgonzo			tftp_log(LOG_ERR, "chroot: %s: %s",
369239268Sgonzo			    chroot_dir, strerror(errno));
370244469Scognet			exit(1);
371244469Scognet		}
372244469Scognet		chdir("/");
373244469Scognet		setgroups(1, &nobody->pw_gid);
374244469Scognet		if (setuid(nobody->pw_uid) != 0) {
375244469Scognet			tftp_log(LOG_ERR, "setuid failed");
376244469Scognet			exit(1);
377244469Scognet		}
378244469Scognet	}
379244469Scognet
380244469Scognet	len = sizeof(me_sock);
381244469Scognet	if (getsockname(0, (struct sockaddr *)&me_sock, &len) == 0) {
382239268Sgonzo		switch (me_sock.ss_family) {
383239268Sgonzo		case AF_INET:
384239268Sgonzo			((struct sockaddr_in *)&me_sock)->sin_port = 0;
385239268Sgonzo			break;
386239268Sgonzo		case AF_INET6:
387239268Sgonzo			((struct sockaddr_in6 *)&me_sock)->sin6_port = 0;
388239268Sgonzo			break;
389239268Sgonzo		default:
390239268Sgonzo			/* unsupported */
391239268Sgonzo			break;
392239268Sgonzo		}
393239268Sgonzo	} else {
394239268Sgonzo		memset(&me_sock, 0, sizeof(me_sock));
395239268Sgonzo		me_sock.ss_family = peer_sock.ss_family;
396239268Sgonzo		me_sock.ss_len = peer_sock.ss_len;
397239268Sgonzo	}
398239268Sgonzo	close(0);
399239268Sgonzo	close(1);
400239268Sgonzo	peer = socket(peer_sock.ss_family, SOCK_DGRAM, 0);
401239268Sgonzo	if (peer < 0) {
402239268Sgonzo		tftp_log(LOG_ERR, "socket: %s", strerror(errno));
403239268Sgonzo		exit(1);
404239268Sgonzo	}
405239268Sgonzo	if (bind(peer, (struct sockaddr *)&me_sock, me_sock.ss_len) < 0) {
406239268Sgonzo		tftp_log(LOG_ERR, "bind: %s", strerror(errno));
407239268Sgonzo		exit(1);
408239268Sgonzo	}
409239268Sgonzo
410239268Sgonzo	tp = (struct tftphdr *)recvbuffer;
411239268Sgonzo	tp->th_opcode = ntohs(tp->th_opcode);
412239268Sgonzo	if (tp->th_opcode == RRQ) {
413239268Sgonzo		if (allow_ro)
414239268Sgonzo			tftp_rrq(peer, tp->th_stuff, n - 1);
415239268Sgonzo		else {
416239268Sgonzo			tftp_log(LOG_WARNING,
417239268Sgonzo			    "%s read access denied", peername);
418239268Sgonzo			exit(1);
419239268Sgonzo		}
420239268Sgonzo	}
421239268Sgonzo	if (tp->th_opcode == WRQ) {
422239268Sgonzo		if (allow_wo)
423239268Sgonzo			tftp_wrq(peer, tp->th_stuff, n - 1);
424239268Sgonzo		else {
425239268Sgonzo			tftp_log(LOG_WARNING,
426239268Sgonzo			    "%s write access denied", peername);
427239268Sgonzo			exit(1);
428239268Sgonzo		}
429239268Sgonzo	}
430239268Sgonzo	exit(1);
431239268Sgonzo}
432239268Sgonzo
433239268Sgonzostatic void
434239268Sgonzoreduce_path(char *fn)
435239268Sgonzo{
436239268Sgonzo	char *slash, *ptr;
437239268Sgonzo
438239268Sgonzo	/* Reduce all "/+./" to "/" (just in case we've got "/./../" later */
439239268Sgonzo	while ((slash = strstr(fn, "/./")) != NULL) {
440239268Sgonzo		for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--)
441239268Sgonzo			;
442239268Sgonzo		slash += 2;
443239268Sgonzo		while (*slash)
444239268Sgonzo			*++ptr = *++slash;
445239268Sgonzo	}
446239268Sgonzo
447239268Sgonzo	/* Now reduce all "/something/+../" to "/" */
448239268Sgonzo	while ((slash = strstr(fn, "/../")) != NULL) {
449239268Sgonzo		if (slash == fn)
450239268Sgonzo			break;
451239268Sgonzo		for (ptr = slash; ptr > fn && ptr[-1] == '/'; ptr--)
452239268Sgonzo			;
453239268Sgonzo		for (ptr--; ptr >= fn; ptr--)
454239268Sgonzo			if (*ptr == '/')
455239268Sgonzo				break;
456239268Sgonzo		if (ptr < fn)
457239268Sgonzo			break;
458239268Sgonzo		slash += 3;
459239268Sgonzo		while (*slash)
460239268Sgonzo			*++ptr = *++slash;
461239268Sgonzo	}
462239268Sgonzo}
463239268Sgonzo
464239268Sgonzostatic char *
465239268Sgonzoparse_header(int peer, char *recvbuffer, ssize_t size,
466239268Sgonzo	char **filename, char **mode)
467239268Sgonzo{
468239268Sgonzo	char	*cp;
469239268Sgonzo	int	i;
470244469Scognet	struct formats *pf;
471244469Scognet
472239268Sgonzo	*mode = NULL;
473239268Sgonzo	cp = recvbuffer;
474239268Sgonzo
475239268Sgonzo	i = get_field(peer, recvbuffer, size);
476239268Sgonzo	if (i >= PATH_MAX) {
477239268Sgonzo		tftp_log(LOG_ERR, "Bad option - filename too long");
478239268Sgonzo		send_error(peer, EBADOP);
479239268Sgonzo		exit(1);
480239268Sgonzo	}
481239268Sgonzo	*filename = recvbuffer;
482239268Sgonzo	tftp_log(LOG_INFO, "Filename: '%s'", *filename);
483239268Sgonzo	cp += i;
484239268Sgonzo
485239268Sgonzo	i = get_field(peer, cp, size);
486239268Sgonzo	*mode = cp;
487239268Sgonzo	cp += i;
488239268Sgonzo
489239268Sgonzo	/* Find the file transfer mode */
490239268Sgonzo	for (cp = *mode; *cp; cp++)
491239268Sgonzo		if (isupper(*cp))
492239268Sgonzo			*cp = tolower(*cp);
493239268Sgonzo	for (pf = formats; pf->f_mode; pf++)
494239268Sgonzo		if (strcmp(pf->f_mode, *mode) == 0)
495239268Sgonzo			break;
496239268Sgonzo	if (pf->f_mode == NULL) {
497239268Sgonzo		tftp_log(LOG_ERR,
498239268Sgonzo		    "Bad option - Unknown transfer mode (%s)", *mode);
499239268Sgonzo		send_error(peer, EBADOP);
500239268Sgonzo		exit(1);
501239268Sgonzo	}
502239268Sgonzo	tftp_log(LOG_INFO, "Mode: '%s'", *mode);
503239268Sgonzo
504239268Sgonzo	return (cp + 1);
505239268Sgonzo}
506239268Sgonzo
507239268Sgonzo/*
508239268Sgonzo * WRQ - receive a file from the client
509239268Sgonzo */
510239268Sgonzovoid
511239268Sgonzotftp_wrq(int peer, char *recvbuffer, ssize_t size)
512239268Sgonzo{
513239268Sgonzo	char *cp;
514239268Sgonzo	int has_options = 0, ecode;
515239268Sgonzo	char *filename, *mode;
516239268Sgonzo	char fnbuf[PATH_MAX];
517239268Sgonzo
518239268Sgonzo	cp = parse_header(peer, recvbuffer, size, &filename, &mode);
519239268Sgonzo	size -= (cp - recvbuffer) + 1;
520239268Sgonzo
521239268Sgonzo	strcpy(fnbuf, filename);
522239268Sgonzo	reduce_path(fnbuf);
523239268Sgonzo	filename = fnbuf;
524239268Sgonzo
525239268Sgonzo	if (size > 0) {
526239268Sgonzo		if (options_rfc_enabled)
527239268Sgonzo			has_options = !parse_options(peer, cp, size);
528239268Sgonzo		else
529239268Sgonzo			tftp_log(LOG_INFO, "Options found but not enabled");
530239268Sgonzo	}
531239268Sgonzo
532239268Sgonzo	ecode = validate_access(peer, &filename, WRQ);
533239268Sgonzo	if (ecode == 0) {
534239268Sgonzo		if (has_options)
535239268Sgonzo			send_oack(peer);
536239268Sgonzo		else
537239268Sgonzo			send_ack(peer, 0);
538239268Sgonzo	}
539239268Sgonzo	if (logging) {
540239268Sgonzo		tftp_log(LOG_INFO, "%s: write request for %s: %s", peername,
541239268Sgonzo			    filename, errtomsg(ecode));
542239268Sgonzo	}
543239268Sgonzo
544239268Sgonzo	tftp_recvfile(peer, mode);
545239268Sgonzo	exit(0);
546239268Sgonzo}
547239268Sgonzo
548239268Sgonzo/*
549239268Sgonzo * RRQ - send a file to the client
550239268Sgonzo */
551239268Sgonzovoid
552239268Sgonzotftp_rrq(int peer, char *recvbuffer, ssize_t size)
553239268Sgonzo{
554239268Sgonzo	char *cp;
555239268Sgonzo	int has_options = 0, ecode;
556239268Sgonzo	char *filename, *mode;
557239268Sgonzo	char	fnbuf[PATH_MAX];
558239268Sgonzo
559239268Sgonzo	cp = parse_header(peer, recvbuffer, size, &filename, &mode);
560239268Sgonzo	size -= (cp - recvbuffer) + 1;
561239268Sgonzo
562239268Sgonzo	strcpy(fnbuf, filename);
563239268Sgonzo	reduce_path(fnbuf);
564239268Sgonzo	filename = fnbuf;
565239268Sgonzo
566239268Sgonzo	if (size > 0) {
567239268Sgonzo		if (options_rfc_enabled)
568239268Sgonzo			has_options = !parse_options(peer, cp, size);
569239268Sgonzo		else
570239268Sgonzo			tftp_log(LOG_INFO, "Options found but not enabled");
571239268Sgonzo	}
572239268Sgonzo
573239268Sgonzo	ecode = validate_access(peer, &filename, RRQ);
574239268Sgonzo	if (ecode == 0) {
575239268Sgonzo		if (has_options) {
576239268Sgonzo			int n;
577239268Sgonzo			char lrecvbuffer[MAXPKTSIZE];
578239268Sgonzo			struct tftphdr *rp = (struct tftphdr *)lrecvbuffer;
579239268Sgonzo
580239268Sgonzo			send_oack(peer);
581239268Sgonzo			n = receive_packet(peer, lrecvbuffer, MAXPKTSIZE,
582239268Sgonzo				NULL, timeoutpacket);
583239268Sgonzo			if (n < 0) {
584239268Sgonzo				if (debug&DEBUG_SIMPLE)
585239268Sgonzo					tftp_log(LOG_DEBUG, "Aborting: %s",
586239268Sgonzo					    rp_strerror(n));
587239268Sgonzo				return;
588239268Sgonzo			}
589239268Sgonzo			if (rp->th_opcode != ACK) {
590239268Sgonzo				if (debug&DEBUG_SIMPLE)
591239268Sgonzo					tftp_log(LOG_DEBUG,
592239268Sgonzo					    "Expected ACK, got %s on OACK",
593239268Sgonzo					    packettype(rp->th_opcode));
594239268Sgonzo				return;
595239268Sgonzo			}
596239268Sgonzo		}
597239268Sgonzo	}
598239268Sgonzo
599239268Sgonzo	if (logging)
600239268Sgonzo		tftp_log(LOG_INFO, "%s: read request for %s: %s", peername,
601239268Sgonzo			    filename, errtomsg(ecode));
602239268Sgonzo
603239268Sgonzo	if (ecode) {
604239268Sgonzo		/*
605244469Scognet		 * Avoid storms of naks to a RRQ broadcast for a relative
606244469Scognet		 * bootfile pathname from a diskless Sun.
607244469Scognet		 */
608244469Scognet		if (suppress_naks && *filename != '/' && ecode == ENOTFOUND)
609239268Sgonzo			exit(0);
610239268Sgonzo		send_error(peer, ecode);
611239268Sgonzo		exit(1);
612239268Sgonzo	}
613239268Sgonzo	tftp_xmitfile(peer, mode);
614239268Sgonzo}
615239268Sgonzo
616239268Sgonzo/*
617239268Sgonzo * Find the next value for YYYYMMDD.nn when the file to be written should
618239268Sgonzo * be unique. Due to the limitations of nn, we will fail if nn reaches 100.
619239268Sgonzo * Besides, that is four updates per hour on a file, which is kind of
620239268Sgonzo * execessive anyway.
621239268Sgonzo */
622239268Sgonzostatic int
623239268Sgonzofind_next_name(char *filename, int *fd)
624239268Sgonzo{
625239268Sgonzo	int i;
626239268Sgonzo	time_t tval;
627239268Sgonzo	size_t len;
628239268Sgonzo	struct tm lt;
629239268Sgonzo	char yyyymmdd[MAXPATHLEN];
630239268Sgonzo	char newname[MAXPATHLEN];
631239268Sgonzo
632239268Sgonzo	/* Create the YYYYMMDD part of the filename */
633239268Sgonzo	time(&tval);
634239268Sgonzo	lt = *localtime(&tval);
635239268Sgonzo	len = strftime(yyyymmdd, sizeof(yyyymmdd), newfile_format, &lt);
636239268Sgonzo	if (len == 0) {
637239268Sgonzo		syslog(LOG_WARNING,
638239268Sgonzo			"Filename suffix too long (%d characters maximum)",
639239268Sgonzo			MAXPATHLEN);
640239268Sgonzo		return (EACCESS);
641239268Sgonzo	}
642244469Scognet
643244469Scognet	/* Make sure the new filename is not too long */
644244469Scognet	if (strlen(filename) > MAXPATHLEN - len - 5) {
645244469Scognet		syslog(LOG_WARNING,
646244469Scognet			"Filename too long (%zd characters, %zd maximum)",
647244469Scognet			strlen(filename), MAXPATHLEN - len - 5);
648244469Scognet		return (EACCESS);
649244469Scognet	}
650244469Scognet
651244469Scognet	/* Find the first file which doesn't exist */
652244469Scognet	for (i = 0; i < 100; i++) {
653244469Scognet		sprintf(newname, "%s.%s.%02d", filename, yyyymmdd, i);
654244469Scognet		*fd = open(newname,
655239268Sgonzo		    O_WRONLY | O_CREAT | O_EXCL,
656244469Scognet		    S_IRUSR | S_IWUSR | S_IRGRP |
657244469Scognet		    S_IWGRP | S_IROTH | S_IWOTH);
658244469Scognet		if (*fd > 0)
659244469Scognet			return 0;
660239268Sgonzo	}
661244469Scognet
662244469Scognet	return (EEXIST);
663244469Scognet}
664244469Scognet
665244469Scognet/*
666244469Scognet * Validate file access.  Since we
667244469Scognet * have no uid or gid, for now require
668244469Scognet * file to exist and be publicly
669244469Scognet * readable/writable.
670244469Scognet * If we were invoked with arguments
671244469Scognet * from inetd then the file must also be
672244469Scognet * in one of the given directory prefixes.
673244469Scognet * Note also, full path name must be
674244469Scognet * given as we have no login directory.
675244469Scognet */
676244469Scognetint
677244469Scognetvalidate_access(int peer, char **filep, int mode)
678244469Scognet{
679244469Scognet	struct stat stbuf;
680244469Scognet	int	fd;
681244469Scognet	int	error;
682244469Scognet	struct dirlist *dirp;
683239268Sgonzo	static char pathname[MAXPATHLEN];
684244469Scognet	char *filename = *filep;
685244469Scognet
686244469Scognet	/*
687239268Sgonzo	 * Prevent tricksters from getting around the directory restrictions
688244469Scognet	 */
689244469Scognet	if (strstr(filename, "/../"))
690239268Sgonzo		return (EACCESS);
691239268Sgonzo
692239268Sgonzo	if (*filename == '/') {
693239268Sgonzo		/*
694239268Sgonzo		 * Allow the request if it's in one of the approved locations.
695239268Sgonzo		 * Special case: check the null prefix ("/") by looking
696239268Sgonzo		 * for length = 1 and relying on the arg. processing that
697239268Sgonzo		 * it's a /.
698239268Sgonzo		 */
699239268Sgonzo		for (dirp = dirs; dirp->name != NULL; dirp++) {
700239268Sgonzo			if (dirp->len == 1 ||
701239268Sgonzo			    (!strncmp(filename, dirp->name, dirp->len) &&
702239268Sgonzo			     filename[dirp->len] == '/'))
703239268Sgonzo				    break;
704239268Sgonzo		}
705239268Sgonzo		/* If directory list is empty, allow access to any file */
706239268Sgonzo		if (dirp->name == NULL && dirp != dirs)
707239268Sgonzo			return (EACCESS);
708239268Sgonzo		if (stat(filename, &stbuf) < 0)
709239268Sgonzo			return (errno == ENOENT ? ENOTFOUND : EACCESS);
710239268Sgonzo		if ((stbuf.st_mode & S_IFMT) != S_IFREG)
711239268Sgonzo			return (ENOTFOUND);
712239268Sgonzo		if (mode == RRQ) {
713244469Scognet			if ((stbuf.st_mode & S_IROTH) == 0)
714244469Scognet				return (EACCESS);
715239268Sgonzo		} else {
716244469Scognet			if ((stbuf.st_mode & S_IWOTH) == 0)
717244469Scognet				return (EACCESS);
718244469Scognet		}
719244469Scognet	} else {
720244469Scognet		int err;
721244469Scognet
722244469Scognet		/*
723244469Scognet		 * Relative file name: search the approved locations for it.
724244469Scognet		 * Don't allow write requests that avoid directory
725244469Scognet		 * restrictions.
726244469Scognet		 */
727244469Scognet
728244469Scognet		if (!strncmp(filename, "../", 3))
729244469Scognet			return (EACCESS);
730244469Scognet
731239268Sgonzo		/*
732239268Sgonzo		 * If the file exists in one of the directories and isn't
733239268Sgonzo		 * readable, continue looking. However, change the error code
734239268Sgonzo		 * to give an indication that the file exists.
735239268Sgonzo		 */
736239268Sgonzo		err = ENOTFOUND;
737239268Sgonzo		for (dirp = dirs; dirp->name != NULL; dirp++) {
738239268Sgonzo			snprintf(pathname, sizeof(pathname), "%s/%s",
739239268Sgonzo				dirp->name, filename);
740239268Sgonzo			if (stat(pathname, &stbuf) == 0 &&
741239268Sgonzo			    (stbuf.st_mode & S_IFMT) == S_IFREG) {
742239268Sgonzo				if ((stbuf.st_mode & S_IROTH) != 0) {
743239268Sgonzo					break;
744239268Sgonzo				}
745239268Sgonzo				err = EACCESS;
746239268Sgonzo			}
747239268Sgonzo		}
748239268Sgonzo		if (dirp->name != NULL)
749239268Sgonzo			*filep = filename = pathname;
750239268Sgonzo		else if (mode == RRQ)
751239268Sgonzo			return (err);
752239268Sgonzo	}
753239268Sgonzo
754239268Sgonzo	/*
755239268Sgonzo	 * This option is handled here because it (might) require(s) the
756239268Sgonzo	 * size of the file.
757239268Sgonzo	 */
758239268Sgonzo	option_tsize(peer, NULL, mode, &stbuf);
759239268Sgonzo
760239268Sgonzo	if (mode == RRQ)
761239268Sgonzo		fd = open(filename, O_RDONLY);
762239268Sgonzo	else {
763239268Sgonzo		if (create_new) {
764239268Sgonzo			if (increase_name) {
765239268Sgonzo				error = find_next_name(filename, &fd);
766239268Sgonzo				if (error > 0)
767239268Sgonzo					return (error + 100);
768239268Sgonzo			} else
769239268Sgonzo				fd = open(filename,
770239268Sgonzo				    O_WRONLY | O_TRUNC | O_CREAT,
771239268Sgonzo				    S_IRUSR | S_IWUSR | S_IRGRP |
772239268Sgonzo				    S_IWGRP | S_IROTH | S_IWOTH );
773239268Sgonzo		} else
774239268Sgonzo			fd = open(filename, O_WRONLY | O_TRUNC);
775239268Sgonzo	}
776239268Sgonzo	if (fd < 0)
777239268Sgonzo		return (errno + 100);
778239268Sgonzo	file = fdopen(fd, (mode == RRQ)? "r":"w");
779239268Sgonzo	if (file == NULL) {
780239268Sgonzo		close(fd);
781239268Sgonzo		return (errno + 100);
782239268Sgonzo	}
783239268Sgonzo	return (0);
784239268Sgonzo}
785239268Sgonzo
786239268Sgonzostatic void
787239268Sgonzotftp_xmitfile(int peer, const char *mode)
788239268Sgonzo{
789239268Sgonzo	uint16_t block;
790239268Sgonzo	time_t now;
791239268Sgonzo	struct tftp_stats ts;
792239268Sgonzo
793239268Sgonzo	now = time(NULL);
794239268Sgonzo	if (debug&DEBUG_SIMPLE)
795239268Sgonzo		tftp_log(LOG_DEBUG, "Transmitting file");
796239268Sgonzo
797239268Sgonzo	read_init(0, file, mode);
798239268Sgonzo	block = 1;
799239268Sgonzo	tftp_send(peer, &block, &ts);
800239268Sgonzo	read_close();
801239268Sgonzo	if (debug&DEBUG_SIMPLE)
802239268Sgonzo		tftp_log(LOG_INFO, "Sent %d bytes in %d seconds",
803239268Sgonzo		    ts.amount, time(NULL) - now);
804239268Sgonzo}
805239268Sgonzo
806239268Sgonzostatic void
807239268Sgonzotftp_recvfile(int peer, const char *mode)
808239268Sgonzo{
809239268Sgonzo	uint16_t block;
810239268Sgonzo	struct timeval now1, now2;
811239268Sgonzo	struct tftp_stats ts;
812239268Sgonzo
813239268Sgonzo	gettimeofday(&now1, NULL);
814239268Sgonzo	if (debug&DEBUG_SIMPLE)
815239268Sgonzo		tftp_log(LOG_DEBUG, "Receiving file");
816239268Sgonzo
817239268Sgonzo	write_init(0, file, mode);
818239268Sgonzo
819239268Sgonzo	block = 0;
820239268Sgonzo	tftp_receive(peer, &block, &ts, NULL, 0);
821239268Sgonzo
822239268Sgonzo	write_close();
823239268Sgonzo	gettimeofday(&now2, NULL);
824239268Sgonzo
825239268Sgonzo	if (debug&DEBUG_SIMPLE) {
826239268Sgonzo		double f;
827239268Sgonzo		if (now1.tv_usec > now2.tv_usec) {
828239268Sgonzo			now2.tv_usec += 1000000;
829239268Sgonzo			now2.tv_sec--;
830239268Sgonzo		}
831239268Sgonzo
832239268Sgonzo		f = now2.tv_sec - now1.tv_sec +
833239268Sgonzo		    (now2.tv_usec - now1.tv_usec) / 100000.0;
834239268Sgonzo		tftp_log(LOG_INFO,
835239268Sgonzo		    "Download of %d bytes in %d blocks completed after %0.1f seconds\n",
836239268Sgonzo		    ts.amount, block, f);
837239268Sgonzo	}
838239268Sgonzo
839239268Sgonzo	return;
840239268Sgonzo}
841239268Sgonzo