macro and text revision (-mdoc version 3)
[unix-history] / usr / src / libexec / tftpd / tftpd.c
CommitLineData
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
9char 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
fc0ce9e9 15static char sccsid[] = "@(#)tftpd.c 5.13 (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/ioctl.h>
ce4fd43b 26#include <sys/stat.h>
fc0ce9e9
KB
27#include <signal.h>
28#include <fcntl.h>
de3b21e8 29
fc0ce9e9 30#include <sys/socket.h>
de3b21e8 31#include <netinet/in.h>
533343f1 32#include <arpa/tftp.h>
396aa79f 33#include <netdb.h>
fc0ce9e9 34
396aa79f 35#include <setjmp.h>
fc0ce9e9 36#include <syslog.h>
f8675e8e 37#include <stdio.h>
f8675e8e
SL
38#include <errno.h>
39#include <ctype.h>
396aa79f 40#include <string.h>
fc0ce9e9 41#include <stdlib.h>
103499bc 42
103499bc 43#define TIMEOUT 5
de3b21e8 44
f8675e8e 45extern int errno;
4d5e33bd 46struct sockaddr_in sin = { AF_INET };
bb933cc2 47int peer;
103499bc
SL
48int rexmtval = TIMEOUT;
49int maxtimeout = 5*TIMEOUT;
7a859218
GM
50
51#define PKTSIZE SEGSIZE+4
52char buf[PKTSIZE];
53char ackbuf[PKTSIZE];
bb933cc2
MK
54struct sockaddr_in from;
55int fromlen;
f8675e8e 56
4d24819a
TF
57#define MAXARG 4
58char *dirs[MAXARG+1];
59
60main(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,
fc0ce9e9 77 (struct sockaddr *)&from, &fromlen);
bb933cc2 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,
fc0ce9e9 116 (struct sockaddr *)&from, &j);
854c42ad
GM
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 }
fc0ce9e9 141 if (bind(peer, (struct sockaddr *)&sin, sizeof (sin)) < 0) {
854c42ad 142 syslog(LOG_ERR, "bind: %m\n");
bb933cc2 143 exit(1);
f8675e8e 144 }
fc0ce9e9 145 if (connect(peer, (struct sockaddr *)&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
156int validate_access();
157int sendfile(), recvfile();
158
159struct 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 177tftp(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;
187again:
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
225FILE *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
238validate_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
278int timeout;
279jmp_buf timeoutbuf;
f8675e8e 280
fc0ce9e9 281void
f8675e8e
SL
282timer()
283{
103499bc
SL
284
285 timeout += rexmtval;
286 if (timeout >= maxtimeout)
f8675e8e 287 exit(1);
103499bc 288 longjmp(timeoutbuf, 1);
f8675e8e
SL
289}
290
291/*
292 * Send the requested file.
293 */
294sendfile(pf)
7a859218 295 struct formats *pf;
f8675e8e 296{
7a859218
GM
297 struct tftphdr *dp, *r_init();
298 register struct tftphdr *ap; /* ack packet */
f8675e8e
SL
299 register int block = 1, size, n;
300
103499bc 301 signal(SIGALRM, timer);
7a859218
GM
302 dp = r_init();
303 ap = (struct tftphdr *)ackbuf;
f8675e8e 304 do {
7a859218 305 size = readit(file, &dp, pf->f_convert);
f8675e8e
SL
306 if (size < 0) {
307 nak(errno + 100);
7a859218 308 goto abort;
f8675e8e 309 }
7a859218
GM
310 dp->th_opcode = htons((u_short)DATA);
311 dp->th_block = htons((u_short)block);
f8675e8e 312 timeout = 0;
103499bc 313 (void) setjmp(timeoutbuf);
7a859218 314
f9cd9486 315send_data:
7a859218 316 if (send(peer, dp, size + 4, 0) != size + 4) {
854c42ad 317 syslog(LOG_ERR, "tftpd: write: %m\n");
7a859218 318 goto abort;
f8675e8e 319 }
7a859218 320 read_ahead(file, pf->f_convert);
f9cd9486 321 for ( ; ; ) {
7a859218
GM
322 alarm(rexmtval); /* read the ack */
323 n = recv(peer, ackbuf, sizeof (ackbuf), 0);
f8675e8e 324 alarm(0);
103499bc 325 if (n < 0) {
854c42ad 326 syslog(LOG_ERR, "tftpd: read: %m\n");
7a859218 327 goto abort;
103499bc 328 }
7a859218
GM
329 ap->th_opcode = ntohs((u_short)ap->th_opcode);
330 ap->th_block = ntohs((u_short)ap->th_block);
331
332 if (ap->th_opcode == ERROR)
333 goto abort;
f9cd9486
GM
334
335 if (ap->th_opcode == ACK) {
336 if (ap->th_block == block) {
337 break;
338 }
81243fce
GM
339 /* Re-synchronize with the other side */
340 (void) synchnet(peer);
f9cd9486
GM
341 if (ap->th_block == (block -1)) {
342 goto send_data;
343 }
344 }
7a859218 345
f9cd9486 346 }
f8675e8e
SL
347 block++;
348 } while (size == SEGSIZE);
7a859218
GM
349abort:
350 (void) fclose(file);
f8675e8e
SL
351}
352
fc0ce9e9 353void
7a859218
GM
354justquit()
355{
356 exit(0);
357}
358
359
f8675e8e
SL
360/*
361 * Receive a file.
362 */
363recvfile(pf)
7a859218 364 struct formats *pf;
f8675e8e 365{
7a859218
GM
366 struct tftphdr *dp, *w_init();
367 register struct tftphdr *ap; /* ack buffer */
f8675e8e
SL
368 register int block = 0, n, size;
369
103499bc 370 signal(SIGALRM, timer);
7a859218
GM
371 dp = w_init();
372 ap = (struct tftphdr *)ackbuf;
f8675e8e
SL
373 do {
374 timeout = 0;
7a859218
GM
375 ap->th_opcode = htons((u_short)ACK);
376 ap->th_block = htons((u_short)block);
f8675e8e 377 block++;
103499bc 378 (void) setjmp(timeoutbuf);
7a859218
GM
379send_ack:
380 if (send(peer, ackbuf, 4, 0) != 4) {
854c42ad 381 syslog(LOG_ERR, "tftpd: write: %m\n");
103499bc 382 goto abort;
f8675e8e 383 }
7a859218
GM
384 write_behind(file, pf->f_convert);
385 for ( ; ; ) {
103499bc 386 alarm(rexmtval);
7a859218 387 n = recv(peer, dp, PKTSIZE, 0);
f8675e8e 388 alarm(0);
7a859218 389 if (n < 0) { /* really? */
854c42ad 390 syslog(LOG_ERR, "tftpd: read: %m\n");
103499bc
SL
391 goto abort;
392 }
7a859218
GM
393 dp->th_opcode = ntohs((u_short)dp->th_opcode);
394 dp->th_block = ntohs((u_short)dp->th_block);
395 if (dp->th_opcode == ERROR)
103499bc 396 goto abort;
7a859218
GM
397 if (dp->th_opcode == DATA) {
398 if (dp->th_block == block) {
399 break; /* normal */
400 }
81243fce
GM
401 /* Re-synchronize with the other side */
402 (void) synchnet(peer);
7a859218
GM
403 if (dp->th_block == (block-1))
404 goto send_ack; /* rexmit */
405 }
406 }
407 /* size = write(file, dp->th_data, n - 4); */
408 size = writeit(file, &dp, n - 4, pf->f_convert);
409 if (size != (n-4)) { /* ahem */
410 if (size < 0) nak(errno + 100);
411 else nak(ENOSPACE);
103499bc 412 goto abort;
f8675e8e
SL
413 }
414 } while (size == SEGSIZE);
7a859218
GM
415 write_behind(file, pf->f_convert);
416 (void) fclose(file); /* close data file */
417
418 ap->th_opcode = htons((u_short)ACK); /* send the "final" ack */
419 ap->th_block = htons((u_short)(block));
420 (void) send(peer, ackbuf, 4, 0);
421
422 signal(SIGALRM, justquit); /* just quit on timeout */
423 alarm(rexmtval);
424 n = recv(peer, buf, sizeof (buf), 0); /* normally times out and quits */
425 alarm(0);
426 if (n >= 4 && /* if read some data */
427 dp->th_opcode == DATA && /* and got a data block */
428 block == dp->th_block) { /* then my last ack was lost */
429 (void) send(peer, ackbuf, 4, 0); /* resend final ack */
430 }
103499bc 431abort:
7a859218 432 return;
f8675e8e
SL
433}
434
435struct errmsg {
436 int e_code;
437 char *e_msg;
438} errmsgs[] = {
439 { EUNDEF, "Undefined error code" },
440 { ENOTFOUND, "File not found" },
441 { EACCESS, "Access violation" },
442 { ENOSPACE, "Disk full or allocation exceeded" },
443 { EBADOP, "Illegal TFTP operation" },
444 { EBADID, "Unknown transfer ID" },
445 { EEXISTS, "File already exists" },
446 { ENOUSER, "No such user" },
447 { -1, 0 }
448};
449
450/*
451 * Send a nak packet (error message).
452 * Error code passed in is one of the
453 * standard TFTP codes, or a UNIX errno
454 * offset by 100.
455 */
456nak(error)
457 int error;
458{
459 register struct tftphdr *tp;
460 int length;
461 register struct errmsg *pe;
f8675e8e
SL
462
463 tp = (struct tftphdr *)buf;
464 tp->th_opcode = htons((u_short)ERROR);
465 tp->th_code = htons((u_short)error);
466 for (pe = errmsgs; pe->e_code >= 0; pe++)
467 if (pe->e_code == error)
468 break;
7a859218 469 if (pe->e_code < 0) {
396aa79f 470 pe->e_msg = strerror(error - 100);
7a859218
GM
471 tp->th_code = EUNDEF; /* set 'undef' errorcode */
472 }
f8675e8e
SL
473 strcpy(tp->th_msg, pe->e_msg);
474 length = strlen(pe->e_msg);
475 tp->th_msg[length] = '\0';
476 length += 5;
bb933cc2 477 if (send(peer, buf, length, 0) != length)
854c42ad 478 syslog(LOG_ERR, "nak: %m\n");
f8675e8e 479}