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