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