getgroups takes a pointer to int's for now
[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
836fe169 15static 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 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,
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
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
SL
280
281timer()
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 */
293sendfile(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 314send_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
348abort:
349 (void) fclose(file);
f8675e8e
SL
350}
351
7a859218
GM
352justquit()
353{
354 exit(0);
355}
356
357
f8675e8e
SL
358/*
359 * Receive a file.
360 */
361recvfile(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
377send_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 429abort:
7a859218 430 return;
f8675e8e
SL
431}
432
433struct 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 */
454nak(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}