xref: /csrg-svn/libexec/tftpd/tftpd.c (revision 13030)
1 /*	tftpd.c	4.10	83/06/12	*/
2 
3 /*
4  * Trivial file transfer protocol server.
5  */
6 #include <sys/types.h>
7 #include <sys/socket.h>
8 #include <sys/ioctl.h>
9 
10 #include <netinet/in.h>
11 
12 #include <arpa/tftp.h>
13 
14 #include <signal.h>
15 #include <stat.h>
16 #include <stdio.h>
17 #include <wait.h>
18 #include <errno.h>
19 #include <ctype.h>
20 #include <netdb.h>
21 #include <setjmp.h>
22 
23 #define	TIMEOUT		5
24 
25 extern	int errno;
26 struct	sockaddr_in sin = { AF_INET };
27 int	f;
28 int	rexmtval = TIMEOUT;
29 int	maxtimeout = 5*TIMEOUT;
30 char	buf[BUFSIZ];
31 int	reapchild();
32 
33 main(argc, argv)
34 	char *argv[];
35 {
36 	struct sockaddr_in from;
37 	register struct tftphdr *tp;
38 	register int n;
39 	struct servent *sp;
40 
41 	sp = getservbyname("tftp", "udp");
42 	if (sp == 0) {
43 		fprintf(stderr, "tftpd: udp/tftp: unknown service\n");
44 		exit(1);
45 	}
46 	sin.sin_port = sp->s_port;
47 #ifndef DEBUG
48 	if (fork())
49 		exit(0);
50 	for (f = 0; f < 10; f++)
51 		(void) close(f);
52 	(void) open("/", 0);
53 	(void) dup2(0, 1);
54 	(void) dup2(0, 2);
55 	{ int t = open("/dev/tty", 2);
56 	  if (t >= 0) {
57 		ioctl(t, TIOCNOTTY, (char *)0);
58 		(void) close(t);
59 	  }
60 	}
61 #endif
62 	signal(SIGCHLD, reapchild);
63 	for (;;) {
64 		int fromlen;
65 
66 		f = socket(AF_INET, SOCK_DGRAM, 0);
67 		if (f < 0) {
68 			perror("tftpd: socket");
69 			sleep(5);
70 			continue;
71 		}
72 		if (setsockopt(f, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0)
73 			perror("tftpd: setsockopt (SO_REUSEADDR)");
74 		sleep(1);			/* let child do connect */
75 		while (bind(f, (caddr_t)&sin, sizeof (sin), 0) < 0) {
76 			perror("tftpd: bind");
77 			sleep(5);
78 		}
79 		do {
80 			fromlen = sizeof (from);
81 			n = recvfrom(f, buf, sizeof (buf), 0,
82 			    (caddr_t)&from, &fromlen);
83 		} while (n <= 0);
84 		tp = (struct tftphdr *)buf;
85 		tp->th_opcode = ntohs(tp->th_opcode);
86 		if (tp->th_opcode == RRQ || tp->th_opcode == WRQ)
87 			if (fork() == 0)
88 				tftp(&from, tp, n);
89 		(void) close(f);
90 	}
91 }
92 
93 reapchild()
94 {
95 	union wait status;
96 
97 	while (wait3(&status, WNOHANG, 0) > 0)
98 		;
99 }
100 
101 int	validate_access();
102 int	sendfile(), recvfile();
103 
104 struct formats {
105 	char	*f_mode;
106 	int	(*f_validate)();
107 	int	(*f_send)();
108 	int	(*f_recv)();
109 } formats[] = {
110 	{ "netascii",	validate_access,	sendfile,	recvfile },
111 	{ "octet",	validate_access,	sendfile,	recvfile },
112 #ifdef notdef
113 	{ "mail",	validate_user,		sendmail,	recvmail },
114 #endif
115 	{ 0 }
116 };
117 
118 int	fd;			/* file being transferred */
119 
120 /*
121  * Handle initial connection protocol.
122  */
123 tftp(client, tp, size)
124 	struct sockaddr_in *client;
125 	struct tftphdr *tp;
126 	int size;
127 {
128 	register char *cp;
129 	int first = 1, ecode;
130 	register struct formats *pf;
131 	char *filename, *mode;
132 
133 	if (connect(f, (caddr_t)client, sizeof (*client), 0) < 0) {
134 		perror("connect");
135 		exit(1);
136 	}
137 	filename = cp = tp->th_stuff;
138 again:
139 	while (cp < buf + size) {
140 		if (*cp == '\0')
141 			break;
142 		cp++;
143 	}
144 	if (*cp != '\0') {
145 		nak(EBADOP);
146 		exit(1);
147 	}
148 	if (first) {
149 		mode = ++cp;
150 		first = 0;
151 		goto again;
152 	}
153 	for (cp = mode; *cp; cp++)
154 		if (isupper(*cp))
155 			*cp = tolower(*cp);
156 	for (pf = formats; pf->f_mode; pf++)
157 		if (strcmp(pf->f_mode, mode) == 0)
158 			break;
159 	if (pf->f_mode == 0) {
160 		nak(EBADOP);
161 		exit(1);
162 	}
163 	ecode = (*pf->f_validate)(filename, client, tp->th_opcode);
164 	if (ecode) {
165 		nak(ecode);
166 		exit(1);
167 	}
168 	if (tp->th_opcode == WRQ)
169 		(*pf->f_recv)(pf);
170 	else
171 		(*pf->f_send)(pf);
172 	exit(0);
173 }
174 
175 /*
176  * Validate file access.  Since we
177  * have no uid or gid, for now require
178  * file to exist and be publicly
179  * readable/writable.
180  * Note also, full path name must be
181  * given as we have no login directory.
182  */
183 validate_access(file, client, mode)
184 	char *file;
185 	struct sockaddr_in *client;
186 	int mode;
187 {
188 	struct stat stbuf;
189 
190 	if (*file != '/')
191 		return (EACCESS);
192 	if (stat(file, &stbuf) < 0)
193 		return (errno == ENOENT ? ENOTFOUND : EACCESS);
194 	if (mode == RRQ) {
195 		if ((stbuf.st_mode&(S_IREAD >> 6)) == 0)
196 			return (EACCESS);
197 	} else {
198 		if ((stbuf.st_mode&(S_IWRITE >> 6)) == 0)
199 			return (EACCESS);
200 	}
201 	fd = open(file, mode == RRQ ? 0 : 1);
202 	if (fd < 0)
203 		return (errno + 100);
204 	return (0);
205 }
206 
207 int	timeout;
208 jmp_buf	timeoutbuf;
209 
210 timer()
211 {
212 
213 	timeout += rexmtval;
214 	if (timeout >= maxtimeout)
215 		exit(1);
216 	longjmp(timeoutbuf, 1);
217 }
218 
219 /*
220  * Send the requested file.
221  */
222 sendfile(pf)
223 	struct format *pf;
224 {
225 	register struct tftphdr *tp;
226 	register int block = 1, size, n;
227 
228 	signal(SIGALRM, timer);
229 	tp = (struct tftphdr *)buf;
230 	do {
231 		size = read(fd, tp->th_data, SEGSIZE);
232 		if (size < 0) {
233 			nak(errno + 100);
234 			goto abort;
235 		}
236 		tp->th_opcode = htons((u_short)DATA);
237 		tp->th_block = htons((u_short)block);
238 		timeout = 0;
239 		(void) setjmp(timeoutbuf);
240 		if (write(f, buf, size + 4) != size + 4) {
241 			perror("tftpd: write");
242 			goto abort;
243 		}
244 		do {
245 			alarm(rexmtval);
246 			n = read(f, buf, sizeof (buf));
247 			alarm(0);
248 			if (n < 0) {
249 				perror("tftpd: read");
250 				goto abort;
251 			}
252 			tp->th_opcode = ntohs((u_short)tp->th_opcode);
253 			tp->th_block = ntohs((u_short)tp->th_block);
254 			if (tp->th_opcode == ERROR)
255 				goto abort;
256 		} while (tp->th_opcode != ACK || tp->th_block != block);
257 		block++;
258 	} while (size == SEGSIZE);
259 abort:
260 	(void) close(fd);
261 }
262 
263 /*
264  * Receive a file.
265  */
266 recvfile(pf)
267 	struct format *pf;
268 {
269 	register struct tftphdr *tp;
270 	register int block = 0, n, size;
271 
272 	signal(SIGALRM, timer);
273 	tp = (struct tftphdr *)buf;
274 	do {
275 		timeout = 0;
276 		tp->th_opcode = htons((u_short)ACK);
277 		tp->th_block = htons((u_short)block);
278 		block++;
279 		(void) setjmp(timeoutbuf);
280 		if (write(f, buf, 4) != 4) {
281 			perror("tftpd: write");
282 			goto abort;
283 		}
284 		do {
285 			alarm(rexmtval);
286 			n = read(f, buf, sizeof (buf));
287 			alarm(0);
288 			if (n < 0) {
289 				perror("tftpd: read");
290 				goto abort;
291 			}
292 			tp->th_opcode = ntohs((u_short)tp->th_opcode);
293 			tp->th_block = ntohs((u_short)tp->th_block);
294 			if (tp->th_opcode == ERROR)
295 				goto abort;
296 		} while (tp->th_opcode != DATA || block != tp->th_block);
297 		size = write(fd, tp->th_data, n - 4);
298 		if (size < 0) {
299 			nak(errno + 100);
300 			goto abort;
301 		}
302 	} while (size == SEGSIZE);
303 abort:
304 	tp->th_opcode = htons((u_short)ACK);
305 	tp->th_block = htons((u_short)(block));
306 	(void) write(f, buf, 4);
307 	(void) close(fd);
308 }
309 
310 struct errmsg {
311 	int	e_code;
312 	char	*e_msg;
313 } errmsgs[] = {
314 	{ EUNDEF,	"Undefined error code" },
315 	{ ENOTFOUND,	"File not found" },
316 	{ EACCESS,	"Access violation" },
317 	{ ENOSPACE,	"Disk full or allocation exceeded" },
318 	{ EBADOP,	"Illegal TFTP operation" },
319 	{ EBADID,	"Unknown transfer ID" },
320 	{ EEXISTS,	"File already exists" },
321 	{ ENOUSER,	"No such user" },
322 	{ -1,		0 }
323 };
324 
325 /*
326  * Send a nak packet (error message).
327  * Error code passed in is one of the
328  * standard TFTP codes, or a UNIX errno
329  * offset by 100.
330  */
331 nak(error)
332 	int error;
333 {
334 	register struct tftphdr *tp;
335 	int length;
336 	register struct errmsg *pe;
337 	extern char *sys_errlist[];
338 
339 	tp = (struct tftphdr *)buf;
340 	tp->th_opcode = htons((u_short)ERROR);
341 	tp->th_code = htons((u_short)error);
342 	for (pe = errmsgs; pe->e_code >= 0; pe++)
343 		if (pe->e_code == error)
344 			break;
345 	if (pe->e_code < 0)
346 		pe->e_msg = sys_errlist[error - 100];
347 	strcpy(tp->th_msg, pe->e_msg);
348 	length = strlen(pe->e_msg);
349 	tp->th_msg[length] = '\0';
350 	length += 5;
351 	if (write(f, buf, length) != length)
352 		perror("nak");
353 }
354