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 |
f9cd9486 | 14 | static char sccsid[] = "@(#)tftpd.c 5.4 (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; | |
60 | ||
076ae92c | 61 | openlog("tftpd", LOG_PID, LOG_DAEMON); |
bb933cc2 MK |
62 | alarm(10); |
63 | fromlen = sizeof (from); | |
64 | n = recvfrom(0, buf, sizeof (buf), 0, | |
65 | (caddr_t)&from, &fromlen); | |
66 | if (n < 0) { | |
67 | perror("tftpd: recvfrom"); | |
4d5e33bd SL |
68 | exit(1); |
69 | } | |
bb933cc2 MK |
70 | from.sin_family = AF_INET; |
71 | alarm(0); | |
bb933cc2 MK |
72 | close(0); |
73 | close(1); | |
74 | peer = socket(AF_INET, SOCK_DGRAM, 0); | |
75 | if (peer < 0) { | |
3f99c0f7 | 76 | syslog(LOG_ERR, "socket: %m"); |
bb933cc2 MK |
77 | exit(1); |
78 | } | |
79 | if (bind(peer, (caddr_t)&sin, sizeof (sin)) < 0) { | |
3f99c0f7 | 80 | syslog(LOG_ERR, "bind: %m"); |
bb933cc2 | 81 | exit(1); |
f8675e8e | 82 | } |
bb933cc2 | 83 | if (connect(peer, (caddr_t)&from, sizeof(from)) < 0) { |
3f99c0f7 | 84 | syslog(LOG_ERR, "connect: %m"); |
bb933cc2 | 85 | exit(1); |
f8675e8e | 86 | } |
bb933cc2 MK |
87 | tp = (struct tftphdr *)buf; |
88 | tp->th_opcode = ntohs(tp->th_opcode); | |
89 | if (tp->th_opcode == RRQ || tp->th_opcode == WRQ) | |
90 | tftp(tp, n); | |
91 | exit(1); | |
103499bc SL |
92 | } |
93 | ||
f8675e8e SL |
94 | int validate_access(); |
95 | int sendfile(), recvfile(); | |
96 | ||
97 | struct formats { | |
98 | char *f_mode; | |
99 | int (*f_validate)(); | |
100 | int (*f_send)(); | |
101 | int (*f_recv)(); | |
7a859218 | 102 | int f_convert; |
f8675e8e | 103 | } formats[] = { |
7a859218 GM |
104 | { "netascii", validate_access, sendfile, recvfile, 1 }, |
105 | { "octet", validate_access, sendfile, recvfile, 0 }, | |
f8675e8e | 106 | #ifdef notdef |
7a859218 | 107 | { "mail", validate_user, sendmail, recvmail, 1 }, |
f8675e8e SL |
108 | #endif |
109 | { 0 } | |
110 | }; | |
111 | ||
f8675e8e SL |
112 | /* |
113 | * Handle initial connection protocol. | |
114 | */ | |
bb933cc2 | 115 | tftp(tp, size) |
f8675e8e SL |
116 | struct tftphdr *tp; |
117 | int size; | |
118 | { | |
119 | register char *cp; | |
120 | int first = 1, ecode; | |
121 | register struct formats *pf; | |
122 | char *filename, *mode; | |
123 | ||
f8675e8e SL |
124 | filename = cp = tp->th_stuff; |
125 | again: | |
126 | while (cp < buf + size) { | |
127 | if (*cp == '\0') | |
128 | break; | |
129 | cp++; | |
130 | } | |
131 | if (*cp != '\0') { | |
132 | nak(EBADOP); | |
133 | exit(1); | |
134 | } | |
135 | if (first) { | |
136 | mode = ++cp; | |
137 | first = 0; | |
138 | goto again; | |
139 | } | |
140 | for (cp = mode; *cp; cp++) | |
141 | if (isupper(*cp)) | |
142 | *cp = tolower(*cp); | |
143 | for (pf = formats; pf->f_mode; pf++) | |
144 | if (strcmp(pf->f_mode, mode) == 0) | |
145 | break; | |
146 | if (pf->f_mode == 0) { | |
147 | nak(EBADOP); | |
148 | exit(1); | |
149 | } | |
bb933cc2 | 150 | ecode = (*pf->f_validate)(filename, tp->th_opcode); |
f8675e8e SL |
151 | if (ecode) { |
152 | nak(ecode); | |
153 | exit(1); | |
154 | } | |
155 | if (tp->th_opcode == WRQ) | |
156 | (*pf->f_recv)(pf); | |
157 | else | |
158 | (*pf->f_send)(pf); | |
159 | exit(0); | |
160 | } | |
161 | ||
7a859218 GM |
162 | |
163 | FILE *file; | |
bb933cc2 | 164 | |
f8675e8e SL |
165 | /* |
166 | * Validate file access. Since we | |
167 | * have no uid or gid, for now require | |
168 | * file to exist and be publicly | |
169 | * readable/writable. | |
170 | * Note also, full path name must be | |
171 | * given as we have no login directory. | |
172 | */ | |
7a859218 GM |
173 | validate_access(filename, mode) |
174 | char *filename; | |
f8675e8e SL |
175 | int mode; |
176 | { | |
177 | struct stat stbuf; | |
7a859218 | 178 | int fd; |
f8675e8e | 179 | |
7a859218 | 180 | if (*filename != '/') |
f8675e8e | 181 | return (EACCESS); |
7a859218 | 182 | if (stat(filename, &stbuf) < 0) |
f8675e8e SL |
183 | return (errno == ENOENT ? ENOTFOUND : EACCESS); |
184 | if (mode == RRQ) { | |
185 | if ((stbuf.st_mode&(S_IREAD >> 6)) == 0) | |
186 | return (EACCESS); | |
187 | } else { | |
188 | if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0) | |
189 | return (EACCESS); | |
190 | } | |
7a859218 | 191 | fd = open(filename, mode == RRQ ? 0 : 1); |
f8675e8e SL |
192 | if (fd < 0) |
193 | return (errno + 100); | |
7a859218 GM |
194 | file = fdopen(fd, (mode == RRQ)? "r":"w"); |
195 | if (file == NULL) { | |
196 | return errno+100; | |
197 | } | |
f8675e8e SL |
198 | return (0); |
199 | } | |
200 | ||
103499bc SL |
201 | int timeout; |
202 | jmp_buf timeoutbuf; | |
f8675e8e SL |
203 | |
204 | timer() | |
205 | { | |
103499bc SL |
206 | |
207 | timeout += rexmtval; | |
208 | if (timeout >= maxtimeout) | |
f8675e8e | 209 | exit(1); |
103499bc | 210 | longjmp(timeoutbuf, 1); |
f8675e8e SL |
211 | } |
212 | ||
213 | /* | |
214 | * Send the requested file. | |
215 | */ | |
216 | sendfile(pf) | |
7a859218 | 217 | struct formats *pf; |
f8675e8e | 218 | { |
7a859218 GM |
219 | struct tftphdr *dp, *r_init(); |
220 | register struct tftphdr *ap; /* ack packet */ | |
f8675e8e SL |
221 | register int block = 1, size, n; |
222 | ||
103499bc | 223 | signal(SIGALRM, timer); |
7a859218 GM |
224 | dp = r_init(); |
225 | ap = (struct tftphdr *)ackbuf; | |
f8675e8e | 226 | do { |
7a859218 | 227 | size = readit(file, &dp, pf->f_convert); |
f8675e8e SL |
228 | if (size < 0) { |
229 | nak(errno + 100); | |
7a859218 | 230 | goto abort; |
f8675e8e | 231 | } |
7a859218 GM |
232 | dp->th_opcode = htons((u_short)DATA); |
233 | dp->th_block = htons((u_short)block); | |
f8675e8e | 234 | timeout = 0; |
103499bc | 235 | (void) setjmp(timeoutbuf); |
7a859218 | 236 | |
f9cd9486 GM |
237 | send_data: |
238 | /* Now, we flush anything pending to be read */ | |
239 | /* This is to try to keep in synch between the two sides */ | |
240 | while (1) { | |
241 | int i; | |
242 | char rbuf[PKTSIZE]; | |
243 | ||
244 | (void) ioctl(peer, FIONREAD, &i); | |
245 | if (i) { | |
246 | fromlen = sizeof from; | |
247 | n = recvfrom(peer, rbuf, sizeof (rbuf), 0, | |
248 | (caddr_t)&from, &fromlen); | |
249 | } else { | |
250 | break; | |
251 | } | |
252 | } | |
7a859218 GM |
253 | if (send(peer, dp, size + 4, 0) != size + 4) { |
254 | perror("tftpd: write"); | |
255 | goto abort; | |
f8675e8e | 256 | } |
7a859218 | 257 | read_ahead(file, pf->f_convert); |
f9cd9486 | 258 | for ( ; ; ) { |
7a859218 GM |
259 | alarm(rexmtval); /* read the ack */ |
260 | n = recv(peer, ackbuf, sizeof (ackbuf), 0); | |
f8675e8e | 261 | alarm(0); |
103499bc | 262 | if (n < 0) { |
7a859218 GM |
263 | perror("tftpd: read"); |
264 | goto abort; | |
103499bc | 265 | } |
7a859218 GM |
266 | ap->th_opcode = ntohs((u_short)ap->th_opcode); |
267 | ap->th_block = ntohs((u_short)ap->th_block); | |
268 | ||
269 | if (ap->th_opcode == ERROR) | |
270 | goto abort; | |
f9cd9486 GM |
271 | |
272 | if (ap->th_opcode == ACK) { | |
273 | if (ap->th_block == block) { | |
274 | break; | |
275 | } | |
276 | if (ap->th_block == (block -1)) { | |
277 | goto send_data; | |
278 | } | |
279 | } | |
7a859218 | 280 | |
f9cd9486 | 281 | } |
f8675e8e SL |
282 | block++; |
283 | } while (size == SEGSIZE); | |
7a859218 GM |
284 | abort: |
285 | (void) fclose(file); | |
f8675e8e SL |
286 | } |
287 | ||
7a859218 GM |
288 | justquit() |
289 | { | |
290 | exit(0); | |
291 | } | |
292 | ||
293 | ||
f8675e8e SL |
294 | /* |
295 | * Receive a file. | |
296 | */ | |
297 | recvfile(pf) | |
7a859218 | 298 | struct formats *pf; |
f8675e8e | 299 | { |
7a859218 GM |
300 | struct tftphdr *dp, *w_init(); |
301 | register struct tftphdr *ap; /* ack buffer */ | |
f8675e8e SL |
302 | register int block = 0, n, size; |
303 | ||
103499bc | 304 | signal(SIGALRM, timer); |
7a859218 GM |
305 | dp = w_init(); |
306 | ap = (struct tftphdr *)ackbuf; | |
f8675e8e SL |
307 | do { |
308 | timeout = 0; | |
7a859218 GM |
309 | ap->th_opcode = htons((u_short)ACK); |
310 | ap->th_block = htons((u_short)block); | |
f8675e8e | 311 | block++; |
103499bc | 312 | (void) setjmp(timeoutbuf); |
7a859218 | 313 | send_ack: |
f9cd9486 GM |
314 | /* Now, we flush anything pending to be read */ |
315 | /* This is to try to keep in synch between the two sides */ | |
316 | while (1) { | |
317 | int i; | |
318 | char rbuf[PKTSIZE]; | |
319 | ||
320 | (void) ioctl(peer, FIONREAD, &i); | |
321 | if (i) { | |
322 | fromlen = sizeof from; | |
323 | n = recvfrom(peer, rbuf, sizeof (rbuf), 0, | |
324 | (caddr_t)&from, &fromlen); | |
325 | } else { | |
326 | break; | |
327 | } | |
328 | } | |
7a859218 GM |
329 | if (send(peer, ackbuf, 4, 0) != 4) { |
330 | perror("tftpd: write"); | |
103499bc | 331 | goto abort; |
f8675e8e | 332 | } |
7a859218 GM |
333 | write_behind(file, pf->f_convert); |
334 | for ( ; ; ) { | |
103499bc | 335 | alarm(rexmtval); |
7a859218 | 336 | n = recv(peer, dp, PKTSIZE, 0); |
f8675e8e | 337 | alarm(0); |
7a859218 GM |
338 | if (n < 0) { /* really? */ |
339 | perror("tftpd: read"); | |
103499bc SL |
340 | goto abort; |
341 | } | |
7a859218 GM |
342 | dp->th_opcode = ntohs((u_short)dp->th_opcode); |
343 | dp->th_block = ntohs((u_short)dp->th_block); | |
344 | if (dp->th_opcode == ERROR) | |
103499bc | 345 | goto abort; |
7a859218 GM |
346 | if (dp->th_opcode == DATA) { |
347 | if (dp->th_block == block) { | |
348 | break; /* normal */ | |
349 | } | |
350 | if (dp->th_block == (block-1)) | |
351 | goto send_ack; /* rexmit */ | |
352 | } | |
353 | } | |
354 | /* size = write(file, dp->th_data, n - 4); */ | |
355 | size = writeit(file, &dp, n - 4, pf->f_convert); | |
356 | if (size != (n-4)) { /* ahem */ | |
357 | if (size < 0) nak(errno + 100); | |
358 | else nak(ENOSPACE); | |
103499bc | 359 | goto abort; |
f8675e8e SL |
360 | } |
361 | } while (size == SEGSIZE); | |
7a859218 GM |
362 | write_behind(file, pf->f_convert); |
363 | (void) fclose(file); /* close data file */ | |
364 | ||
365 | ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */ | |
366 | ap->th_block = htons((u_short)(block)); | |
367 | (void) send(peer, ackbuf, 4, 0); | |
368 | ||
369 | signal(SIGALRM, justquit); /* just quit on timeout */ | |
370 | alarm(rexmtval); | |
371 | n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */ | |
372 | alarm(0); | |
373 | if (n >= 4 && /* if read some data */ | |
374 | dp->th_opcode == DATA && /* and got a data block */ | |
375 | block == dp->th_block) { /* then my last ack was lost */ | |
376 | (void) send(peer, ackbuf, 4, 0); /* resend final ack */ | |
377 | } | |
103499bc | 378 | abort: |
7a859218 | 379 | return; |
f8675e8e SL |
380 | } |
381 | ||
382 | struct errmsg { | |
383 | int e_code; | |
384 | char *e_msg; | |
385 | } errmsgs[] = { | |
386 | { EUNDEF, "Undefined error code" }, | |
387 | { ENOTFOUND, "File not found" }, | |
388 | { EACCESS, "Access violation" }, | |
389 | { ENOSPACE, "Disk full or allocation exceeded" }, | |
390 | { EBADOP, "Illegal TFTP operation" }, | |
391 | { EBADID, "Unknown transfer ID" }, | |
392 | { EEXISTS, "File already exists" }, | |
393 | { ENOUSER, "No such user" }, | |
394 | { -1, 0 } | |
395 | }; | |
396 | ||
397 | /* | |
398 | * Send a nak packet (error message). | |
399 | * Error code passed in is one of the | |
400 | * standard TFTP codes, or a UNIX errno | |
401 | * offset by 100. | |
402 | */ | |
403 | nak(error) | |
404 | int error; | |
405 | { | |
406 | register struct tftphdr *tp; | |
407 | int length; | |
408 | register struct errmsg *pe; | |
409 | extern char *sys_errlist[]; | |
410 | ||
411 | tp = (struct tftphdr *)buf; | |
412 | tp->th_opcode = htons((u_short)ERROR); | |
413 | tp->th_code = htons((u_short)error); | |
414 | for (pe = errmsgs; pe->e_code >= 0; pe++) | |
415 | if (pe->e_code == error) | |
416 | break; | |
7a859218 | 417 | if (pe->e_code < 0) { |
f8675e8e | 418 | pe->e_msg = sys_errlist[error - 100]; |
7a859218 GM |
419 | tp->th_code = EUNDEF; /* set 'undef' errorcode */ |
420 | } | |
f8675e8e SL |
421 | strcpy(tp->th_msg, pe->e_msg); |
422 | length = strlen(pe->e_msg); | |
423 | tp->th_msg[length] = '\0'; | |
424 | length += 5; | |
bb933cc2 | 425 | if (send(peer, buf, length, 0) != length) |
f8675e8e SL |
426 | perror("nak"); |
427 | } |