1*ef0b7ea3Sshm /* $NetBSD: tftpd.c,v 1.45 2016/07/20 20:18:21 shm Exp $ */
260cef501Smrg
361f28255Scgd /*
460cef501Smrg * Copyright (c) 1983, 1993
560cef501Smrg * The Regents of the University of California. All rights reserved.
661f28255Scgd *
761f28255Scgd * Redistribution and use in source and binary forms, with or without
861f28255Scgd * modification, are permitted provided that the following conditions
961f28255Scgd * are met:
1061f28255Scgd * 1. Redistributions of source code must retain the above copyright
1161f28255Scgd * notice, this list of conditions and the following disclaimer.
1261f28255Scgd * 2. Redistributions in binary form must reproduce the above copyright
1361f28255Scgd * notice, this list of conditions and the following disclaimer in the
1461f28255Scgd * documentation and/or other materials provided with the distribution.
158e6ab883Sagc * 3. Neither the name of the University nor the names of its contributors
1661f28255Scgd * may be used to endorse or promote products derived from this software
1761f28255Scgd * without specific prior written permission.
1861f28255Scgd *
1961f28255Scgd * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2061f28255Scgd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2161f28255Scgd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2261f28255Scgd * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2361f28255Scgd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2461f28255Scgd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2561f28255Scgd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2661f28255Scgd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2761f28255Scgd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2861f28255Scgd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2961f28255Scgd * SUCH DAMAGE.
3061f28255Scgd */
3161f28255Scgd
3260cef501Smrg #include <sys/cdefs.h>
3361f28255Scgd #ifndef lint
34f0bccc0fSlukem __COPYRIGHT("@(#) Copyright (c) 1983, 1993\
35f0bccc0fSlukem The Regents of the University of California. All rights reserved.");
3660cef501Smrg #if 0
3760cef501Smrg static char sccsid[] = "@(#)tftpd.c 8.1 (Berkeley) 6/4/93";
3860cef501Smrg #else
39*ef0b7ea3Sshm __RCSID("$NetBSD: tftpd.c,v 1.45 2016/07/20 20:18:21 shm Exp $");
4060cef501Smrg #endif
4161f28255Scgd #endif /* not lint */
4261f28255Scgd
4361f28255Scgd /*
4461f28255Scgd * Trivial file transfer protocol server.
4561f28255Scgd *
4660cef501Smrg * This version includes many modifications by Jim Guyton
4760cef501Smrg * <guyton@rand-unix>.
4861f28255Scgd */
4961f28255Scgd
5060cef501Smrg #include <sys/param.h>
5161f28255Scgd #include <sys/ioctl.h>
5261f28255Scgd #include <sys/stat.h>
5360cef501Smrg #include <sys/socket.h>
5460cef501Smrg
5561f28255Scgd #include <netinet/in.h>
5661f28255Scgd #include <arpa/tftp.h>
5760cef501Smrg #include <arpa/inet.h>
5861f28255Scgd
5961f28255Scgd #include <ctype.h>
6060cef501Smrg #include <errno.h>
6160cef501Smrg #include <fcntl.h>
6215b3c2bfSlukem #include <grp.h>
6360cef501Smrg #include <netdb.h>
6415b3c2bfSlukem #include <pwd.h>
6560cef501Smrg #include <setjmp.h>
6660cef501Smrg #include <signal.h>
6760cef501Smrg #include <stdio.h>
6861f28255Scgd #include <stdlib.h>
6960cef501Smrg #include <string.h>
7060cef501Smrg #include <syslog.h>
71a87145f3Skleink #include <time.h>
7260cef501Smrg #include <unistd.h>
7360cef501Smrg
7460cef501Smrg #include "tftpsubs.h"
7561f28255Scgd
7615b3c2bfSlukem #define DEFAULTUSER "nobody"
77ad6a2b10Sjtc
7861f28255Scgd #define TIMEOUT 5
7961f28255Scgd
8073a2253aSchristos static int peer;
8173a2253aSchristos static int rexmtval = TIMEOUT;
8273a2253aSchristos static int maxtimeout = 5*TIMEOUT;
8361f28255Scgd
8473a2253aSchristos static char buf[MAXPKTSIZE];
8573a2253aSchristos static char ackbuf[PKTSIZE];
8673a2253aSchristos static char oackbuf[PKTSIZE];
8773a2253aSchristos static struct sockaddr_storage from;
8873a2253aSchristos static socklen_t fromlen;
8973a2253aSchristos static int debug;
9061f28255Scgd
9173a2253aSchristos static int tftp_opt_tsize = 0;
9273a2253aSchristos static int tftp_blksize = SEGSIZE;
9373a2253aSchristos static int tftp_tsize = 0;
9444411286Sbriggs
9560cef501Smrg /*
9660cef501Smrg * Null-terminated directory prefix list for absolute pathname requests and
9760cef501Smrg * search list for relative pathname requests.
9860cef501Smrg *
9960cef501Smrg * MAXDIRS should be at least as large as the number of arguments that
10060cef501Smrg * inetd allows (currently 20).
10160cef501Smrg */
10260cef501Smrg #define MAXDIRS 20
10360cef501Smrg static struct dirlist {
10460cef501Smrg char *name;
10560cef501Smrg int len;
10660cef501Smrg } dirs[MAXDIRS+1];
10760cef501Smrg static int suppress_naks;
10860cef501Smrg static int logging;
10960cef501Smrg static int secure;
11073a2253aSchristos static char pathsep = '\0';
11160cef501Smrg static char *securedir;
112cb578cacShubertf static int unrestricted_writes; /* uploaded files don't have to exist */
1133a2e9669Sbuhrow static int broadcast_client = 0; /* Some clients ack to the broadcast address */
11461f28255Scgd
11560cef501Smrg struct formats;
11660cef501Smrg
117edc4b408Slukem static const char *errtomsg(int);
118edc4b408Slukem static void nak(int);
1199eba1e42Sjoerg __dead static void tftp(struct tftphdr *, int);
1209eba1e42Sjoerg __dead static void usage(void);
121edc4b408Slukem static char *verifyhost(struct sockaddr *);
1229eba1e42Sjoerg __dead static void justquit(int);
12373a2253aSchristos static void recvfile(struct formats *, int, int);
12473a2253aSchristos static void sendfile(struct formats *, int, int);
1259eba1e42Sjoerg __dead static void timer(int);
126d8c9b346Schristos static const char *opcode(int);
12773a2253aSchristos static int validate_access(char **, int);
12860cef501Smrg
12973a2253aSchristos static struct formats {
130edc4b408Slukem const char *f_mode;
131edc4b408Slukem int (*f_validate)(char **, int);
13244411286Sbriggs void (*f_send)(struct formats *, int, int);
13344411286Sbriggs void (*f_recv)(struct formats *, int, int);
13460cef501Smrg int f_convert;
13560cef501Smrg } formats[] = {
13660cef501Smrg { "netascii", validate_access, sendfile, recvfile, 1 },
13760cef501Smrg { "octet", validate_access, sendfile, recvfile, 0 },
13873a2253aSchristos { .f_mode = NULL }
13960cef501Smrg };
14045fe1558Smycroft
1419fd1fef0Scgd static void
usage(void)142edc4b408Slukem usage(void)
1439fd1fef0Scgd {
144edc4b408Slukem
14515b3c2bfSlukem syslog(LOG_ERR,
1463a2e9669Sbuhrow "Usage: %s [-bcdln] [-g group] [-p pathsep] [-s directory] [-u user] [directory ...]",
14765a10264Scgd getprogname());
1489fd1fef0Scgd exit(1);
1499fd1fef0Scgd }
1509fd1fef0Scgd
15160cef501Smrg int
main(int argc,char * argv[])152edc4b408Slukem main(int argc, char *argv[])
15361f28255Scgd {
154edc4b408Slukem struct sockaddr_storage me;
15515b3c2bfSlukem struct passwd *pwent;
15615b3c2bfSlukem struct group *grent;
15715b3c2bfSlukem struct tftphdr *tp;
15873a2253aSchristos const char *tgtuser, *tgtgroup;
15973a2253aSchristos char *ep;
160edc4b408Slukem int n, ch, on, fd;
1610c37c63eSmrg int soopt;
1620c37c63eSmrg socklen_t len;
16315b3c2bfSlukem uid_t curuid, tgtuid;
16415b3c2bfSlukem gid_t curgid, tgtgid;
16515b3c2bfSlukem long nid;
16661f28255Scgd
167edc4b408Slukem n = 0;
168edc4b408Slukem fd = 0;
169a87145f3Skleink tzset();
17052e679a1Slukem openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
17115b3c2bfSlukem tgtuser = DEFAULTUSER;
17215b3c2bfSlukem tgtgroup = NULL;
17315b3c2bfSlukem curuid = getuid();
17415b3c2bfSlukem curgid = getgid();
1759fd1fef0Scgd
1763a2e9669Sbuhrow while ((ch = getopt(argc, argv, "bcdg:lnp:s:u:")) != -1)
17760cef501Smrg switch (ch) {
1783a2e9669Sbuhrow case 'b':
1793a2e9669Sbuhrow /*
1803a2e9669Sbuhrow * Some clients, notably older Cisco boot loaders,
1813a2e9669Sbuhrow * send their acknowledgements to the broadcast address
1823a2e9669Sbuhrow * rather than the unicast address of the server.
1833a2e9669Sbuhrow * Allow those clients to inter-operate with us.
1843a2e9669Sbuhrow * It's worth noting that this interaction doesn't cause the
1853a2e9669Sbuhrow * server to change where it sends the responses, meaning
1863a2e9669Sbuhrow * servers that have this flag enabled are no more
1873a2e9669Sbuhrow * susceptible to magnifcation DOS attacks than those
1883a2e9669Sbuhrow * servers that don't use this flag. This flag merely
1893a2e9669Sbuhrow * permits the reception of acknowledgement traffic to the
1903a2e9669Sbuhrow * broadcast address/specific port number that's being used for
1913a2e9669Sbuhrow * this session as well as the unicast address/specific port
1923a2e9669Sbuhrow * number for this session. For example, if the session is
1933a2e9669Sbuhrow * expecting acks on 192.168.1.40:50201, then this flag
1943a2e9669Sbuhrow * would also allow acks to be returned to
1953a2e9669Sbuhrow * 192.168.1.255:50201, assuming that 192.168.1.255 is the
1963a2e9669Sbuhrow * broadcast address for the subnet containing 192.168.1.40.
1973a2e9669Sbuhrow */
1983a2e9669Sbuhrow broadcast_client = 1;
1993a2e9669Sbuhrow break;
2004862012fSjnemeth case 'c':
20181fae527Shubertf unrestricted_writes = 1;
20281fae527Shubertf break;
20381fae527Shubertf
204d8c9b346Schristos case 'd':
205d8c9b346Schristos debug++;
206d8c9b346Schristos break;
20715b3c2bfSlukem
20815b3c2bfSlukem case 'g':
20915b3c2bfSlukem tgtgroup = optarg;
21015b3c2bfSlukem break;
21115b3c2bfSlukem
21260cef501Smrg case 'l':
21360cef501Smrg logging = 1;
21460cef501Smrg break;
21560cef501Smrg
21660cef501Smrg case 'n':
21760cef501Smrg suppress_naks = 1;
21860cef501Smrg break;
21960cef501Smrg
22073a2253aSchristos case 'p':
22173a2253aSchristos if (optarg[0] == '\0' || optarg[1] != '\0')
22273a2253aSchristos usage();
22373a2253aSchristos pathsep = optarg[0];
22473a2253aSchristos break;
22573a2253aSchristos
2269fd1fef0Scgd case 's':
2279fd1fef0Scgd secure = 1;
22860cef501Smrg securedir = optarg;
2299fd1fef0Scgd break;
2309fd1fef0Scgd
23115b3c2bfSlukem case 'u':
23215b3c2bfSlukem tgtuser = optarg;
23315b3c2bfSlukem break;
23415b3c2bfSlukem
2359fd1fef0Scgd default:
2369fd1fef0Scgd usage();
2379fd1fef0Scgd break;
2389fd1fef0Scgd }
2399fd1fef0Scgd
24060cef501Smrg if (optind < argc) {
24160cef501Smrg struct dirlist *dirp;
24260cef501Smrg
24360cef501Smrg /* Get list of directory prefixes. Skip relative pathnames. */
24460cef501Smrg for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS];
24560cef501Smrg optind++) {
24660cef501Smrg if (argv[optind][0] == '/') {
24760cef501Smrg dirp->name = argv[optind];
24860cef501Smrg dirp->len = strlen(dirp->name);
24960cef501Smrg dirp++;
25045fe1558Smycroft }
25145fe1558Smycroft }
25245fe1558Smycroft }
2539fd1fef0Scgd
25415b3c2bfSlukem if (*tgtuser == '\0' || (tgtgroup != NULL && *tgtgroup == '\0'))
25515b3c2bfSlukem usage();
25615b3c2bfSlukem
25715b3c2bfSlukem nid = (strtol(tgtuser, &ep, 10));
25815b3c2bfSlukem if (*ep == '\0') {
259b877ced6Smbalmer if ((uid_t)nid > UID_MAX) {
26015b3c2bfSlukem syslog(LOG_ERR, "uid %ld is too large", nid);
26115b3c2bfSlukem exit(1);
26215b3c2bfSlukem }
26315b3c2bfSlukem pwent = getpwuid((uid_t)nid);
26415b3c2bfSlukem } else
26515b3c2bfSlukem pwent = getpwnam(tgtuser);
26615b3c2bfSlukem if (pwent == NULL) {
26715b3c2bfSlukem syslog(LOG_ERR, "unknown user `%s'", tgtuser);
26815b3c2bfSlukem exit(1);
26915b3c2bfSlukem }
27015b3c2bfSlukem tgtuid = pwent->pw_uid;
27115b3c2bfSlukem tgtgid = pwent->pw_gid;
27215b3c2bfSlukem
27315b3c2bfSlukem if (tgtgroup != NULL) {
27415b3c2bfSlukem nid = (strtol(tgtgroup, &ep, 10));
27515b3c2bfSlukem if (*ep == '\0') {
276b877ced6Smbalmer if ((uid_t)nid > GID_MAX) {
27715b3c2bfSlukem syslog(LOG_ERR, "gid %ld is too large", nid);
27815b3c2bfSlukem exit(1);
27915b3c2bfSlukem }
28015b3c2bfSlukem grent = getgrgid((gid_t)nid);
28115b3c2bfSlukem } else
28215b3c2bfSlukem grent = getgrnam(tgtgroup);
28315b3c2bfSlukem if (grent != NULL)
28415b3c2bfSlukem tgtgid = grent->gr_gid;
28515b3c2bfSlukem else {
28615b3c2bfSlukem syslog(LOG_ERR, "unknown group `%s'", tgtgroup);
28715b3c2bfSlukem exit(1);
28815b3c2bfSlukem }
28915b3c2bfSlukem }
29015b3c2bfSlukem
29160cef501Smrg if (secure) {
29260cef501Smrg if (chdir(securedir) < 0) {
29360cef501Smrg syslog(LOG_ERR, "chdir %s: %m", securedir);
29460cef501Smrg exit(1);
29560cef501Smrg }
29660cef501Smrg if (chroot(".")) {
29715b3c2bfSlukem syslog(LOG_ERR, "chroot: %m");
29845fe1558Smycroft exit(1);
29945fe1558Smycroft }
30060cef501Smrg }
3019fd1fef0Scgd
302edc4b408Slukem if (logging)
30315b3c2bfSlukem syslog(LOG_DEBUG, "running as user `%s' (%d), group `%s' (%d)",
304edc4b408Slukem tgtuser, tgtuid, tgtgroup ? tgtgroup : "(unspecified)",
305edc4b408Slukem tgtgid);
30615b3c2bfSlukem if (curgid != tgtgid) {
30715b3c2bfSlukem if (setgid(tgtgid)) {
30815b3c2bfSlukem syslog(LOG_ERR, "setgid to %d: %m", (int)tgtgid);
309ad6a2b10Sjtc exit(1);
310ad6a2b10Sjtc }
3118a6057d6Smrg if (setgroups(0, NULL)) {
3128a6057d6Smrg syslog(LOG_ERR, "setgroups: %m");
3138a6057d6Smrg exit(1);
3148a6057d6Smrg }
31515b3c2bfSlukem }
3168a6057d6Smrg
31715b3c2bfSlukem if (curuid != tgtuid) {
31815b3c2bfSlukem if (setuid(tgtuid)) {
31915b3c2bfSlukem syslog(LOG_ERR, "setuid to %d: %m", (int)tgtuid);
320ad6a2b10Sjtc exit(1);
321ad6a2b10Sjtc }
32215b3c2bfSlukem }
323ad6a2b10Sjtc
32460cef501Smrg on = 1;
3259fd1fef0Scgd if (ioctl(fd, FIONBIO, &on) < 0) {
32615b3c2bfSlukem syslog(LOG_ERR, "ioctl(FIONBIO): %m");
32761f28255Scgd exit(1);
32861f28255Scgd }
32961f28255Scgd fromlen = sizeof (from);
3309fd1fef0Scgd n = recvfrom(fd, buf, sizeof (buf), 0,
33161f28255Scgd (struct sockaddr *)&from, &fromlen);
33261f28255Scgd if (n < 0) {
33315b3c2bfSlukem syslog(LOG_ERR, "recvfrom: %m");
33461f28255Scgd exit(1);
33561f28255Scgd }
33661f28255Scgd /*
33761f28255Scgd * Now that we have read the message out of the UDP
33861f28255Scgd * socket, we fork and exit. Thus, inetd will go back
33961f28255Scgd * to listening to the tftp port, and the next request
34061f28255Scgd * to come in will start up a new instance of tftpd.
34161f28255Scgd *
34261f28255Scgd * We do this so that inetd can run tftpd in "wait" mode.
34361f28255Scgd * The problem with tftpd running in "nowait" mode is that
34461f28255Scgd * inetd may get one or more successful "selects" on the
34561f28255Scgd * tftp port before we do our receive, so more than one
34661f28255Scgd * instance of tftpd may be started up. Worse, if tftpd
34761f28255Scgd * break before doing the above "recvfrom", inetd would
34861f28255Scgd * spawn endless instances, clogging the system.
34961f28255Scgd */
35061f28255Scgd {
35161f28255Scgd int pid;
3520c37c63eSmrg int i;
3530c37c63eSmrg socklen_t j;
35461f28255Scgd
35561f28255Scgd for (i = 1; i < 20; i++) {
35661f28255Scgd pid = fork();
35761f28255Scgd if (pid < 0) {
35861f28255Scgd sleep(i);
35961f28255Scgd /*
36061f28255Scgd * flush out to most recently sent request.
36161f28255Scgd *
36261f28255Scgd * This may drop some request, but those
36361f28255Scgd * will be resent by the clients when
36461f28255Scgd * they timeout. The positive effect of
36561f28255Scgd * this flush is to (try to) prevent more
36661f28255Scgd * than one tftpd being started up to service
36761f28255Scgd * a single request from a single client.
36861f28255Scgd */
36961f28255Scgd j = sizeof from;
3709fd1fef0Scgd i = recvfrom(fd, buf, sizeof (buf), 0,
37161f28255Scgd (struct sockaddr *)&from, &j);
37261f28255Scgd if (i > 0) {
37361f28255Scgd n = i;
37461f28255Scgd fromlen = j;
37561f28255Scgd }
37661f28255Scgd } else {
37761f28255Scgd break;
37861f28255Scgd }
37961f28255Scgd }
38061f28255Scgd if (pid < 0) {
38115b3c2bfSlukem syslog(LOG_ERR, "fork: %m");
38261f28255Scgd exit(1);
38361f28255Scgd } else if (pid != 0) {
38461f28255Scgd exit(0);
38561f28255Scgd }
38661f28255Scgd }
387ed33c873Sexplorer
388ed33c873Sexplorer /*
389ed33c873Sexplorer * remember what address this was sent to, so we can respond on the
390ed33c873Sexplorer * same interface
391ed33c873Sexplorer */
39247b0e5ffSitojun len = sizeof(me);
39347b0e5ffSitojun if (getsockname(fd, (struct sockaddr *)&me, &len) == 0) {
39447b0e5ffSitojun switch (me.ss_family) {
39547b0e5ffSitojun case AF_INET:
39647b0e5ffSitojun ((struct sockaddr_in *)&me)->sin_port = 0;
39747b0e5ffSitojun break;
39847b0e5ffSitojun case AF_INET6:
39947b0e5ffSitojun ((struct sockaddr_in6 *)&me)->sin6_port = 0;
40047b0e5ffSitojun break;
40147b0e5ffSitojun default:
40247b0e5ffSitojun /* unsupported */
40347b0e5ffSitojun break;
40447b0e5ffSitojun }
40547b0e5ffSitojun } else {
40647b0e5ffSitojun memset(&me, 0, sizeof(me));
40747b0e5ffSitojun me.ss_family = from.ss_family;
40847b0e5ffSitojun me.ss_len = from.ss_len;
409ed33c873Sexplorer }
410ed33c873Sexplorer
41161f28255Scgd alarm(0);
4129fd1fef0Scgd close(fd);
41361f28255Scgd close(1);
41447b0e5ffSitojun peer = socket(from.ss_family, SOCK_DGRAM, 0);
41561f28255Scgd if (peer < 0) {
41615b3c2bfSlukem syslog(LOG_ERR, "socket: %m");
41761f28255Scgd exit(1);
41861f28255Scgd }
4193a2e9669Sbuhrow if (broadcast_client) {
4203a2e9669Sbuhrow soopt = 1;
4213a2e9669Sbuhrow if (setsockopt(peer, SOL_SOCKET, SO_BROADCAST, (void *) &soopt, sizeof(soopt)) < 0) {
4223a2e9669Sbuhrow syslog(LOG_ERR, "set SO_BROADCAST: %m");
42361f28255Scgd exit(1);
42461f28255Scgd }
4253a2e9669Sbuhrow }
4263a2e9669Sbuhrow if (bind(peer, (struct sockaddr *)&me, me.ss_len) < 0) {
4273a2e9669Sbuhrow syslog(LOG_ERR, "bind: %m");
42861f28255Scgd exit(1);
42961f28255Scgd }
43044411286Sbriggs soopt = 65536; /* larger than we'll ever need */
43144411286Sbriggs if (setsockopt(peer, SOL_SOCKET, SO_SNDBUF, (void *) &soopt, sizeof(soopt)) < 0) {
43244411286Sbriggs syslog(LOG_ERR, "set SNDBUF: %m");
43344411286Sbriggs exit(1);
43444411286Sbriggs }
43544411286Sbriggs if (setsockopt(peer, SOL_SOCKET, SO_RCVBUF, (void *) &soopt, sizeof(soopt)) < 0) {
43644411286Sbriggs syslog(LOG_ERR, "set RCVBUF: %m");
43744411286Sbriggs exit(1);
43844411286Sbriggs }
43944411286Sbriggs
44061f28255Scgd tp = (struct tftphdr *)buf;
44161f28255Scgd tp->th_opcode = ntohs(tp->th_opcode);
44261f28255Scgd if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
44361f28255Scgd tftp(tp, n);
44461f28255Scgd exit(1);
44561f28255Scgd }
44661f28255Scgd
44744411286Sbriggs static int
blk_handler(struct tftphdr * tp,const char * val,char * ack,size_t asize,size_t * ackl,int * ecode)4487807c800Schristos blk_handler(struct tftphdr *tp, const char *val, char *ack, size_t asize,
4491c129848Schristos size_t *ackl, int *ecode)
45044411286Sbriggs {
45144411286Sbriggs unsigned long bsize;
45244411286Sbriggs char *endp;
45344411286Sbriggs int l;
45444411286Sbriggs
45544411286Sbriggs /*
45644411286Sbriggs * On these failures, we could just ignore the blocksize option.
45744411286Sbriggs * Perhaps that should be a command-line option.
45844411286Sbriggs */
45944411286Sbriggs errno = 0;
46044411286Sbriggs bsize = strtoul(val, &endp, 10);
46144411286Sbriggs if ((bsize == ULONG_MAX && errno == ERANGE) || *endp) {
46244411286Sbriggs syslog(LOG_NOTICE, "%s: %s request for %s: "
46344411286Sbriggs "illegal value %s for blksize option",
46444411286Sbriggs verifyhost((struct sockaddr *)&from),
46544411286Sbriggs tp->th_opcode == WRQ ? "write" : "read",
46644411286Sbriggs tp->th_stuff, val);
4671c129848Schristos *ecode = EBADOP;
4681c129848Schristos return -1;
46944411286Sbriggs }
47044411286Sbriggs if (bsize < 8 || bsize > 65464) {
47144411286Sbriggs syslog(LOG_NOTICE, "%s: %s request for %s: "
47244411286Sbriggs "out of range value %s for blksize option",
47344411286Sbriggs verifyhost((struct sockaddr *)&from),
47444411286Sbriggs tp->th_opcode == WRQ ? "write" : "read",
47544411286Sbriggs tp->th_stuff, val);
4761c129848Schristos *ecode = EBADOP;
4771c129848Schristos return -1;
47844411286Sbriggs }
47944411286Sbriggs
48044411286Sbriggs tftp_blksize = bsize;
4817807c800Schristos if (asize > *ackl && (l = snprintf(ack + *ackl, asize - *ackl,
4821c129848Schristos "blksize%c%lu%c", 0, bsize, 0)) > 0) {
4837807c800Schristos *ackl += l;
4841c129848Schristos } else {
4851c129848Schristos *ecode = EBADOP;
4867807c800Schristos return -1;
4871c129848Schristos }
48844411286Sbriggs
48944411286Sbriggs return 0;
49044411286Sbriggs }
49144411286Sbriggs
49244411286Sbriggs static int
timeout_handler(struct tftphdr * tp,const char * val,char * ack,size_t asize,size_t * ackl,int * ecode)4937807c800Schristos timeout_handler(struct tftphdr *tp, const char *val, char *ack, size_t asize,
4941c129848Schristos size_t *ackl, int *ecode)
49544411286Sbriggs {
49644411286Sbriggs unsigned long tout;
49744411286Sbriggs char *endp;
49844411286Sbriggs int l;
49944411286Sbriggs
50044411286Sbriggs errno = 0;
50144411286Sbriggs tout = strtoul(val, &endp, 10);
50244411286Sbriggs if ((tout == ULONG_MAX && errno == ERANGE) || *endp) {
50344411286Sbriggs syslog(LOG_NOTICE, "%s: %s request for %s: "
50444411286Sbriggs "illegal value %s for timeout option",
50544411286Sbriggs verifyhost((struct sockaddr *)&from),
50644411286Sbriggs tp->th_opcode == WRQ ? "write" : "read",
50744411286Sbriggs tp->th_stuff, val);
5081c129848Schristos *ecode = EBADOP;
5091c129848Schristos return -1;
51044411286Sbriggs }
51144411286Sbriggs if (tout < 1 || tout > 255) {
51244411286Sbriggs syslog(LOG_NOTICE, "%s: %s request for %s: "
51344411286Sbriggs "out of range value %s for timeout option",
51444411286Sbriggs verifyhost((struct sockaddr *)&from),
51544411286Sbriggs tp->th_opcode == WRQ ? "write" : "read",
51644411286Sbriggs tp->th_stuff, val);
51744411286Sbriggs return 0;
51844411286Sbriggs }
51944411286Sbriggs
52044411286Sbriggs rexmtval = tout;
5217807c800Schristos if (asize > *ackl && (l = snprintf(ack + *ackl, asize - *ackl,
52259b4948aSchristos "timeout%c%lu%c", 0, tout, 0)) > 0)
5237807c800Schristos *ackl += l;
5247807c800Schristos else
5257807c800Schristos return -1;
52644411286Sbriggs /*
52744411286Sbriggs * Arbitrarily pick a maximum timeout on a request to 3
52844411286Sbriggs * retransmissions if the interval timeout is more than
52944411286Sbriggs * one minute. Longest possible timeout is therefore
53044411286Sbriggs * 3 * 255 - 1, or 764 seconds.
53144411286Sbriggs */
53244411286Sbriggs if (rexmtval > 60) {
53344411286Sbriggs maxtimeout = rexmtval * 3;
53444411286Sbriggs } else {
53544411286Sbriggs maxtimeout = rexmtval * 5;
53644411286Sbriggs }
53744411286Sbriggs
53844411286Sbriggs return 0;
53944411286Sbriggs }
54044411286Sbriggs
54144411286Sbriggs static int
tsize_handler(struct tftphdr * tp,const char * val,char * ack,size_t asize,size_t * ackl,int * ecode)5427807c800Schristos tsize_handler(struct tftphdr *tp, const char *val, char *ack, size_t asize,
5431c129848Schristos size_t *ackl, int *ecode)
54444411286Sbriggs {
54544411286Sbriggs unsigned long fsize;
54644411286Sbriggs char *endp;
54744411286Sbriggs
54844411286Sbriggs /*
54944411286Sbriggs * Maximum file even with extended tftp is 65535 blocks of
55044411286Sbriggs * length 65464, or 4290183240 octets (4784056 less than 2^32).
55144411286Sbriggs * unsigned long is at least 32 bits on all NetBSD archs.
55244411286Sbriggs */
55344411286Sbriggs
55444411286Sbriggs errno = 0;
55544411286Sbriggs fsize = strtoul(val, &endp, 10);
55644411286Sbriggs if ((fsize == ULONG_MAX && errno == ERANGE) || *endp) {
55744411286Sbriggs syslog(LOG_NOTICE, "%s: %s request for %s: "
55844411286Sbriggs "illegal value %s for tsize option",
55944411286Sbriggs verifyhost((struct sockaddr *)&from),
56044411286Sbriggs tp->th_opcode == WRQ ? "write" : "read",
56144411286Sbriggs tp->th_stuff, val);
5621c129848Schristos *ecode = EBADOP;
5631c129848Schristos return -1;
56444411286Sbriggs }
56544411286Sbriggs if (fsize > (unsigned long) 65535 * 65464) {
56644411286Sbriggs syslog(LOG_NOTICE, "%s: %s request for %s: "
56744411286Sbriggs "out of range value %s for tsize option",
56844411286Sbriggs verifyhost((struct sockaddr *)&from),
56944411286Sbriggs tp->th_opcode == WRQ ? "write" : "read",
57044411286Sbriggs tp->th_stuff, val);
5711c129848Schristos *ecode = EBADOP;
5721c129848Schristos return -1;
57344411286Sbriggs }
57444411286Sbriggs
57544411286Sbriggs tftp_opt_tsize = 1;
57644411286Sbriggs tftp_tsize = fsize;
57744411286Sbriggs /*
57844411286Sbriggs * We will report this later -- either replying with the fsize (WRQ)
57944411286Sbriggs * or replying with the actual filesize (RRQ).
58044411286Sbriggs */
58144411286Sbriggs
58244411286Sbriggs return 0;
58344411286Sbriggs }
58444411286Sbriggs
58573a2253aSchristos static const struct tftp_options {
58673a2253aSchristos const char *o_name;
5877807c800Schristos int (*o_handler)(struct tftphdr *, const char *, char *, size_t,
5887807c800Schristos size_t *, int *);
58944411286Sbriggs } options[] = {
59044411286Sbriggs { "blksize", blk_handler },
59144411286Sbriggs { "timeout", timeout_handler },
59244411286Sbriggs { "tsize", tsize_handler },
59373a2253aSchristos { .o_name = NULL }
59444411286Sbriggs };
59544411286Sbriggs
59644411286Sbriggs /*
59744411286Sbriggs * Get options for an extended tftp session. Stuff the ones we
59844411286Sbriggs * recognize in oackbuf.
59944411286Sbriggs */
60044411286Sbriggs static int
get_options(struct tftphdr * tp,char * cp,int size,char * ackb,size_t asize,size_t * alen,int * ecode)6017807c800Schristos get_options(struct tftphdr *tp, char *cp, int size, char *ackb, size_t asize,
6021c129848Schristos size_t *alen, int *ecode)
60344411286Sbriggs {
60473a2253aSchristos const struct tftp_options *op;
60544411286Sbriggs char *option, *value, *endp;
6061c129848Schristos int r, rv=0;
60744411286Sbriggs
60844411286Sbriggs endp = cp + size;
60944411286Sbriggs while (cp < endp) {
61044411286Sbriggs option = cp;
61144411286Sbriggs while (*cp && cp < endp) {
6121869f0e1Sdsl *cp = tolower((unsigned char)*cp);
61344411286Sbriggs cp++;
61444411286Sbriggs }
61544411286Sbriggs if (*cp) {
61644411286Sbriggs /* if we have garbage at the end, just ignore it */
61744411286Sbriggs break;
61844411286Sbriggs }
61944411286Sbriggs cp++; /* skip over NUL */
62044411286Sbriggs value = cp;
62144411286Sbriggs while (*cp && cp < endp) {
62244411286Sbriggs cp++;
62344411286Sbriggs }
62444411286Sbriggs if (*cp) {
62544411286Sbriggs /* if we have garbage at the end, just ignore it */
62644411286Sbriggs break;
62744411286Sbriggs }
62844411286Sbriggs cp++;
62944411286Sbriggs for (op = options; op->o_name; op++) {
63044411286Sbriggs if (strcmp(op->o_name, option) == 0)
63144411286Sbriggs break;
63244411286Sbriggs }
63344411286Sbriggs if (op->o_name) {
6341c129848Schristos r = op->o_handler(tp, value, ackb, asize, alen, ecode);
63544411286Sbriggs if (r < 0) {
63644411286Sbriggs rv = -1;
63744411286Sbriggs break;
63844411286Sbriggs }
63944411286Sbriggs rv++;
64044411286Sbriggs } /* else ignore unknown options */
64144411286Sbriggs }
64244411286Sbriggs
64344411286Sbriggs return rv;
64444411286Sbriggs }
64544411286Sbriggs
64661f28255Scgd /*
64761f28255Scgd * Handle initial connection protocol.
64861f28255Scgd */
64960cef501Smrg static void
tftp(struct tftphdr * tp,int size)650edc4b408Slukem tftp(struct tftphdr *tp, int size)
65161f28255Scgd {
65215b3c2bfSlukem struct formats *pf;
653edc4b408Slukem char *cp;
654edc4b408Slukem char *filename, *mode;
6557807c800Schristos int first, ecode, etftp = 0, r;
6567807c800Schristos size_t alen;
657edc4b408Slukem
6580c37c63eSmrg ecode = 0; /* XXX gcc */
659edc4b408Slukem first = 1;
660edc4b408Slukem mode = NULL;
66161f28255Scgd
66261f28255Scgd filename = cp = tp->th_stuff;
66361f28255Scgd again:
66461f28255Scgd while (cp < buf + size) {
66561f28255Scgd if (*cp == '\0')
66661f28255Scgd break;
66761f28255Scgd cp++;
66861f28255Scgd }
66961f28255Scgd if (*cp != '\0') {
67061f28255Scgd nak(EBADOP);
67161f28255Scgd exit(1);
67261f28255Scgd }
67361f28255Scgd if (first) {
67461f28255Scgd mode = ++cp;
67561f28255Scgd first = 0;
67661f28255Scgd goto again;
67761f28255Scgd }
67861f28255Scgd for (cp = mode; *cp; cp++)
6791869f0e1Sdsl *cp = tolower((unsigned char)*cp);
68061f28255Scgd for (pf = formats; pf->f_mode; pf++)
68161f28255Scgd if (strcmp(pf->f_mode, mode) == 0)
68261f28255Scgd break;
68361f28255Scgd if (pf->f_mode == 0) {
68461f28255Scgd nak(EBADOP);
68561f28255Scgd exit(1);
68661f28255Scgd }
68744411286Sbriggs /*
68844411286Sbriggs * cp currently points to the NUL byte following the mode.
68944411286Sbriggs *
69044411286Sbriggs * If we have some valid options, then let's assume that we're
69144411286Sbriggs * now dealing with an extended tftp session. Note that if we
69244411286Sbriggs * don't get any options, then we *must* assume that we do not
69344411286Sbriggs * have an extended tftp session. If we get options, we fill
69444411286Sbriggs * in the ack buf to acknowledge them. If we skip that, then
69544411286Sbriggs * the client *must* assume that we are not using an extended
69644411286Sbriggs * session.
69744411286Sbriggs */
69844411286Sbriggs size -= (++cp - (char *) tp);
69944411286Sbriggs if (size > 0 && *cp) {
70044411286Sbriggs alen = 2; /* Skip over opcode */
7017807c800Schristos r = get_options(tp, cp, size, oackbuf, sizeof(oackbuf),
7027807c800Schristos &alen, &ecode);
70344411286Sbriggs if (r > 0) {
70444411286Sbriggs etftp = 1;
70544411286Sbriggs } else if (r < 0) {
70644411286Sbriggs nak(ecode);
70744411286Sbriggs exit(1);
70844411286Sbriggs }
70944411286Sbriggs }
71073a2253aSchristos /*
71173a2253aSchristos * Globally replace the path separator given in the -p option
71273a2253aSchristos * with / to cope with clients expecting a non-unix path separator.
71373a2253aSchristos */
71473a2253aSchristos if (pathsep != '\0') {
71573a2253aSchristos for (cp = filename; *cp != '\0'; ++cp) {
71673a2253aSchristos if (*cp == pathsep)
71773a2253aSchristos *cp = '/';
71873a2253aSchristos }
71973a2253aSchristos }
72060cef501Smrg ecode = (*pf->f_validate)(&filename, tp->th_opcode);
72160cef501Smrg if (logging) {
72260cef501Smrg syslog(LOG_INFO, "%s: %s request for %s: %s",
72347b0e5ffSitojun verifyhost((struct sockaddr *)&from),
72460cef501Smrg tp->th_opcode == WRQ ? "write" : "read",
72560cef501Smrg filename, errtomsg(ecode));
72660cef501Smrg }
72761f28255Scgd if (ecode) {
72860cef501Smrg /*
72960cef501Smrg * Avoid storms of naks to a RRQ broadcast for a relative
73060cef501Smrg * bootfile pathname from a diskless Sun.
73160cef501Smrg */
73260cef501Smrg if (suppress_naks && *filename != '/' && ecode == ENOTFOUND)
73360cef501Smrg exit(0);
73461f28255Scgd nak(ecode);
73561f28255Scgd exit(1);
73661f28255Scgd }
73744411286Sbriggs
73844411286Sbriggs if (etftp) {
73944411286Sbriggs struct tftphdr *oack_h;
74044411286Sbriggs
74144411286Sbriggs if (tftp_opt_tsize) {
74244411286Sbriggs int l;
74344411286Sbriggs
7447807c800Schristos if (sizeof(oackbuf) > alen &&
7457807c800Schristos (l = snprintf(oackbuf + alen,
7467807c800Schristos sizeof(oackbuf) - alen, "tsize%c%u%c", 0,
74759b4948aSchristos tftp_tsize, 0)) > 0)
7487807c800Schristos alen += l;
74944411286Sbriggs }
75044411286Sbriggs oack_h = (struct tftphdr *) oackbuf;
75144411286Sbriggs oack_h->th_opcode = htons(OACK);
75244411286Sbriggs }
75344411286Sbriggs
75461f28255Scgd if (tp->th_opcode == WRQ)
75544411286Sbriggs (*pf->f_recv)(pf, etftp, alen);
75661f28255Scgd else
75744411286Sbriggs (*pf->f_send)(pf, etftp, alen);
75861f28255Scgd exit(0);
75961f28255Scgd }
76061f28255Scgd
76161f28255Scgd
76261f28255Scgd FILE *file;
76361f28255Scgd
76461f28255Scgd /*
76561f28255Scgd * Validate file access. Since we
76661f28255Scgd * have no uid or gid, for now require
76761f28255Scgd * file to exist and be publicly
76861f28255Scgd * readable/writable.
76961f28255Scgd * If we were invoked with arguments
77061f28255Scgd * from inetd then the file must also be
77161f28255Scgd * in one of the given directory prefixes.
77261f28255Scgd */
77360cef501Smrg int
validate_access(char ** filep,int mode)774edc4b408Slukem validate_access(char **filep, int mode)
77561f28255Scgd {
77661f28255Scgd struct stat stbuf;
77760cef501Smrg struct dirlist *dirp;
77860cef501Smrg static char pathname[MAXPATHLEN];
779edc4b408Slukem char *filename;
780edc4b408Slukem int fd;
781cb578cacShubertf int create = 0;
782cb578cacShubertf int trunc = 0;
783edc4b408Slukem
784edc4b408Slukem filename = *filep;
78561f28255Scgd
78661f28255Scgd /*
78760cef501Smrg * Prevent tricksters from getting around the directory restrictions
78861f28255Scgd */
78960cef501Smrg if (strstr(filename, "/../"))
79061f28255Scgd return (EACCESS);
79160cef501Smrg
79260cef501Smrg if (*filename == '/') {
79360cef501Smrg /*
79460cef501Smrg * Allow the request if it's in one of the approved locations.
79560cef501Smrg * Special case: check the null prefix ("/") by looking
79660cef501Smrg * for length = 1 and relying on the arg. processing that
79760cef501Smrg * it's a /.
79860cef501Smrg */
79960cef501Smrg for (dirp = dirs; dirp->name != NULL; dirp++) {
80060cef501Smrg if (dirp->len == 1 ||
80160cef501Smrg (!strncmp(filename, dirp->name, dirp->len) &&
80260cef501Smrg filename[dirp->len] == '/'))
80361f28255Scgd break;
80445fe1558Smycroft }
80560cef501Smrg /* If directory list is empty, allow access to any file */
80660cef501Smrg if (dirp->name == NULL && dirp != dirs)
80760cef501Smrg return (EACCESS);
80861f28255Scgd if (stat(filename, &stbuf) < 0)
80961f28255Scgd return (errno == ENOENT ? ENOTFOUND : EACCESS);
810b247da14Smycroft if (!S_ISREG(stbuf.st_mode))
81160cef501Smrg return (ENOTFOUND);
81261f28255Scgd if (mode == RRQ) {
81360cef501Smrg if ((stbuf.st_mode & S_IROTH) == 0)
81461f28255Scgd return (EACCESS);
81561f28255Scgd } else {
81660cef501Smrg if ((stbuf.st_mode & S_IWOTH) == 0)
81761f28255Scgd return (EACCESS);
81861f28255Scgd }
81960cef501Smrg } else {
82060cef501Smrg /*
82160cef501Smrg * Relative file name: search the approved locations for it.
82260cef501Smrg */
82360cef501Smrg
8249935ad76Saidan if (!strncmp(filename, "../", 3))
82560cef501Smrg return (EACCESS);
82660cef501Smrg
82760cef501Smrg /*
8289935ad76Saidan * Find the first file that exists in any of the directories,
8299935ad76Saidan * check access on it.
83060cef501Smrg */
83160cef501Smrg if (dirs[0].name != NULL) {
83260cef501Smrg for (dirp = dirs; dirp->name != NULL; dirp++) {
83360cef501Smrg snprintf(pathname, sizeof pathname, "%s/%s",
83460cef501Smrg dirp->name, filename);
83560cef501Smrg if (stat(pathname, &stbuf) == 0 &&
83660cef501Smrg (stbuf.st_mode & S_IFMT) == S_IFREG) {
83760cef501Smrg break;
83860cef501Smrg }
83960cef501Smrg }
84060cef501Smrg if (dirp->name == NULL)
8419935ad76Saidan return (ENOTFOUND);
8429935ad76Saidan if (mode == RRQ && !(stbuf.st_mode & S_IROTH))
8439935ad76Saidan return (EACCESS);
8449935ad76Saidan if (mode == WRQ && !(stbuf.st_mode & S_IWOTH))
8459935ad76Saidan return (EACCESS);
84660cef501Smrg *filep = filename = pathname;
8479935ad76Saidan } else {
848cb578cacShubertf int stat_rc;
849cb578cacShubertf
8509935ad76Saidan /*
8519935ad76Saidan * If there's no directory list, take our cue from the
8529935ad76Saidan * absolute file request check above (*filename == '/'),
8539935ad76Saidan * and allow access to anything.
8549935ad76Saidan */
855cb578cacShubertf stat_rc = stat(filename, &stbuf);
856cb578cacShubertf if (mode == RRQ) {
857cb578cacShubertf /* Read request */
858cb578cacShubertf if (stat_rc < 0)
8599935ad76Saidan return (errno == ENOENT ? ENOTFOUND : EACCESS);
8609935ad76Saidan if (!S_ISREG(stbuf.st_mode))
8619935ad76Saidan return (ENOTFOUND);
8629935ad76Saidan if ((stbuf.st_mode & S_IROTH) == 0)
8639935ad76Saidan return (EACCESS);
8649935ad76Saidan } else {
865cb578cacShubertf if (stat_rc < 0) {
866cb578cacShubertf /* Can't stat */
867cb578cacShubertf if (errno == EACCES) {
868cb578cacShubertf /* Permission denied */
869cb578cacShubertf return EACCESS;
870cb578cacShubertf } else {
871cb578cacShubertf /* Not there */
872cb578cacShubertf if (unrestricted_writes) {
873cb578cacShubertf /* need to creat new file! */
874cb578cacShubertf create = O_CREAT;
875cb578cacShubertf } else {
876cb578cacShubertf /* Permission denied */
877cb578cacShubertf return EACCESS;
878cb578cacShubertf }
879cb578cacShubertf }
880cb578cacShubertf } else {
881cb578cacShubertf /* Can stat */
882cb578cacShubertf if ((stbuf.st_mode & S_IWOTH) == 0) {
8839935ad76Saidan return (EACCESS);
8849935ad76Saidan }
885cb578cacShubertf trunc = O_TRUNC;
886cb578cacShubertf }
887cb578cacShubertf }
88860cef501Smrg *filep = filename;
88960cef501Smrg }
8909935ad76Saidan }
89144411286Sbriggs
89244411286Sbriggs if (tftp_opt_tsize && mode == RRQ)
89344411286Sbriggs tftp_tsize = (unsigned long) stbuf.st_size;
89444411286Sbriggs
895cb578cacShubertf fd = open(filename, mode == RRQ ? O_RDONLY : O_WRONLY | trunc | create,
896cb578cacShubertf 0644); /* debatable */
89761f28255Scgd if (fd < 0)
89861f28255Scgd return (errno + 100);
89961f28255Scgd file = fdopen(fd, (mode == RRQ)? "r":"w");
90061f28255Scgd if (file == NULL) {
9019935ad76Saidan close(fd);
902edc4b408Slukem return (errno + 100);
90361f28255Scgd }
90461f28255Scgd return (0);
90561f28255Scgd }
90661f28255Scgd
90773a2253aSchristos static int timeout;
90873a2253aSchristos static jmp_buf timeoutbuf;
90961f28255Scgd
91073a2253aSchristos static void
timer(int dummy)911edc4b408Slukem timer(int dummy)
91261f28255Scgd {
91361f28255Scgd
91461f28255Scgd timeout += rexmtval;
91561f28255Scgd if (timeout >= maxtimeout)
91661f28255Scgd exit(1);
91761f28255Scgd longjmp(timeoutbuf, 1);
91861f28255Scgd }
91961f28255Scgd
920d8c9b346Schristos static const char *
opcode(int code)921d8c9b346Schristos opcode(int code)
922d8c9b346Schristos {
923c12ec901Slukem static char obuf[64];
924d8c9b346Schristos
925d8c9b346Schristos switch (code) {
926d8c9b346Schristos case RRQ:
927d8c9b346Schristos return "RRQ";
928d8c9b346Schristos case WRQ:
929d8c9b346Schristos return "WRQ";
930d8c9b346Schristos case DATA:
931d8c9b346Schristos return "DATA";
932d8c9b346Schristos case ACK:
933d8c9b346Schristos return "ACK";
934d8c9b346Schristos case ERROR:
935d8c9b346Schristos return "ERROR";
93644411286Sbriggs case OACK:
93744411286Sbriggs return "OACK";
938d8c9b346Schristos default:
93973a2253aSchristos (void)snprintf(obuf, sizeof(obuf), "*code 0x%x*", code);
940c12ec901Slukem return obuf;
941d8c9b346Schristos }
942d8c9b346Schristos }
943d8c9b346Schristos
94461f28255Scgd /*
94561f28255Scgd * Send the requested file.
94661f28255Scgd */
94773a2253aSchristos static void
sendfile(struct formats * pf,volatile int etftp,int acklength)94873a2253aSchristos sendfile(struct formats *pf, volatile int etftp, int acklength)
94961f28255Scgd {
950edc4b408Slukem volatile unsigned int block;
95160cef501Smrg struct tftphdr *dp;
95215b3c2bfSlukem struct tftphdr *ap; /* ack packet */
95373a2253aSchristos volatile int size;
95473a2253aSchristos int n;
95561f28255Scgd
95661f28255Scgd signal(SIGALRM, timer);
95761f28255Scgd ap = (struct tftphdr *)ackbuf;
95844411286Sbriggs if (etftp) {
95944411286Sbriggs dp = (struct tftphdr *)oackbuf;
96044411286Sbriggs size = acklength - 4;
96144411286Sbriggs block = 0;
96244411286Sbriggs } else {
96344411286Sbriggs dp = r_init();
96444411286Sbriggs size = 0;
96560cef501Smrg block = 1;
96644411286Sbriggs }
96744411286Sbriggs
96861f28255Scgd do {
96944411286Sbriggs if (block > 0) {
97044411286Sbriggs size = readit(file, &dp, tftp_blksize, pf->f_convert);
97161f28255Scgd if (size < 0) {
97261f28255Scgd nak(errno + 100);
97361f28255Scgd goto abort;
97461f28255Scgd }
97561f28255Scgd dp->th_opcode = htons((u_short)DATA);
97661f28255Scgd dp->th_block = htons((u_short)block);
97744411286Sbriggs }
97861f28255Scgd timeout = 0;
97961f28255Scgd (void)setjmp(timeoutbuf);
98061f28255Scgd
98161f28255Scgd send_data:
98244411286Sbriggs if (!etftp && debug)
983d8c9b346Schristos syslog(LOG_DEBUG, "Send DATA %u", block);
9843a2e9669Sbuhrow if ((n = sendto(peer, dp, size + 4, 0, (struct sockaddr *)&from, fromlen)) != size + 4) {
98515b3c2bfSlukem syslog(LOG_ERR, "tftpd: write: %m");
98661f28255Scgd goto abort;
98761f28255Scgd }
98844411286Sbriggs if (block)
98944411286Sbriggs read_ahead(file, tftp_blksize, pf->f_convert);
99061f28255Scgd for ( ; ; ) {
99161f28255Scgd alarm(rexmtval); /* read the ack */
9923a2e9669Sbuhrow n = recvfrom(peer, ackbuf, tftp_blksize, 0,(struct sockaddr
9933a2e9669Sbuhrow *)&from, &fromlen );
99461f28255Scgd alarm(0);
99561f28255Scgd if (n < 0) {
99615b3c2bfSlukem syslog(LOG_ERR, "tftpd: read: %m");
99761f28255Scgd goto abort;
99861f28255Scgd }
99961f28255Scgd ap->th_opcode = ntohs((u_short)ap->th_opcode);
100061f28255Scgd ap->th_block = ntohs((u_short)ap->th_block);
1001d8c9b346Schristos switch (ap->th_opcode) {
1002d8c9b346Schristos case ERROR:
100361f28255Scgd goto abort;
100461f28255Scgd
1005d8c9b346Schristos case ACK:
100673a2253aSchristos if (etftp && ap->th_block == 0) {
100744411286Sbriggs etftp = 0;
100844411286Sbriggs acklength = 0;
100944411286Sbriggs dp = r_init();
101044411286Sbriggs goto done;
101144411286Sbriggs }
101273a2253aSchristos if (ap->th_block == (u_short)block)
1013d8c9b346Schristos goto done;
1014d8c9b346Schristos if (debug)
1015d8c9b346Schristos syslog(LOG_DEBUG, "Resync ACK %u != %u",
1016d8c9b346Schristos (unsigned int)ap->th_block, block);
101761f28255Scgd /* Re-synchronize with the other side */
101844411286Sbriggs (void) synchnet(peer, tftp_blksize);
101973a2253aSchristos if (ap->th_block == (u_short)(block - 1))
102061f28255Scgd goto send_data;
1021*ef0b7ea3Sshm /* FALLTHROUGH */
1022d8c9b346Schristos default:
1023d8c9b346Schristos syslog(LOG_INFO, "Received %s in sendfile\n",
1024d8c9b346Schristos opcode(dp->th_opcode));
102561f28255Scgd }
102661f28255Scgd
102761f28255Scgd }
1028d8c9b346Schristos done:
1029d8c9b346Schristos if (debug)
1030d8c9b346Schristos syslog(LOG_DEBUG, "Received ACK for block %u", block);
103173a2253aSchristos if (block == UINT16_MAX && size == tftp_blksize)
103273a2253aSchristos syslog(LOG_WARNING,
103373a2253aSchristos "Block number wrapped (hint: increase block size)");
103461f28255Scgd block++;
103544411286Sbriggs } while (size == tftp_blksize || block == 1);
103661f28255Scgd abort:
103761f28255Scgd (void) fclose(file);
103861f28255Scgd }
103961f28255Scgd
104073a2253aSchristos static void
justquit(int dummy)1041edc4b408Slukem justquit(int dummy)
104261f28255Scgd {
1043edc4b408Slukem
104461f28255Scgd exit(0);
104561f28255Scgd }
104661f28255Scgd
104761f28255Scgd /*
104861f28255Scgd * Receive a file.
104961f28255Scgd */
105073a2253aSchristos static void
recvfile(struct formats * pf,volatile int etftp,volatile int acklength)105173a2253aSchristos recvfile(struct formats *pf, volatile int etftp, volatile int acklength)
105261f28255Scgd {
1053edc4b408Slukem volatile unsigned int block;
105460cef501Smrg struct tftphdr *dp;
105515b3c2bfSlukem struct tftphdr *ap; /* ack buffer */
105673a2253aSchristos volatile int size;
105773a2253aSchristos int n;
105861f28255Scgd
105961f28255Scgd signal(SIGALRM, timer);
106061f28255Scgd dp = w_init();
106144411286Sbriggs ap = (struct tftphdr *)oackbuf;
106260cef501Smrg block = 0;
106361f28255Scgd do {
106461f28255Scgd timeout = 0;
106544411286Sbriggs if (etftp == 0) {
106644411286Sbriggs ap = (struct tftphdr *)ackbuf;
106761f28255Scgd ap->th_opcode = htons((u_short)ACK);
106861f28255Scgd ap->th_block = htons((u_short)block);
106944411286Sbriggs acklength = 4;
107044411286Sbriggs }
1071d8c9b346Schristos if (debug)
1072d8c9b346Schristos syslog(LOG_DEBUG, "Sending ACK for block %u\n", block);
107373a2253aSchristos if (block == UINT16_MAX)
107473a2253aSchristos syslog(LOG_WARNING,
107573a2253aSchristos "Block number wrapped (hint: increase block size)");
107661f28255Scgd block++;
107761f28255Scgd (void) setjmp(timeoutbuf);
107861f28255Scgd send_ack:
107973a2253aSchristos ap = (struct tftphdr *) (etftp ? oackbuf : ackbuf);
10803a2e9669Sbuhrow if (sendto(peer, ap, acklength, 0, (struct sockaddr *)&from, fromlen) != acklength) {
108115b3c2bfSlukem syslog(LOG_ERR, "tftpd: write: %m");
108261f28255Scgd goto abort;
108361f28255Scgd }
108461f28255Scgd write_behind(file, pf->f_convert);
108561f28255Scgd for ( ; ; ) {
108661f28255Scgd alarm(rexmtval);
10873a2e9669Sbuhrow n = recvfrom(peer, dp, tftp_blksize + 4, 0, (struct sockaddr
10883a2e9669Sbuhrow *)&from, &fromlen);
108961f28255Scgd alarm(0);
109061f28255Scgd if (n < 0) { /* really? */
109115b3c2bfSlukem syslog(LOG_ERR, "tftpd: read: %m");
109261f28255Scgd goto abort;
109361f28255Scgd }
109444411286Sbriggs etftp = 0;
109561f28255Scgd dp->th_opcode = ntohs((u_short)dp->th_opcode);
109661f28255Scgd dp->th_block = ntohs((u_short)dp->th_block);
1097d8c9b346Schristos if (debug)
1098d8c9b346Schristos syslog(LOG_DEBUG, "Received %s for block %u",
1099d8c9b346Schristos opcode(dp->th_opcode),
1100d8c9b346Schristos (unsigned int)dp->th_block);
1101d8c9b346Schristos
1102d8c9b346Schristos switch (dp->th_opcode) {
1103d8c9b346Schristos case ERROR:
110461f28255Scgd goto abort;
1105d8c9b346Schristos case DATA:
1106d8c9b346Schristos if (dp->th_block == block)
1107d8c9b346Schristos goto done; /* normal */
1108d8c9b346Schristos if (debug)
1109d8c9b346Schristos syslog(LOG_DEBUG, "Resync %u != %u",
1110d8c9b346Schristos (unsigned int)dp->th_block, block);
111161f28255Scgd /* Re-synchronize with the other side */
111244411286Sbriggs (void) synchnet(peer, tftp_blksize);
111361f28255Scgd if (dp->th_block == (block-1))
111461f28255Scgd goto send_ack; /* rexmit */
1115d8c9b346Schristos break;
1116d8c9b346Schristos default:
1117d8c9b346Schristos syslog(LOG_INFO, "Received %s in recvfile\n",
1118d8c9b346Schristos opcode(dp->th_opcode));
1119d8c9b346Schristos break;
112061f28255Scgd }
112161f28255Scgd }
1122d8c9b346Schristos done:
1123d8c9b346Schristos if (debug)
1124d8c9b346Schristos syslog(LOG_DEBUG, "Got block %u", block);
112561f28255Scgd /* size = write(file, dp->th_data, n - 4); */
112661f28255Scgd size = writeit(file, &dp, n - 4, pf->f_convert);
112761f28255Scgd if (size != (n-4)) { /* ahem */
112861f28255Scgd if (size < 0) nak(errno + 100);
112961f28255Scgd else nak(ENOSPACE);
113061f28255Scgd goto abort;
113161f28255Scgd }
113244411286Sbriggs } while (size == tftp_blksize);
113361f28255Scgd write_behind(file, pf->f_convert);
113461f28255Scgd (void) fclose(file); /* close data file */
113561f28255Scgd
113661f28255Scgd ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */
113761f28255Scgd ap->th_block = htons((u_short)(block));
1138d8c9b346Schristos if (debug)
1139d8c9b346Schristos syslog(LOG_DEBUG, "Send final ACK %u", block);
11403a2e9669Sbuhrow (void) sendto(peer, ackbuf, 4, 0, (struct sockaddr *)&from, fromlen);
114161f28255Scgd
114261f28255Scgd signal(SIGALRM, justquit); /* just quit on timeout */
114361f28255Scgd alarm(rexmtval);
11443a2e9669Sbuhrow n = recvfrom(peer, buf, sizeof (buf), 0, (struct sockaddr *)&from, &fromlen); /* normally times out and quits */
114561f28255Scgd alarm(0);
114661f28255Scgd if (n >= 4 && /* if read some data */
114761f28255Scgd dp->th_opcode == DATA && /* and got a data block */
114861f28255Scgd block == dp->th_block) { /* then my last ack was lost */
11493a2e9669Sbuhrow (void) sendto(peer, ackbuf, 4, 0, (struct sockaddr *)&from, fromlen); /* resend final ack */
115061f28255Scgd }
115161f28255Scgd abort:
115261f28255Scgd return;
115361f28255Scgd }
115461f28255Scgd
11550db548a9Smycroft const struct errmsg {
115661f28255Scgd int e_code;
11570db548a9Smycroft const char *e_msg;
115861f28255Scgd } errmsgs[] = {
115961f28255Scgd { EUNDEF, "Undefined error code" },
116061f28255Scgd { ENOTFOUND, "File not found" },
116161f28255Scgd { EACCESS, "Access violation" },
116261f28255Scgd { ENOSPACE, "Disk full or allocation exceeded" },
116361f28255Scgd { EBADOP, "Illegal TFTP operation" },
116461f28255Scgd { EBADID, "Unknown transfer ID" },
116561f28255Scgd { EEXISTS, "File already exists" },
116661f28255Scgd { ENOUSER, "No such user" },
116744411286Sbriggs { EOPTNEG, "Option negotiation failed" },
116861f28255Scgd { -1, 0 }
116961f28255Scgd };
117061f28255Scgd
11710db548a9Smycroft static const char *
errtomsg(int error)1172edc4b408Slukem errtomsg(int error)
117360cef501Smrg {
1174edc4b408Slukem static char ebuf[20];
117515b3c2bfSlukem const struct errmsg *pe;
117660cef501Smrg
117760cef501Smrg if (error == 0)
1178edc4b408Slukem return ("success");
117960cef501Smrg for (pe = errmsgs; pe->e_code >= 0; pe++)
118060cef501Smrg if (pe->e_code == error)
1181edc4b408Slukem return (pe->e_msg);
1182edc4b408Slukem snprintf(ebuf, sizeof(ebuf), "error %d", error);
1183edc4b408Slukem return (ebuf);
118460cef501Smrg }
118560cef501Smrg
118661f28255Scgd /*
118761f28255Scgd * Send a nak packet (error message).
118861f28255Scgd * Error code passed in is one of the
118961f28255Scgd * standard TFTP codes, or a UNIX errno
119061f28255Scgd * offset by 100.
119161f28255Scgd */
119260cef501Smrg static void
nak(int error)1193edc4b408Slukem nak(int error)
119461f28255Scgd {
1195edc4b408Slukem const struct errmsg *pe;
119615b3c2bfSlukem struct tftphdr *tp;
119761f28255Scgd int length;
1198b7b60560Sitojun size_t msglen;
119961f28255Scgd
120061f28255Scgd tp = (struct tftphdr *)buf;
120161f28255Scgd tp->th_opcode = htons((u_short)ERROR);
1202b7b60560Sitojun msglen = sizeof(buf) - (&tp->th_msg[0] - buf);
120361f28255Scgd for (pe = errmsgs; pe->e_code >= 0; pe++)
120461f28255Scgd if (pe->e_code == error)
120561f28255Scgd break;
120661f28255Scgd if (pe->e_code < 0) {
120761f28255Scgd tp->th_code = EUNDEF; /* set 'undef' errorcode */
1208b7b60560Sitojun strlcpy(tp->th_msg, strerror(error - 100), msglen);
12090db548a9Smycroft } else {
12100db548a9Smycroft tp->th_code = htons((u_short)error);
1211b7b60560Sitojun strlcpy(tp->th_msg, pe->e_msg, msglen);
12120db548a9Smycroft }
1213d8c9b346Schristos if (debug)
1214d8c9b346Schristos syslog(LOG_DEBUG, "Send NACK %s", tp->th_msg);
1215b7b60560Sitojun length = strlen(tp->th_msg);
1216b7b60560Sitojun msglen = &tp->th_msg[length + 1] - buf;
12173a2e9669Sbuhrow if (sendto(peer, buf, msglen, 0, (struct sockaddr *)&from, fromlen) != (ssize_t)msglen)
121815b3c2bfSlukem syslog(LOG_ERR, "nak: %m");
121961f28255Scgd }
122060cef501Smrg
122160cef501Smrg static char *
verifyhost(struct sockaddr * fromp)1222edc4b408Slukem verifyhost(struct sockaddr *fromp)
122360cef501Smrg {
122447b0e5ffSitojun static char hbuf[MAXHOSTNAMELEN];
122560cef501Smrg
12269b1ccfd1Sitojun if (getnameinfo(fromp, fromp->sa_len, hbuf, sizeof(hbuf), NULL, 0, 0))
12279b1ccfd1Sitojun strlcpy(hbuf, "?", sizeof(hbuf));
1228edc4b408Slukem return (hbuf);
122960cef501Smrg }
1230