Commit | Line | Data |
---|---|---|
15637ed4 RG |
1 | /* |
2 | * Copyright (c) 1983 Regents of the University of California. | |
3 | * All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: | |
8 | * 1. Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * 2. Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * 3. All advertising materials mentioning features or use of this software | |
14 | * must display the following acknowledgement: | |
15 | * This product includes software developed by the University of | |
16 | * California, Berkeley and its contributors. | |
17 | * 4. Neither the name of the University nor the names of its contributors | |
18 | * may be used to endorse or promote products derived from this software | |
19 | * without specific prior written permission. | |
20 | * | |
21 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
27 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
29 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
30 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
31 | * SUCH DAMAGE. | |
32 | */ | |
33 | ||
34 | #ifndef lint | |
35 | char copyright[] = | |
36 | "@(#) Copyright (c) 1983 Regents of the University of California.\n\ | |
37 | All rights reserved.\n"; | |
38 | #endif /* not lint */ | |
39 | ||
40 | #ifndef lint | |
41 | static char sccsid[] = "@(#)tftpd.c 5.13 (Berkeley) 2/26/91"; | |
42 | #endif /* not lint */ | |
43 | ||
44 | /* | |
45 | * Trivial file transfer protocol server. | |
46 | * | |
47 | * This version includes many modifications by Jim Guyton <guyton@rand-unix> | |
48 | */ | |
49 | ||
50 | #include <sys/types.h> | |
51 | #include <sys/ioctl.h> | |
52 | #include <sys/stat.h> | |
53 | #include <signal.h> | |
54 | #include <fcntl.h> | |
55 | ||
56 | #include <sys/socket.h> | |
57 | #include <netinet/in.h> | |
58 | #include <arpa/tftp.h> | |
59 | #include <netdb.h> | |
60 | ||
61 | #include <setjmp.h> | |
62 | #include <syslog.h> | |
63 | #include <stdio.h> | |
64 | #include <errno.h> | |
65 | #include <ctype.h> | |
66 | #include <string.h> | |
67 | #include <stdlib.h> | |
68 | ||
69 | #define TIMEOUT 5 | |
70 | ||
71 | extern int errno; | |
2bc2fe21 | 72 | struct sockaddr_in s_in = { AF_INET }; |
15637ed4 RG |
73 | int peer; |
74 | int rexmtval = TIMEOUT; | |
75 | int maxtimeout = 5*TIMEOUT; | |
76 | ||
77 | #define PKTSIZE SEGSIZE+4 | |
78 | char buf[PKTSIZE]; | |
79 | char ackbuf[PKTSIZE]; | |
80 | struct sockaddr_in from; | |
81 | int fromlen; | |
82 | ||
83 | #define MAXARG 4 | |
84 | char *dirs[MAXARG+1]; | |
85 | ||
86 | main(ac, av) | |
87 | char **av; | |
88 | { | |
89 | register struct tftphdr *tp; | |
90 | register int n = 0; | |
91 | int on = 1; | |
92 | ||
93 | ac--; av++; | |
94 | while (ac-- > 0 && n < MAXARG) | |
95 | dirs[n++] = *av++; | |
96 | openlog("tftpd", LOG_PID, LOG_DAEMON); | |
97 | if (ioctl(0, FIONBIO, &on) < 0) { | |
98 | syslog(LOG_ERR, "ioctl(FIONBIO): %m\n"); | |
99 | exit(1); | |
100 | } | |
101 | fromlen = sizeof (from); | |
102 | n = recvfrom(0, buf, sizeof (buf), 0, | |
103 | (struct sockaddr *)&from, &fromlen); | |
104 | if (n < 0) { | |
105 | syslog(LOG_ERR, "recvfrom: %m\n"); | |
106 | exit(1); | |
107 | } | |
108 | /* | |
109 | * Now that we have read the message out of the UDP | |
110 | * socket, we fork and exit. Thus, inetd will go back | |
111 | * to listening to the tftp port, and the next request | |
112 | * to come in will start up a new instance of tftpd. | |
113 | * | |
114 | * We do this so that inetd can run tftpd in "wait" mode. | |
115 | * The problem with tftpd running in "nowait" mode is that | |
116 | * inetd may get one or more successful "selects" on the | |
117 | * tftp port before we do our receive, so more than one | |
118 | * instance of tftpd may be started up. Worse, if tftpd | |
119 | * break before doing the above "recvfrom", inetd would | |
120 | * spawn endless instances, clogging the system. | |
121 | */ | |
122 | { | |
123 | int pid; | |
124 | int i, j; | |
125 | ||
126 | for (i = 1; i < 20; i++) { | |
127 | pid = fork(); | |
128 | if (pid < 0) { | |
129 | sleep(i); | |
130 | /* | |
131 | * flush out to most recently sent request. | |
132 | * | |
133 | * This may drop some request, but those | |
134 | * will be resent by the clients when | |
135 | * they timeout. The positive effect of | |
136 | * this flush is to (try to) prevent more | |
137 | * than one tftpd being started up to service | |
138 | * a single request from a single client. | |
139 | */ | |
140 | j = sizeof from; | |
141 | i = recvfrom(0, buf, sizeof (buf), 0, | |
142 | (struct sockaddr *)&from, &j); | |
143 | if (i > 0) { | |
144 | n = i; | |
145 | fromlen = j; | |
146 | } | |
147 | } else { | |
148 | break; | |
149 | } | |
150 | } | |
151 | if (pid < 0) { | |
152 | syslog(LOG_ERR, "fork: %m\n"); | |
153 | exit(1); | |
154 | } else if (pid != 0) { | |
155 | exit(0); | |
156 | } | |
157 | } | |
158 | from.sin_family = AF_INET; | |
159 | alarm(0); | |
160 | close(0); | |
161 | close(1); | |
162 | peer = socket(AF_INET, SOCK_DGRAM, 0); | |
163 | if (peer < 0) { | |
164 | syslog(LOG_ERR, "socket: %m\n"); | |
165 | exit(1); | |
166 | } | |
2bc2fe21 | 167 | if (bind(peer, (struct sockaddr *)&s_in, sizeof (s_in)) < 0) { |
15637ed4 RG |
168 | syslog(LOG_ERR, "bind: %m\n"); |
169 | exit(1); | |
170 | } | |
171 | if (connect(peer, (struct sockaddr *)&from, sizeof(from)) < 0) { | |
172 | syslog(LOG_ERR, "connect: %m\n"); | |
173 | exit(1); | |
174 | } | |
175 | tp = (struct tftphdr *)buf; | |
176 | tp->th_opcode = ntohs(tp->th_opcode); | |
177 | if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) | |
178 | tftp(tp, n); | |
179 | exit(1); | |
180 | } | |
181 | ||
182 | int validate_access(); | |
183 | int sendfile(), recvfile(); | |
184 | ||
185 | struct formats { | |
186 | char *f_mode; | |
187 | int (*f_validate)(); | |
188 | int (*f_send)(); | |
189 | int (*f_recv)(); | |
190 | int f_convert; | |
191 | } formats[] = { | |
192 | { "netascii", validate_access, sendfile, recvfile, 1 }, | |
193 | { "octet", validate_access, sendfile, recvfile, 0 }, | |
194 | #ifdef notdef | |
195 | { "mail", validate_user, sendmail, recvmail, 1 }, | |
196 | #endif | |
197 | { 0 } | |
198 | }; | |
199 | ||
200 | /* | |
201 | * Handle initial connection protocol. | |
202 | */ | |
203 | tftp(tp, size) | |
204 | struct tftphdr *tp; | |
205 | int size; | |
206 | { | |
207 | register char *cp; | |
208 | int first = 1, ecode; | |
209 | register struct formats *pf; | |
210 | char *filename, *mode; | |
211 | ||
212 | filename = cp = tp->th_stuff; | |
213 | again: | |
214 | while (cp < buf + size) { | |
215 | if (*cp == '\0') | |
216 | break; | |
217 | cp++; | |
218 | } | |
219 | if (*cp != '\0') { | |
220 | nak(EBADOP); | |
221 | exit(1); | |
222 | } | |
223 | if (first) { | |
224 | mode = ++cp; | |
225 | first = 0; | |
226 | goto again; | |
227 | } | |
228 | for (cp = mode; *cp; cp++) | |
229 | if (isupper(*cp)) | |
230 | *cp = tolower(*cp); | |
231 | for (pf = formats; pf->f_mode; pf++) | |
232 | if (strcmp(pf->f_mode, mode) == 0) | |
233 | break; | |
234 | if (pf->f_mode == 0) { | |
235 | nak(EBADOP); | |
236 | exit(1); | |
237 | } | |
238 | ecode = (*pf->f_validate)(filename, tp->th_opcode); | |
239 | if (ecode) { | |
240 | nak(ecode); | |
241 | exit(1); | |
242 | } | |
243 | if (tp->th_opcode == WRQ) | |
244 | (*pf->f_recv)(pf); | |
245 | else | |
246 | (*pf->f_send)(pf); | |
247 | exit(0); | |
248 | } | |
249 | ||
250 | ||
251 | FILE *file; | |
252 | ||
253 | /* | |
254 | * Validate file access. Since we | |
255 | * have no uid or gid, for now require | |
256 | * file to exist and be publicly | |
257 | * readable/writable. | |
258 | * If we were invoked with arguments | |
259 | * from inetd then the file must also be | |
260 | * in one of the given directory prefixes. | |
261 | * Note also, full path name must be | |
262 | * given as we have no login directory. | |
263 | */ | |
264 | validate_access(filename, mode) | |
265 | char *filename; | |
266 | int mode; | |
267 | { | |
268 | struct stat stbuf; | |
269 | int fd; | |
270 | char *cp, **dirp; | |
271 | ||
272 | if (*filename != '/') | |
273 | return (EACCESS); | |
274 | /* | |
275 | * prevent tricksters from getting around the directory restrictions | |
276 | */ | |
277 | for (cp = filename + 1; *cp; cp++) | |
278 | if(*cp == '.' && strncmp(cp-1, "/../", 4) == 0) | |
279 | return(EACCESS); | |
280 | for (dirp = dirs; *dirp; dirp++) | |
281 | if (strncmp(filename, *dirp, strlen(*dirp)) == 0) | |
282 | break; | |
283 | if (*dirp==0 && dirp!=dirs) | |
284 | return (EACCESS); | |
285 | if (stat(filename, &stbuf) < 0) | |
286 | return (errno == ENOENT ? ENOTFOUND : EACCESS); | |
287 | if (mode == RRQ) { | |
288 | if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) | |
289 | return (EACCESS); | |
290 | } else { | |
291 | if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) | |
292 | return (EACCESS); | |
293 | } | |
294 | fd = open(filename, mode == RRQ ? 0 : 1); | |
295 | if (fd < 0) | |
296 | return (errno + 100); | |
297 | file = fdopen(fd, (mode == RRQ)? "r":"w"); | |
298 | if (file == NULL) { | |
299 | return errno+100; | |
300 | } | |
301 | return (0); | |
302 | } | |
303 | ||
304 | int timeout; | |
305 | jmp_buf timeoutbuf; | |
306 | ||
307 | void | |
308 | timer() | |
309 | { | |
310 | ||
311 | timeout += rexmtval; | |
312 | if (timeout >= maxtimeout) | |
313 | exit(1); | |
314 | longjmp(timeoutbuf, 1); | |
315 | } | |
316 | ||
317 | /* | |
318 | * Send the requested file. | |
319 | */ | |
320 | sendfile(pf) | |
321 | struct formats *pf; | |
322 | { | |
323 | struct tftphdr *dp, *r_init(); | |
324 | register struct tftphdr *ap; /* ack packet */ | |
325 | register int block = 1, size, n; | |
326 | ||
327 | signal(SIGALRM, timer); | |
328 | dp = r_init(); | |
329 | ap = (struct tftphdr *)ackbuf; | |
330 | do { | |
331 | size = readit(file, &dp, pf->f_convert); | |
332 | if (size < 0) { | |
333 | nak(errno + 100); | |
334 | goto abort; | |
335 | } | |
336 | dp->th_opcode = htons((u_short)DATA); | |
337 | dp->th_block = htons((u_short)block); | |
338 | timeout = 0; | |
339 | (void) setjmp(timeoutbuf); | |
340 | ||
341 | send_data: | |
342 | if (send(peer, dp, size + 4, 0) != size + 4) { | |
343 | syslog(LOG_ERR, "tftpd: write: %m\n"); | |
344 | goto abort; | |
345 | } | |
346 | read_ahead(file, pf->f_convert); | |
347 | for ( ; ; ) { | |
348 | alarm(rexmtval); /* read the ack */ | |
349 | n = recv(peer, ackbuf, sizeof (ackbuf), 0); | |
350 | alarm(0); | |
351 | if (n < 0) { | |
352 | syslog(LOG_ERR, "tftpd: read: %m\n"); | |
353 | goto abort; | |
354 | } | |
355 | ap->th_opcode = ntohs((u_short)ap->th_opcode); | |
356 | ap->th_block = ntohs((u_short)ap->th_block); | |
357 | ||
358 | if (ap->th_opcode == ERROR) | |
359 | goto abort; | |
360 | ||
361 | if (ap->th_opcode == ACK) { | |
362 | if (ap->th_block == block) { | |
363 | break; | |
364 | } | |
365 | /* Re-synchronize with the other side */ | |
366 | (void) synchnet(peer); | |
367 | if (ap->th_block == (block -1)) { | |
368 | goto send_data; | |
369 | } | |
370 | } | |
371 | ||
372 | } | |
373 | block++; | |
374 | } while (size == SEGSIZE); | |
375 | abort: | |
376 | (void) fclose(file); | |
377 | } | |
378 | ||
379 | void | |
380 | justquit() | |
381 | { | |
382 | exit(0); | |
383 | } | |
384 | ||
385 | ||
386 | /* | |
387 | * Receive a file. | |
388 | */ | |
389 | recvfile(pf) | |
390 | struct formats *pf; | |
391 | { | |
392 | struct tftphdr *dp, *w_init(); | |
393 | register struct tftphdr *ap; /* ack buffer */ | |
394 | register int block = 0, n, size; | |
395 | ||
396 | signal(SIGALRM, timer); | |
397 | dp = w_init(); | |
398 | ap = (struct tftphdr *)ackbuf; | |
399 | do { | |
400 | timeout = 0; | |
401 | ap->th_opcode = htons((u_short)ACK); | |
402 | ap->th_block = htons((u_short)block); | |
403 | block++; | |
404 | (void) setjmp(timeoutbuf); | |
405 | send_ack: | |
406 | if (send(peer, ackbuf, 4, 0) != 4) { | |
407 | syslog(LOG_ERR, "tftpd: write: %m\n"); | |
408 | goto abort; | |
409 | } | |
410 | write_behind(file, pf->f_convert); | |
411 | for ( ; ; ) { | |
412 | alarm(rexmtval); | |
413 | n = recv(peer, dp, PKTSIZE, 0); | |
414 | alarm(0); | |
415 | if (n < 0) { /* really? */ | |
416 | syslog(LOG_ERR, "tftpd: read: %m\n"); | |
417 | goto abort; | |
418 | } | |
419 | dp->th_opcode = ntohs((u_short)dp->th_opcode); | |
420 | dp->th_block = ntohs((u_short)dp->th_block); | |
421 | if (dp->th_opcode == ERROR) | |
422 | goto abort; | |
423 | if (dp->th_opcode == DATA) { | |
424 | if (dp->th_block == block) { | |
425 | break; /* normal */ | |
426 | } | |
427 | /* Re-synchronize with the other side */ | |
428 | (void) synchnet(peer); | |
429 | if (dp->th_block == (block-1)) | |
430 | goto send_ack; /* rexmit */ | |
431 | } | |
432 | } | |
433 | /* size = write(file, dp->th_data, n - 4); */ | |
434 | size = writeit(file, &dp, n - 4, pf->f_convert); | |
435 | if (size != (n-4)) { /* ahem */ | |
436 | if (size < 0) nak(errno + 100); | |
437 | else nak(ENOSPACE); | |
438 | goto abort; | |
439 | } | |
440 | } while (size == SEGSIZE); | |
441 | write_behind(file, pf->f_convert); | |
442 | (void) fclose(file); /* close data file */ | |
443 | ||
444 | ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ | |
445 | ap->th_block = htons((u_short)(block)); | |
446 | (void) send(peer, ackbuf, 4, 0); | |
447 | ||
448 | signal(SIGALRM, justquit); /* just quit on timeout */ | |
449 | alarm(rexmtval); | |
450 | n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ | |
451 | alarm(0); | |
452 | if (n >= 4 && /* if read some data */ | |
453 | dp->th_opcode == DATA && /* and got a data block */ | |
454 | block == dp->th_block) { /* then my last ack was lost */ | |
455 | (void) send(peer, ackbuf, 4, 0); /* resend final ack */ | |
456 | } | |
457 | abort: | |
458 | return; | |
459 | } | |
460 | ||
461 | struct errmsg { | |
462 | int e_code; | |
463 | char *e_msg; | |
464 | } errmsgs[] = { | |
465 | { EUNDEF, "Undefined error code" }, | |
466 | { ENOTFOUND, "File not found" }, | |
467 | { EACCESS, "Access violation" }, | |
468 | { ENOSPACE, "Disk full or allocation exceeded" }, | |
469 | { EBADOP, "Illegal TFTP operation" }, | |
470 | { EBADID, "Unknown transfer ID" }, | |
471 | { EEXISTS, "File already exists" }, | |
472 | { ENOUSER, "No such user" }, | |
473 | { -1, 0 } | |
474 | }; | |
475 | ||
476 | /* | |
477 | * Send a nak packet (error message). | |
478 | * Error code passed in is one of the | |
479 | * standard TFTP codes, or a UNIX errno | |
480 | * offset by 100. | |
481 | */ | |
482 | nak(error) | |
483 | int error; | |
484 | { | |
485 | register struct tftphdr *tp; | |
486 | int length; | |
487 | register struct errmsg *pe; | |
488 | ||
489 | tp = (struct tftphdr *)buf; | |
490 | tp->th_opcode = htons((u_short)ERROR); | |
491 | tp->th_code = htons((u_short)error); | |
492 | for (pe = errmsgs; pe->e_code >= 0; pe++) | |
493 | if (pe->e_code == error) | |
494 | break; | |
495 | if (pe->e_code < 0) { | |
496 | pe->e_msg = strerror(error - 100); | |
497 | tp->th_code = EUNDEF; /* set 'undef' errorcode */ | |
498 | } | |
499 | strcpy(tp->th_msg, pe->e_msg); | |
500 | length = strlen(pe->e_msg); | |
501 | tp->th_msg[length] = '\0'; | |
502 | length += 5; | |
503 | if (send(peer, buf, length, 0) != length) | |
504 | syslog(LOG_ERR, "nak: %m\n"); | |
505 | } |