]>
Commit | Line | Data |
---|---|---|
2b484d24 A |
1 | /* |
2 | * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
a2c93a76 A |
6 | * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights |
7 | * Reserved. This file contains Original Code and/or Modifications of | |
8 | * Original Code as defined in and that are subject to the Apple Public | |
9 | * Source License Version 1.0 (the 'License'). You may not use this file | |
10 | * except in compliance with the License. Please obtain a copy of the | |
11 | * License at http://www.apple.com/publicsource and read it before using | |
12 | * this file. | |
2b484d24 A |
13 | * |
14 | * The Original Code and all software distributed under the License are | |
15 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
16 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
17 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
a2c93a76 A |
18 | * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the |
19 | * License for the specific language governing rights and limitations | |
20 | * under the License." | |
2b484d24 A |
21 | * |
22 | * @APPLE_LICENSE_HEADER_END@ | |
23 | */ | |
24 | /* $NetBSD: tftpd.c,v 1.28 2004/05/05 20:15:45 kleink Exp $ */ | |
b7080c8e A |
25 | /* |
26 | * Copyright (c) 1983, 1993 | |
27 | * The Regents of the University of California. All rights reserved. | |
28 | * | |
29 | * Redistribution and use in source and binary forms, with or without | |
30 | * modification, are permitted provided that the following conditions | |
31 | * are met: | |
32 | * 1. Redistributions of source code must retain the above copyright | |
33 | * notice, this list of conditions and the following disclaimer. | |
34 | * 2. Redistributions in binary form must reproduce the above copyright | |
35 | * notice, this list of conditions and the following disclaimer in the | |
36 | * documentation and/or other materials provided with the distribution. | |
2b484d24 | 37 | * 3. Neither the name of the University nor the names of its contributors |
b7080c8e A |
38 | * may be used to endorse or promote products derived from this software |
39 | * without specific prior written permission. | |
40 | * | |
41 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
42 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
43 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
44 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
45 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
46 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
47 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
48 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
49 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
50 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
51 | * SUCH DAMAGE. | |
52 | */ | |
53 | ||
2b484d24 | 54 | #include <sys/cdefs.h> |
b7080c8e | 55 | #ifndef lint |
2b484d24 A |
56 | __COPYRIGHT("@(#) Copyright (c) 1983, 1993\n\ |
57 | The Regents of the University of California. All rights reserved.\n"); | |
8052502f | 58 | #if 0 |
b7080c8e | 59 | static char sccsid[] = "@(#)tftpd.c 8.1 (Berkeley) 6/4/93"; |
2b484d24 A |
60 | #else |
61 | __RCSID("$NetBSD: tftpd.c,v 1.28 2004/05/05 20:15:45 kleink Exp $"); | |
8052502f | 62 | #endif |
b7080c8e A |
63 | #endif /* not lint */ |
64 | ||
65 | /* | |
66 | * Trivial file transfer protocol server. | |
67 | * | |
68 | * This version includes many modifications by Jim Guyton | |
69 | * <guyton@rand-unix>. | |
70 | */ | |
71 | ||
72 | #include <sys/param.h> | |
73 | #include <sys/ioctl.h> | |
74 | #include <sys/stat.h> | |
75 | #include <sys/socket.h> | |
76 | ||
77 | #include <netinet/in.h> | |
2b484d24 | 78 | #include "tftp.h" |
b7080c8e A |
79 | #include <arpa/inet.h> |
80 | ||
81 | #include <ctype.h> | |
82 | #include <errno.h> | |
83 | #include <fcntl.h> | |
2b484d24 | 84 | #include <grp.h> |
b7080c8e | 85 | #include <netdb.h> |
8052502f | 86 | #include <pwd.h> |
b7080c8e A |
87 | #include <setjmp.h> |
88 | #include <signal.h> | |
89 | #include <stdio.h> | |
90 | #include <stdlib.h> | |
91 | #include <string.h> | |
92 | #include <syslog.h> | |
2b484d24 | 93 | #include <time.h> |
b7080c8e A |
94 | #include <unistd.h> |
95 | ||
96 | #include "tftpsubs.h" | |
97 | ||
2b484d24 A |
98 | #define DEFAULTUSER "nobody" |
99 | ||
b7080c8e A |
100 | #define TIMEOUT 5 |
101 | ||
102 | int peer; | |
103 | int rexmtval = TIMEOUT; | |
104 | int maxtimeout = 5*TIMEOUT; | |
105 | ||
2b484d24 | 106 | char buf[MAXPKTSIZE]; |
b7080c8e | 107 | char ackbuf[PKTSIZE]; |
2b484d24 A |
108 | char oackbuf[PKTSIZE]; |
109 | struct sockaddr_storage from; | |
b7080c8e | 110 | int fromlen; |
2b484d24 | 111 | int debug; |
b7080c8e | 112 | |
2b484d24 A |
113 | int tftp_opt_tsize = 0; |
114 | int tftp_blksize = SEGSIZE; | |
115 | int tftp_tsize = 0; | |
b7080c8e A |
116 | |
117 | /* | |
118 | * Null-terminated directory prefix list for absolute pathname requests and | |
119 | * search list for relative pathname requests. | |
120 | * | |
121 | * MAXDIRS should be at least as large as the number of arguments that | |
122 | * inetd allows (currently 20). | |
123 | */ | |
124 | #define MAXDIRS 20 | |
125 | static struct dirlist { | |
126 | char *name; | |
127 | int len; | |
128 | } dirs[MAXDIRS+1]; | |
129 | static int suppress_naks; | |
130 | static int logging; | |
2b484d24 A |
131 | static int insecure=0; |
132 | static int secure; | |
133 | static char *securedir; | |
b7080c8e | 134 | |
2b484d24 A |
135 | struct formats; |
136 | ||
137 | static const char *errtomsg(int); | |
138 | static void nak(int); | |
139 | static void tftp(struct tftphdr *, int); | |
140 | static void usage(void); | |
141 | static char *verifyhost(struct sockaddr *); | |
142 | void justquit(int); | |
143 | int main(int, char **); | |
144 | void recvfile(struct formats *, int, int); | |
145 | void sendfile(struct formats *, int, int); | |
146 | void timer(int); | |
147 | static const char *opcode(int); | |
148 | int validate_access(char **, int); | |
149 | ||
150 | struct formats { | |
151 | const char *f_mode; | |
152 | int (*f_validate)(char **, int); | |
153 | void (*f_send)(struct formats *, int, int); | |
154 | void (*f_recv)(struct formats *, int, int); | |
155 | int f_convert; | |
156 | } formats[] = { | |
157 | { "netascii", validate_access, sendfile, recvfile, 1 }, | |
158 | { "octet", validate_access, sendfile, recvfile, 0 }, | |
159 | { 0 } | |
160 | }; | |
161 | ||
162 | static void | |
163 | usage(void) | |
164 | { | |
165 | ||
166 | syslog(LOG_ERR, | |
167 | "Usage: %s [-diln] [-u user] [-g group] [-s directory] [directory ...]", | |
168 | getprogname()); | |
169 | exit(1); | |
170 | } | |
b7080c8e A |
171 | |
172 | int | |
2b484d24 | 173 | main(int argc, char *argv[]) |
b7080c8e | 174 | { |
2b484d24 A |
175 | struct sockaddr_storage me; |
176 | struct passwd *pwent; | |
177 | struct group *grent; | |
178 | struct tftphdr *tp; | |
179 | char *tgtuser, *tgtgroup, *ep; | |
180 | int n, ch, on, fd; | |
181 | int len, soopt; | |
182 | uid_t curuid, tgtuid; | |
183 | gid_t curgid, tgtgid; | |
184 | long nid; | |
185 | ||
186 | n = 0; | |
187 | fd = 0; | |
188 | tzset(); | |
189 | openlog("tftpd", LOG_PID | LOG_NDELAY, LOG_DAEMON); | |
190 | tgtuser = DEFAULTUSER; | |
191 | tgtgroup = NULL; | |
192 | curuid = getuid(); | |
193 | curgid = getgid(); | |
194 | ||
195 | while ((ch = getopt(argc, argv, "dg:ilns:u:")) != -1) | |
b7080c8e | 196 | switch (ch) { |
2b484d24 A |
197 | case 'd': |
198 | debug++; | |
8052502f | 199 | break; |
2b484d24 A |
200 | |
201 | case 'g': | |
202 | tgtgroup = optarg; | |
203 | break; | |
204 | ||
205 | case 'i': | |
206 | insecure = 1; | |
8052502f | 207 | break; |
2b484d24 | 208 | |
b7080c8e A |
209 | case 'l': |
210 | logging = 1; | |
211 | break; | |
2b484d24 | 212 | |
b7080c8e A |
213 | case 'n': |
214 | suppress_naks = 1; | |
215 | break; | |
2b484d24 | 216 | |
8052502f | 217 | case 's': |
2b484d24 A |
218 | secure = 1; |
219 | securedir = optarg; | |
8052502f | 220 | break; |
2b484d24 | 221 | |
8052502f | 222 | case 'u': |
2b484d24 | 223 | tgtuser = optarg; |
8052502f | 224 | break; |
2b484d24 | 225 | |
b7080c8e | 226 | default: |
2b484d24 A |
227 | usage(); |
228 | break; | |
b7080c8e | 229 | } |
2b484d24 | 230 | |
b7080c8e A |
231 | if (optind < argc) { |
232 | struct dirlist *dirp; | |
233 | ||
234 | /* Get list of directory prefixes. Skip relative pathnames. */ | |
235 | for (dirp = dirs; optind < argc && dirp < &dirs[MAXDIRS]; | |
236 | optind++) { | |
237 | if (argv[optind][0] == '/') { | |
238 | dirp->name = argv[optind]; | |
239 | dirp->len = strlen(dirp->name); | |
240 | dirp++; | |
241 | } | |
242 | } | |
243 | } | |
2b484d24 A |
244 | |
245 | if (*tgtuser == '\0' || (tgtgroup != NULL && *tgtgroup == '\0')) | |
246 | usage(); | |
247 | ||
248 | nid = (strtol(tgtuser, &ep, 10)); | |
249 | if (*ep == '\0') { | |
250 | if (nid > UID_MAX) { | |
251 | syslog(LOG_ERR, "uid %ld is too large", nid); | |
252 | exit(1); | |
253 | } | |
254 | pwent = getpwuid((uid_t)nid); | |
255 | } else | |
256 | pwent = getpwnam(tgtuser); | |
257 | if (pwent == NULL) { | |
258 | syslog(LOG_ERR, "unknown user `%s'", tgtuser); | |
8052502f A |
259 | exit(1); |
260 | } | |
2b484d24 A |
261 | tgtuid = pwent->pw_uid; |
262 | tgtgid = pwent->pw_gid; | |
263 | ||
264 | if (tgtgroup != NULL) { | |
265 | nid = (strtol(tgtgroup, &ep, 10)); | |
266 | if (*ep == '\0') { | |
267 | if (nid > GID_MAX) { | |
268 | syslog(LOG_ERR, "gid %ld is too large", nid); | |
269 | exit(1); | |
270 | } | |
271 | grent = getgrgid((gid_t)nid); | |
272 | } else | |
273 | grent = getgrnam(tgtgroup); | |
274 | if (grent != NULL) | |
275 | tgtgid = grent->gr_gid; | |
276 | else { | |
277 | syslog(LOG_ERR, "unknown group `%s'", tgtgroup); | |
278 | exit(1); | |
279 | } | |
280 | } | |
281 | ||
282 | if (secure) { | |
283 | if (chdir(securedir) < 0) { | |
284 | syslog(LOG_ERR, "chdir %s: %m", securedir); | |
285 | exit(1); | |
286 | } | |
287 | if (chroot(".")) { | |
288 | syslog(LOG_ERR, "chroot: %m"); | |
289 | exit(1); | |
290 | } | |
291 | } | |
292 | ||
293 | if (logging) | |
294 | syslog(LOG_DEBUG, "running as user `%s' (%d), group `%s' (%d)", | |
295 | tgtuser, tgtuid, tgtgroup ? tgtgroup : "(unspecified)", | |
296 | tgtgid); | |
297 | if (curgid != tgtgid) { | |
298 | if (setgid(tgtgid)) { | |
299 | syslog(LOG_ERR, "setgid to %d: %m", (int)tgtgid); | |
300 | exit(1); | |
301 | } | |
302 | if (setgroups(0, NULL)) { | |
303 | syslog(LOG_ERR, "setgroups: %m"); | |
304 | exit(1); | |
305 | } | |
306 | } | |
307 | ||
308 | if (curuid != tgtuid) { | |
309 | if (setuid(tgtuid)) { | |
310 | syslog(LOG_ERR, "setuid to %d: %m", (int)tgtuid); | |
311 | exit(1); | |
312 | } | |
313 | } | |
b7080c8e A |
314 | |
315 | on = 1; | |
2b484d24 | 316 | if (ioctl(fd, FIONBIO, &on) < 0) { |
8052502f | 317 | syslog(LOG_ERR, "ioctl(FIONBIO): %m"); |
b7080c8e A |
318 | exit(1); |
319 | } | |
320 | fromlen = sizeof (from); | |
2b484d24 | 321 | n = recvfrom(fd, buf, sizeof (buf), 0, |
b7080c8e A |
322 | (struct sockaddr *)&from, &fromlen); |
323 | if (n < 0) { | |
8052502f | 324 | syslog(LOG_ERR, "recvfrom: %m"); |
b7080c8e A |
325 | exit(1); |
326 | } | |
327 | /* | |
328 | * Now that we have read the message out of the UDP | |
329 | * socket, we fork and exit. Thus, inetd will go back | |
330 | * to listening to the tftp port, and the next request | |
331 | * to come in will start up a new instance of tftpd. | |
332 | * | |
333 | * We do this so that inetd can run tftpd in "wait" mode. | |
334 | * The problem with tftpd running in "nowait" mode is that | |
335 | * inetd may get one or more successful "selects" on the | |
336 | * tftp port before we do our receive, so more than one | |
337 | * instance of tftpd may be started up. Worse, if tftpd | |
338 | * break before doing the above "recvfrom", inetd would | |
339 | * spawn endless instances, clogging the system. | |
340 | */ | |
341 | { | |
342 | int pid; | |
343 | int i, j; | |
344 | ||
345 | for (i = 1; i < 20; i++) { | |
346 | pid = fork(); | |
347 | if (pid < 0) { | |
348 | sleep(i); | |
349 | /* | |
350 | * flush out to most recently sent request. | |
351 | * | |
352 | * This may drop some request, but those | |
353 | * will be resent by the clients when | |
354 | * they timeout. The positive effect of | |
355 | * this flush is to (try to) prevent more | |
356 | * than one tftpd being started up to service | |
357 | * a single request from a single client. | |
358 | */ | |
359 | j = sizeof from; | |
2b484d24 | 360 | i = recvfrom(fd, buf, sizeof (buf), 0, |
b7080c8e A |
361 | (struct sockaddr *)&from, &j); |
362 | if (i > 0) { | |
363 | n = i; | |
364 | fromlen = j; | |
365 | } | |
366 | } else { | |
367 | break; | |
368 | } | |
369 | } | |
370 | if (pid < 0) { | |
8052502f | 371 | syslog(LOG_ERR, "fork: %m"); |
b7080c8e A |
372 | exit(1); |
373 | } else if (pid != 0) { | |
374 | exit(0); | |
375 | } | |
376 | } | |
8052502f A |
377 | |
378 | /* | |
2b484d24 A |
379 | * remember what address this was sent to, so we can respond on the |
380 | * same interface | |
8052502f | 381 | */ |
2b484d24 A |
382 | len = sizeof(me); |
383 | if (getsockname(fd, (struct sockaddr *)&me, &len) == 0) { | |
384 | switch (me.ss_family) { | |
385 | case AF_INET: | |
386 | ((struct sockaddr_in *)&me)->sin_port = 0; | |
387 | break; | |
388 | case AF_INET6: | |
389 | ((struct sockaddr_in6 *)&me)->sin6_port = 0; | |
390 | break; | |
391 | default: | |
392 | /* unsupported */ | |
393 | break; | |
8052502f | 394 | } |
2b484d24 A |
395 | } else { |
396 | memset(&me, 0, sizeof(me)); | |
397 | me.ss_family = from.ss_family; | |
398 | me.ss_len = from.ss_len; | |
8052502f A |
399 | } |
400 | ||
b7080c8e | 401 | alarm(0); |
2b484d24 | 402 | close(fd); |
b7080c8e | 403 | close(1); |
2b484d24 | 404 | peer = socket(from.ss_family, SOCK_DGRAM, 0); |
b7080c8e | 405 | if (peer < 0) { |
8052502f | 406 | syslog(LOG_ERR, "socket: %m"); |
b7080c8e A |
407 | exit(1); |
408 | } | |
2b484d24 | 409 | if (bind(peer, (struct sockaddr *)&me, me.ss_len) < 0) { |
8052502f | 410 | syslog(LOG_ERR, "bind: %m"); |
b7080c8e A |
411 | exit(1); |
412 | } | |
2b484d24 | 413 | if (connect(peer, (struct sockaddr *)&from, from.ss_len) < 0) { |
8052502f | 414 | syslog(LOG_ERR, "connect: %m"); |
b7080c8e A |
415 | exit(1); |
416 | } | |
2b484d24 A |
417 | soopt = 65536; /* larger than we'll ever need */ |
418 | if (setsockopt(peer, SOL_SOCKET, SO_SNDBUF, (void *) &soopt, sizeof(soopt)) < 0) { | |
419 | syslog(LOG_ERR, "set SNDBUF: %m"); | |
420 | exit(1); | |
421 | } | |
422 | if (setsockopt(peer, SOL_SOCKET, SO_RCVBUF, (void *) &soopt, sizeof(soopt)) < 0) { | |
423 | syslog(LOG_ERR, "set RCVBUF: %m"); | |
424 | exit(1); | |
425 | } | |
426 | ||
b7080c8e A |
427 | tp = (struct tftphdr *)buf; |
428 | tp->th_opcode = ntohs(tp->th_opcode); | |
429 | if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) | |
430 | tftp(tp, n); | |
431 | exit(1); | |
432 | } | |
433 | ||
2b484d24 A |
434 | static int |
435 | blk_handler(struct tftphdr *tp, char *opt, char *val, char *ack, | |
436 | int *ackl, int *ec) | |
437 | { | |
438 | unsigned long bsize; | |
439 | char *endp; | |
440 | int l; | |
b7080c8e | 441 | |
2b484d24 A |
442 | /* |
443 | * On these failures, we could just ignore the blocksize option. | |
444 | * Perhaps that should be a command-line option. | |
445 | */ | |
446 | errno = 0; | |
447 | bsize = strtoul(val, &endp, 10); | |
448 | if ((bsize == ULONG_MAX && errno == ERANGE) || *endp) { | |
449 | syslog(LOG_NOTICE, "%s: %s request for %s: " | |
450 | "illegal value %s for blksize option", | |
451 | verifyhost((struct sockaddr *)&from), | |
452 | tp->th_opcode == WRQ ? "write" : "read", | |
453 | tp->th_stuff, val); | |
454 | return 0; | |
455 | } | |
456 | if (bsize < 8 || bsize > 65464) { | |
457 | syslog(LOG_NOTICE, "%s: %s request for %s: " | |
458 | "out of range value %s for blksize option", | |
459 | verifyhost((struct sockaddr *)&from), | |
460 | tp->th_opcode == WRQ ? "write" : "read", | |
461 | tp->th_stuff, val); | |
462 | return 0; | |
463 | } | |
464 | ||
465 | tftp_blksize = bsize; | |
466 | strcpy(ack + *ackl, "blksize"); | |
467 | *ackl += 8; | |
468 | l = sprintf(ack + *ackl, "%lu", bsize); | |
469 | *ackl += l + 1; | |
470 | ||
471 | return 0; | |
472 | } | |
473 | ||
474 | static int | |
475 | timeout_handler(struct tftphdr *tp, char *opt, char *val, char *ack, | |
476 | int *ackl, int *ec) | |
477 | { | |
478 | unsigned long tout; | |
479 | char *endp; | |
480 | int l; | |
481 | ||
482 | errno = 0; | |
483 | tout = strtoul(val, &endp, 10); | |
484 | if ((tout == ULONG_MAX && errno == ERANGE) || *endp) { | |
485 | syslog(LOG_NOTICE, "%s: %s request for %s: " | |
486 | "illegal value %s for timeout option", | |
487 | verifyhost((struct sockaddr *)&from), | |
488 | tp->th_opcode == WRQ ? "write" : "read", | |
489 | tp->th_stuff, val); | |
490 | return 0; | |
491 | } | |
492 | if (tout < 1 || tout > 255) { | |
493 | syslog(LOG_NOTICE, "%s: %s request for %s: " | |
494 | "out of range value %s for timeout option", | |
495 | verifyhost((struct sockaddr *)&from), | |
496 | tp->th_opcode == WRQ ? "write" : "read", | |
497 | tp->th_stuff, val); | |
498 | return 0; | |
499 | } | |
500 | ||
501 | rexmtval = tout; | |
502 | strcpy(ack + *ackl, "timeout"); | |
503 | *ackl += 8; | |
504 | l = sprintf(ack + *ackl, "%lu", tout); | |
505 | *ackl += l + 1; | |
506 | ||
507 | /* | |
508 | * Arbitrarily pick a maximum timeout on a request to 3 | |
509 | * retransmissions if the interval timeout is more than | |
510 | * one minute. Longest possible timeout is therefore | |
511 | * 3 * 255 - 1, or 764 seconds. | |
512 | */ | |
513 | if (rexmtval > 60) { | |
514 | maxtimeout = rexmtval * 3; | |
515 | } else { | |
516 | maxtimeout = rexmtval * 5; | |
517 | } | |
518 | ||
519 | return 0; | |
520 | } | |
521 | ||
522 | static int | |
523 | tsize_handler(struct tftphdr *tp, char *opt, char *val, char *ack, | |
524 | int *ackl, int *ec) | |
525 | { | |
526 | unsigned long fsize; | |
527 | char *endp; | |
528 | ||
529 | /* | |
530 | * Maximum file even with extended tftp is 65535 blocks of | |
531 | * length 65464, or 4290183240 octets (4784056 less than 2^32). | |
532 | * unsigned long is at least 32 bits on all NetBSD archs. | |
533 | */ | |
534 | ||
535 | errno = 0; | |
536 | fsize = strtoul(val, &endp, 10); | |
537 | if ((fsize == ULONG_MAX && errno == ERANGE) || *endp) { | |
538 | syslog(LOG_NOTICE, "%s: %s request for %s: " | |
539 | "illegal value %s for tsize option", | |
540 | verifyhost((struct sockaddr *)&from), | |
541 | tp->th_opcode == WRQ ? "write" : "read", | |
542 | tp->th_stuff, val); | |
543 | return 0; | |
544 | } | |
545 | if (fsize > (unsigned long) 65535 * 65464) { | |
546 | syslog(LOG_NOTICE, "%s: %s request for %s: " | |
547 | "out of range value %s for tsize option", | |
548 | verifyhost((struct sockaddr *)&from), | |
549 | tp->th_opcode == WRQ ? "write" : "read", | |
550 | tp->th_stuff, val); | |
551 | return 0; | |
552 | } | |
553 | ||
554 | tftp_opt_tsize = 1; | |
555 | tftp_tsize = fsize; | |
556 | /* | |
557 | * We will report this later -- either replying with the fsize (WRQ) | |
558 | * or replying with the actual filesize (RRQ). | |
559 | */ | |
560 | ||
561 | return 0; | |
562 | } | |
563 | ||
564 | struct tftp_options { | |
565 | char *o_name; | |
566 | int (*o_handler)(struct tftphdr *, char *, char *, char *, | |
567 | int *, int *); | |
568 | } options[] = { | |
569 | { "blksize", blk_handler }, | |
570 | { "timeout", timeout_handler }, | |
571 | { "tsize", tsize_handler }, | |
572 | { NULL, NULL } | |
b7080c8e A |
573 | }; |
574 | ||
2b484d24 A |
575 | /* |
576 | * Get options for an extended tftp session. Stuff the ones we | |
577 | * recognize in oackbuf. | |
578 | */ | |
579 | static int | |
580 | get_options(struct tftphdr *tp, char *cp, int size, char *ackb, | |
581 | int *alen, int *err) | |
582 | { | |
583 | struct tftp_options *op; | |
584 | char *option, *value, *endp; | |
585 | int r, rv=0, ec=0; | |
586 | ||
587 | endp = cp + size; | |
588 | while (cp < endp) { | |
589 | option = cp; | |
590 | while (*cp && cp < endp) { | |
591 | *cp = tolower(*cp); | |
592 | cp++; | |
593 | } | |
594 | if (*cp) { | |
595 | /* if we have garbage at the end, just ignore it */ | |
596 | break; | |
597 | } | |
598 | cp++; /* skip over NUL */ | |
599 | value = cp; | |
600 | while (*cp && cp < endp) { | |
601 | cp++; | |
602 | } | |
603 | if (*cp) { | |
604 | /* if we have garbage at the end, just ignore it */ | |
605 | break; | |
606 | } | |
607 | cp++; | |
608 | for (op = options; op->o_name; op++) { | |
609 | if (strcmp(op->o_name, option) == 0) | |
610 | break; | |
611 | } | |
612 | if (op->o_name) { | |
613 | r = op->o_handler(tp, option, value, ackb, alen, &ec); | |
614 | if (r < 0) { | |
615 | rv = -1; | |
616 | break; | |
617 | } | |
618 | rv++; | |
619 | } /* else ignore unknown options */ | |
620 | } | |
621 | ||
622 | if (rv < 0) | |
623 | *err = ec; | |
624 | ||
625 | return rv; | |
626 | } | |
627 | ||
b7080c8e A |
628 | /* |
629 | * Handle initial connection protocol. | |
630 | */ | |
2b484d24 A |
631 | static void |
632 | tftp(struct tftphdr *tp, int size) | |
b7080c8e | 633 | { |
2b484d24 A |
634 | struct formats *pf; |
635 | char *cp; | |
636 | char *filename, *mode; | |
637 | int first, ecode, alen, etftp=0, r; | |
638 | ||
639 | first = 1; | |
640 | mode = NULL; | |
b7080c8e A |
641 | |
642 | filename = cp = tp->th_stuff; | |
643 | again: | |
644 | while (cp < buf + size) { | |
645 | if (*cp == '\0') | |
646 | break; | |
647 | cp++; | |
648 | } | |
649 | if (*cp != '\0') { | |
650 | nak(EBADOP); | |
651 | exit(1); | |
652 | } | |
653 | if (first) { | |
654 | mode = ++cp; | |
655 | first = 0; | |
656 | goto again; | |
657 | } | |
658 | for (cp = mode; *cp; cp++) | |
659 | if (isupper(*cp)) | |
660 | *cp = tolower(*cp); | |
661 | for (pf = formats; pf->f_mode; pf++) | |
662 | if (strcmp(pf->f_mode, mode) == 0) | |
663 | break; | |
664 | if (pf->f_mode == 0) { | |
665 | nak(EBADOP); | |
666 | exit(1); | |
667 | } | |
2b484d24 A |
668 | /* |
669 | * cp currently points to the NUL byte following the mode. | |
670 | * | |
671 | * If we have some valid options, then let's assume that we're | |
672 | * now dealing with an extended tftp session. Note that if we | |
673 | * don't get any options, then we *must* assume that we do not | |
674 | * have an extended tftp session. If we get options, we fill | |
675 | * in the ack buf to acknowledge them. If we skip that, then | |
676 | * the client *must* assume that we are not using an extended | |
677 | * session. | |
678 | */ | |
679 | size -= (++cp - (char *) tp); | |
680 | if (size > 0 && *cp) { | |
681 | alen = 2; /* Skip over opcode */ | |
682 | r = get_options(tp, cp, size, oackbuf, &alen, &ecode); | |
683 | if (r > 0) { | |
684 | etftp = 1; | |
685 | } else if (r < 0) { | |
686 | nak(ecode); | |
687 | exit(1); | |
688 | } | |
689 | } | |
b7080c8e A |
690 | ecode = (*pf->f_validate)(&filename, tp->th_opcode); |
691 | if (logging) { | |
2b484d24 A |
692 | syslog(LOG_INFO, "%s: %s request for %s: %s", |
693 | verifyhost((struct sockaddr *)&from), | |
b7080c8e A |
694 | tp->th_opcode == WRQ ? "write" : "read", |
695 | filename, errtomsg(ecode)); | |
696 | } | |
697 | if (ecode) { | |
698 | /* | |
699 | * Avoid storms of naks to a RRQ broadcast for a relative | |
700 | * bootfile pathname from a diskless Sun. | |
701 | */ | |
702 | if (suppress_naks && *filename != '/' && ecode == ENOTFOUND) | |
703 | exit(0); | |
704 | nak(ecode); | |
705 | exit(1); | |
706 | } | |
2b484d24 A |
707 | |
708 | if (etftp) { | |
709 | struct tftphdr *oack_h; | |
710 | ||
711 | if (tftp_opt_tsize) { | |
712 | int l; | |
713 | ||
714 | strcpy(oackbuf + alen, "tsize"); | |
715 | alen += 6; | |
716 | l = sprintf(oackbuf + alen, "%u", tftp_tsize); | |
717 | alen += l + 1; | |
718 | } | |
719 | oack_h = (struct tftphdr *) oackbuf; | |
720 | oack_h->th_opcode = htons(OACK); | |
721 | } | |
722 | ||
b7080c8e | 723 | if (tp->th_opcode == WRQ) |
2b484d24 | 724 | (*pf->f_recv)(pf, etftp, alen); |
b7080c8e | 725 | else |
2b484d24 | 726 | (*pf->f_send)(pf, etftp, alen); |
b7080c8e A |
727 | exit(0); |
728 | } | |
729 | ||
730 | ||
731 | FILE *file; | |
732 | ||
733 | /* | |
734 | * Validate file access. Since we | |
735 | * have no uid or gid, for now require | |
736 | * file to exist and be publicly | |
737 | * readable/writable. | |
738 | * If we were invoked with arguments | |
739 | * from inetd then the file must also be | |
740 | * in one of the given directory prefixes. | |
b7080c8e A |
741 | */ |
742 | int | |
2b484d24 | 743 | validate_access(char **filep, int mode) |
b7080c8e | 744 | { |
2b484d24 A |
745 | struct stat stbuf; |
746 | struct dirlist *dirp; | |
747 | static char pathname[MAXPATHLEN]; | |
748 | int fd; | |
749 | char *filename; | |
750 | #ifdef __APPLE__ | |
751 | static char resolved_path[PATH_MAX+1]; | |
752 | bzero(resolved_path,PATH_MAX+1); | |
753 | if(insecure) { | |
754 | filename = *filep; | |
755 | } else { | |
756 | if (realpath(*filep, resolved_path)==NULL) { | |
757 | return (EACCESS); | |
758 | } | |
759 | filename = resolved_path; | |
760 | } | |
761 | #else | |
762 | filename = *filep; | |
763 | #endif | |
b7080c8e A |
764 | /* |
765 | * Prevent tricksters from getting around the directory restrictions | |
766 | */ | |
767 | if (strstr(filename, "/../")) | |
768 | return (EACCESS); | |
769 | ||
770 | if (*filename == '/') { | |
771 | /* | |
772 | * Allow the request if it's in one of the approved locations. | |
773 | * Special case: check the null prefix ("/") by looking | |
774 | * for length = 1 and relying on the arg. processing that | |
775 | * it's a /. | |
776 | */ | |
777 | for (dirp = dirs; dirp->name != NULL; dirp++) { | |
778 | if (dirp->len == 1 || | |
779 | (!strncmp(filename, dirp->name, dirp->len) && | |
780 | filename[dirp->len] == '/')) | |
781 | break; | |
782 | } | |
783 | /* If directory list is empty, allow access to any file */ | |
784 | if (dirp->name == NULL && dirp != dirs) | |
785 | return (EACCESS); | |
786 | if (stat(filename, &stbuf) < 0) | |
787 | return (errno == ENOENT ? ENOTFOUND : EACCESS); | |
2b484d24 | 788 | if (!S_ISREG(stbuf.st_mode)) |
b7080c8e A |
789 | return (ENOTFOUND); |
790 | if (mode == RRQ) { | |
791 | if ((stbuf.st_mode & S_IROTH) == 0) | |
792 | return (EACCESS); | |
793 | } else { | |
794 | if ((stbuf.st_mode & S_IWOTH) == 0) | |
795 | return (EACCESS); | |
796 | } | |
797 | } else { | |
b7080c8e A |
798 | /* |
799 | * Relative file name: search the approved locations for it. | |
b7080c8e A |
800 | */ |
801 | ||
8052502f | 802 | if (!strncmp(filename, "../", 3)) |
b7080c8e A |
803 | return (EACCESS); |
804 | ||
805 | /* | |
2b484d24 A |
806 | * Find the first file that exists in any of the directories, |
807 | * check access on it. | |
b7080c8e | 808 | */ |
2b484d24 A |
809 | if (dirs[0].name != NULL) { |
810 | for (dirp = dirs; dirp->name != NULL; dirp++) { | |
811 | snprintf(pathname, sizeof pathname, "%s/%s", | |
812 | dirp->name, filename); | |
813 | if (stat(pathname, &stbuf) == 0 && | |
814 | (stbuf.st_mode & S_IFMT) == S_IFREG) { | |
b7080c8e A |
815 | break; |
816 | } | |
b7080c8e | 817 | } |
2b484d24 A |
818 | if (dirp->name == NULL) |
819 | return (ENOTFOUND); | |
820 | if (mode == RRQ && !(stbuf.st_mode & S_IROTH)) | |
821 | return (EACCESS); | |
822 | if (mode == WRQ && !(stbuf.st_mode & S_IWOTH)) | |
823 | return (EACCESS); | |
824 | filename = pathname; | |
825 | *filep = filename; | |
826 | } else { | |
827 | /* | |
828 | * If there's no directory list, take our cue from the | |
829 | * absolute file request check above (*filename == '/'), | |
830 | * and allow access to anything. | |
831 | */ | |
832 | if (stat(filename, &stbuf) < 0) | |
833 | return (errno == ENOENT ? ENOTFOUND : EACCESS); | |
834 | if (!S_ISREG(stbuf.st_mode)) | |
835 | return (ENOTFOUND); | |
836 | if (mode == RRQ) { | |
837 | if ((stbuf.st_mode & S_IROTH) == 0) | |
838 | return (EACCESS); | |
839 | } else { | |
840 | if ((stbuf.st_mode & S_IWOTH) == 0) | |
841 | return (EACCESS); | |
842 | } | |
843 | *filep = filename; | |
b7080c8e | 844 | } |
b7080c8e | 845 | } |
2b484d24 A |
846 | |
847 | if (tftp_opt_tsize && mode == RRQ) | |
848 | tftp_tsize = (unsigned long) stbuf.st_size; | |
849 | ||
850 | fd = open(filename, mode == RRQ ? O_RDONLY : O_WRONLY | O_TRUNC); | |
b7080c8e A |
851 | if (fd < 0) |
852 | return (errno + 100); | |
853 | file = fdopen(fd, (mode == RRQ)? "r":"w"); | |
854 | if (file == NULL) { | |
2b484d24 A |
855 | close(fd); |
856 | return (errno + 100); | |
b7080c8e A |
857 | } |
858 | return (0); | |
859 | } | |
860 | ||
861 | int timeout; | |
862 | jmp_buf timeoutbuf; | |
863 | ||
864 | void | |
2b484d24 | 865 | timer(int dummy) |
b7080c8e A |
866 | { |
867 | ||
868 | timeout += rexmtval; | |
869 | if (timeout >= maxtimeout) | |
870 | exit(1); | |
871 | longjmp(timeoutbuf, 1); | |
872 | } | |
873 | ||
2b484d24 A |
874 | static const char * |
875 | opcode(int code) | |
876 | { | |
877 | static char buf[64]; | |
878 | ||
879 | switch (code) { | |
880 | case RRQ: | |
881 | return "RRQ"; | |
882 | case WRQ: | |
883 | return "WRQ"; | |
884 | case DATA: | |
885 | return "DATA"; | |
886 | case ACK: | |
887 | return "ACK"; | |
888 | case ERROR: | |
889 | return "ERROR"; | |
890 | case OACK: | |
891 | return "OACK"; | |
892 | default: | |
893 | (void)snprintf(buf, sizeof(buf), "*code %d*", code); | |
894 | return buf; | |
895 | } | |
896 | } | |
897 | ||
b7080c8e A |
898 | /* |
899 | * Send the requested file. | |
900 | */ | |
901 | void | |
2b484d24 | 902 | sendfile(struct formats *pf, int etftp, int acklength) |
b7080c8e | 903 | { |
2b484d24 A |
904 | volatile unsigned int block; |
905 | struct tftphdr *dp; | |
906 | struct tftphdr *ap; /* ack packet */ | |
907 | int size, n; | |
b7080c8e A |
908 | |
909 | signal(SIGALRM, timer); | |
b7080c8e | 910 | ap = (struct tftphdr *)ackbuf; |
2b484d24 A |
911 | if (etftp) { |
912 | dp = (struct tftphdr *)oackbuf; | |
913 | size = acklength - 4; | |
914 | block = 0; | |
915 | } else { | |
916 | dp = r_init(); | |
917 | size = 0; | |
918 | block = 1; | |
919 | } | |
920 | ||
b7080c8e | 921 | do { |
2b484d24 A |
922 | if (block > 0) { |
923 | size = readit(file, &dp, tftp_blksize, pf->f_convert); | |
924 | if (size < 0) { | |
925 | nak(errno + 100); | |
926 | goto abort; | |
927 | } | |
928 | dp->th_opcode = htons((u_short)DATA); | |
929 | dp->th_block = htons((u_short)block); | |
b7080c8e | 930 | } |
b7080c8e A |
931 | timeout = 0; |
932 | (void)setjmp(timeoutbuf); | |
933 | ||
934 | send_data: | |
2b484d24 A |
935 | if (!etftp && debug) |
936 | syslog(LOG_DEBUG, "Send DATA %u", block); | |
937 | if ((n = send(peer, dp, size + 4, 0)) != size + 4) { | |
938 | syslog(LOG_ERR, "tftpd: write: %m"); | |
b7080c8e A |
939 | goto abort; |
940 | } | |
2b484d24 A |
941 | if (block) |
942 | read_ahead(file, tftp_blksize, pf->f_convert); | |
b7080c8e A |
943 | for ( ; ; ) { |
944 | alarm(rexmtval); /* read the ack */ | |
2b484d24 | 945 | n = recv(peer, ackbuf, tftp_blksize, 0); |
b7080c8e A |
946 | alarm(0); |
947 | if (n < 0) { | |
2b484d24 | 948 | syslog(LOG_ERR, "tftpd: read: %m"); |
b7080c8e A |
949 | goto abort; |
950 | } | |
951 | ap->th_opcode = ntohs((u_short)ap->th_opcode); | |
952 | ap->th_block = ntohs((u_short)ap->th_block); | |
2b484d24 A |
953 | switch (ap->th_opcode) { |
954 | case ERROR: | |
b7080c8e A |
955 | goto abort; |
956 | ||
2b484d24 A |
957 | case ACK: |
958 | if (ap->th_block == 0) { | |
959 | etftp = 0; | |
960 | acklength = 0; | |
961 | dp = r_init(); | |
962 | goto done; | |
963 | } | |
b7080c8e | 964 | if (ap->th_block == block) |
2b484d24 A |
965 | goto done; |
966 | if (debug) | |
967 | syslog(LOG_DEBUG, "Resync ACK %u != %u", | |
968 | (unsigned int)ap->th_block, block); | |
b7080c8e | 969 | /* Re-synchronize with the other side */ |
2b484d24 | 970 | (void) synchnet(peer, tftp_blksize); |
b7080c8e A |
971 | if (ap->th_block == (block -1)) |
972 | goto send_data; | |
2b484d24 A |
973 | default: |
974 | syslog(LOG_INFO, "Received %s in sendfile\n", | |
975 | opcode(dp->th_opcode)); | |
b7080c8e A |
976 | } |
977 | ||
978 | } | |
2b484d24 A |
979 | done: |
980 | if (debug) | |
981 | syslog(LOG_DEBUG, "Received ACK for block %u", block); | |
b7080c8e | 982 | block++; |
2b484d24 | 983 | } while (size == tftp_blksize || block == 1); |
b7080c8e A |
984 | abort: |
985 | (void) fclose(file); | |
986 | } | |
987 | ||
988 | void | |
2b484d24 | 989 | justquit(int dummy) |
b7080c8e | 990 | { |
2b484d24 | 991 | |
b7080c8e A |
992 | exit(0); |
993 | } | |
994 | ||
b7080c8e A |
995 | /* |
996 | * Receive a file. | |
997 | */ | |
998 | void | |
2b484d24 | 999 | recvfile(struct formats *pf, int etftp, int acklength) |
b7080c8e | 1000 | { |
2b484d24 A |
1001 | volatile unsigned int block; |
1002 | struct tftphdr *dp; | |
1003 | struct tftphdr *ap; /* ack buffer */ | |
1004 | int n, size; | |
b7080c8e A |
1005 | |
1006 | signal(SIGALRM, timer); | |
1007 | dp = w_init(); | |
2b484d24 | 1008 | ap = (struct tftphdr *)oackbuf; |
b7080c8e A |
1009 | block = 0; |
1010 | do { | |
1011 | timeout = 0; | |
2b484d24 A |
1012 | if (etftp == 0) { |
1013 | ap = (struct tftphdr *)ackbuf; | |
1014 | ap->th_opcode = htons((u_short)ACK); | |
1015 | ap->th_block = htons((u_short)block); | |
1016 | acklength = 4; | |
1017 | } | |
1018 | if (debug) | |
1019 | syslog(LOG_DEBUG, "Sending ACK for block %u\n", block); | |
b7080c8e A |
1020 | block++; |
1021 | (void) setjmp(timeoutbuf); | |
1022 | send_ack: | |
2b484d24 A |
1023 | if (send(peer, ap, acklength, 0) != acklength) { |
1024 | syslog(LOG_ERR, "tftpd: write: %m"); | |
b7080c8e A |
1025 | goto abort; |
1026 | } | |
1027 | write_behind(file, pf->f_convert); | |
1028 | for ( ; ; ) { | |
1029 | alarm(rexmtval); | |
2b484d24 | 1030 | n = recv(peer, dp, tftp_blksize + 4, 0); |
b7080c8e A |
1031 | alarm(0); |
1032 | if (n < 0) { /* really? */ | |
2b484d24 | 1033 | syslog(LOG_ERR, "tftpd: read: %m"); |
b7080c8e A |
1034 | goto abort; |
1035 | } | |
2b484d24 | 1036 | etftp = 0; |
b7080c8e A |
1037 | dp->th_opcode = ntohs((u_short)dp->th_opcode); |
1038 | dp->th_block = ntohs((u_short)dp->th_block); | |
2b484d24 A |
1039 | if (debug) |
1040 | syslog(LOG_DEBUG, "Received %s for block %u", | |
1041 | opcode(dp->th_opcode), | |
1042 | (unsigned int)dp->th_block); | |
1043 | ||
1044 | switch (dp->th_opcode) { | |
1045 | case ERROR: | |
b7080c8e | 1046 | goto abort; |
2b484d24 A |
1047 | case DATA: |
1048 | if (dp->th_block == block) | |
1049 | goto done; /* normal */ | |
1050 | if (debug) | |
1051 | syslog(LOG_DEBUG, "Resync %u != %u", | |
1052 | (unsigned int)dp->th_block, block); | |
b7080c8e | 1053 | /* Re-synchronize with the other side */ |
2b484d24 | 1054 | (void) synchnet(peer, tftp_blksize); |
b7080c8e A |
1055 | if (dp->th_block == (block-1)) |
1056 | goto send_ack; /* rexmit */ | |
2b484d24 A |
1057 | break; |
1058 | default: | |
1059 | syslog(LOG_INFO, "Received %s in recvfile\n", | |
1060 | opcode(dp->th_opcode)); | |
1061 | break; | |
b7080c8e A |
1062 | } |
1063 | } | |
2b484d24 A |
1064 | done: |
1065 | if (debug) | |
1066 | syslog(LOG_DEBUG, "Got block %u", block); | |
b7080c8e A |
1067 | /* size = write(file, dp->th_data, n - 4); */ |
1068 | size = writeit(file, &dp, n - 4, pf->f_convert); | |
1069 | if (size != (n-4)) { /* ahem */ | |
1070 | if (size < 0) nak(errno + 100); | |
1071 | else nak(ENOSPACE); | |
1072 | goto abort; | |
1073 | } | |
2b484d24 | 1074 | } while (size == tftp_blksize); |
b7080c8e A |
1075 | write_behind(file, pf->f_convert); |
1076 | (void) fclose(file); /* close data file */ | |
1077 | ||
1078 | ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ | |
1079 | ap->th_block = htons((u_short)(block)); | |
2b484d24 A |
1080 | if (debug) |
1081 | syslog(LOG_DEBUG, "Send final ACK %u", block); | |
b7080c8e A |
1082 | (void) send(peer, ackbuf, 4, 0); |
1083 | ||
1084 | signal(SIGALRM, justquit); /* just quit on timeout */ | |
1085 | alarm(rexmtval); | |
1086 | n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ | |
1087 | alarm(0); | |
1088 | if (n >= 4 && /* if read some data */ | |
1089 | dp->th_opcode == DATA && /* and got a data block */ | |
1090 | block == dp->th_block) { /* then my last ack was lost */ | |
1091 | (void) send(peer, ackbuf, 4, 0); /* resend final ack */ | |
1092 | } | |
1093 | abort: | |
1094 | return; | |
1095 | } | |
1096 | ||
2b484d24 A |
1097 | const struct errmsg { |
1098 | int e_code; | |
1099 | const char *e_msg; | |
b7080c8e A |
1100 | } errmsgs[] = { |
1101 | { EUNDEF, "Undefined error code" }, | |
1102 | { ENOTFOUND, "File not found" }, | |
1103 | { EACCESS, "Access violation" }, | |
1104 | { ENOSPACE, "Disk full or allocation exceeded" }, | |
1105 | { EBADOP, "Illegal TFTP operation" }, | |
1106 | { EBADID, "Unknown transfer ID" }, | |
1107 | { EEXISTS, "File already exists" }, | |
1108 | { ENOUSER, "No such user" }, | |
2b484d24 | 1109 | { EOPTNEG, "Option negotiation failed" }, |
b7080c8e A |
1110 | { -1, 0 } |
1111 | }; | |
1112 | ||
2b484d24 A |
1113 | static const char * |
1114 | errtomsg(int error) | |
b7080c8e | 1115 | { |
2b484d24 A |
1116 | static char ebuf[20]; |
1117 | const struct errmsg *pe; | |
1118 | ||
b7080c8e | 1119 | if (error == 0) |
2b484d24 | 1120 | return ("success"); |
b7080c8e A |
1121 | for (pe = errmsgs; pe->e_code >= 0; pe++) |
1122 | if (pe->e_code == error) | |
2b484d24 A |
1123 | return (pe->e_msg); |
1124 | snprintf(ebuf, sizeof(ebuf), "error %d", error); | |
1125 | return (ebuf); | |
b7080c8e A |
1126 | } |
1127 | ||
1128 | /* | |
1129 | * Send a nak packet (error message). | |
1130 | * Error code passed in is one of the | |
1131 | * standard TFTP codes, or a UNIX errno | |
1132 | * offset by 100. | |
1133 | */ | |
1134 | static void | |
2b484d24 | 1135 | nak(int error) |
b7080c8e | 1136 | { |
2b484d24 A |
1137 | const struct errmsg *pe; |
1138 | struct tftphdr *tp; | |
1139 | int length; | |
1140 | size_t msglen; | |
b7080c8e A |
1141 | |
1142 | tp = (struct tftphdr *)buf; | |
1143 | tp->th_opcode = htons((u_short)ERROR); | |
2b484d24 | 1144 | msglen = sizeof(buf) - (&tp->th_msg[0] - buf); |
b7080c8e A |
1145 | for (pe = errmsgs; pe->e_code >= 0; pe++) |
1146 | if (pe->e_code == error) | |
1147 | break; | |
1148 | if (pe->e_code < 0) { | |
b7080c8e | 1149 | tp->th_code = EUNDEF; /* set 'undef' errorcode */ |
2b484d24 A |
1150 | strlcpy(tp->th_msg, strerror(error - 100), msglen); |
1151 | } else { | |
1152 | tp->th_code = htons((u_short)error); | |
1153 | strlcpy(tp->th_msg, pe->e_msg, msglen); | |
b7080c8e | 1154 | } |
2b484d24 A |
1155 | if (debug) |
1156 | syslog(LOG_DEBUG, "Send NACK %s", tp->th_msg); | |
1157 | length = strlen(tp->th_msg); | |
1158 | msglen = &tp->th_msg[length + 1] - buf; | |
1159 | if (send(peer, buf, msglen, 0) != msglen) | |
8052502f | 1160 | syslog(LOG_ERR, "nak: %m"); |
b7080c8e A |
1161 | } |
1162 | ||
1163 | static char * | |
2b484d24 | 1164 | verifyhost(struct sockaddr *fromp) |
b7080c8e | 1165 | { |
2b484d24 | 1166 | static char hbuf[MAXHOSTNAMELEN]; |
b7080c8e | 1167 | |
2b484d24 A |
1168 | if (getnameinfo(fromp, fromp->sa_len, hbuf, sizeof(hbuf), NULL, 0, 0)) |
1169 | strlcpy(hbuf, "?", sizeof(hbuf)); | |
1170 | return (hbuf); | |
b7080c8e | 1171 | } |