xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.bin/tftp/tftpsubs.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 /*
31  * University Copyright- Copyright (c) 1982, 1986, 1988
32  * The Regents of the University of California
33  * All Rights Reserved
34  *
35  * University Acknowledgment- Portions of this document are derived from
36  * software developed by the University of California, Berkeley, and its
37  * contributors.
38  */
39 
40 #pragma ident	"%Z%%M%	%I%	%E% SMI"
41 
42 /*
43  * Simple minded read-ahead/write-behind subroutines for tftp user and
44  * server.  Written originally with multiple buffers in mind, but current
45  * implementation has two buffer logic wired in.
46  *
47  * Todo:  add some sort of final error check so when the write-buffer
48  * is finally flushed, the caller can detect if the disk filled up
49  * (or had an i/o error) and return a nak to the other side.
50  */
51 
52 #include <sys/types.h>
53 #include <sys/socket.h>
54 #include <sys/ioctl.h>
55 #include <sys/filio.h>
56 
57 #include <netinet/in.h>
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <unistd.h>
61 #include <signal.h>
62 #include <errno.h>
63 #include <poll.h>
64 #include <string.h>
65 
66 #include "tftpcommon.h"
67 
68 struct errmsg errmsgs[] = {
69 	{ EUNDEF,	"Undefined error code" },
70 	{ ENOTFOUND,	"File not found" },
71 	{ EACCESS,	"Access violation" },
72 	{ ENOSPACE,	"Disk full or allocation exceeded" },
73 	{ EBADOP,	"Illegal TFTP operation" },
74 	{ EBADID,	"Unknown transfer ID" },
75 	{ EEXISTS,	"File already exists" },
76 	{ ENOUSER,	"No such user" },
77 	{ EOPTNEG,	"Option negotiation error" },
78 	{ -1,		NULL }
79 };
80 
81 static struct bf {
82 	int	counter;	/* size of data in buffer, or flag */
83 	tftpbuf	buf;		/* room for data packet */
84 } bfs[2];
85 
86 extern int blocksize;		/* Number of data bytes in a DATA packet */
87 				/* Values for bf.counter  */
88 #define	BF_ALLOC -3	/* alloc'd but not yet filled */
89 #define	BF_FREE  -2	/* free */
90 /* [-1 .. blocksize] = size of data in the data buffer */
91 
92 static int nextone;	/* index of next buffer to use */
93 static int current;	/* index of buffer in use */
94 
95 			/* control flags for crlf conversions */
96 static int newline = 0;	/* fillbuf: in middle of newline expansion */
97 static int prevchar = -1;	/* putbuf: previous char (cr check) */
98 
99 static struct tftphdr *rw_init(int);
100 
101 struct tftphdr *w_init() { return (rw_init(0)); }	/* write-behind */
102 struct tftphdr *r_init() { return (rw_init(1)); }	/* read-ahead */
103 
104 /*
105  * Init for either read-ahead or write-behind.
106  * x is zero for write-behind, one for read-head.
107  */
108 static struct tftphdr *
109 rw_init(int x)
110 {
111 	newline = 0;	/* init crlf flag */
112 	prevchar = -1;
113 	bfs[0].counter = BF_ALLOC;	/* pass out the first buffer */
114 	current = 0;
115 	bfs[1].counter = BF_FREE;
116 	nextone = x;	/* ahead or behind? */
117 	return (&bfs[0].buf.tb_hdr);
118 }
119 
120 
121 /*
122  * Have emptied current buffer by sending to net and getting ack.
123  * Free it and return next buffer filled with data.
124  */
125 int
126 readit(FILE *file, struct tftphdr **dpp, int convert)
127 {
128 	struct bf *b;
129 
130 	bfs[current].counter = BF_FREE; /* free old one */
131 	current = !current;	/* "incr" current */
132 
133 	b = &bfs[current];	/* look at new buffer */
134 	if (b->counter == BF_FREE)	/* if it's empty */
135 		read_ahead(file, convert);	/* fill it */
136 	*dpp = &b->buf.tb_hdr;	/* set caller's ptr */
137 	return (b->counter);
138 }
139 
140 /*
141  * fill the input buffer, doing ascii conversions if requested
142  * conversions are  lf -> cr,lf  and cr -> cr, nul
143  */
144 void
145 read_ahead(FILE *file, int convert)
146 {
147 	int i;
148 	char *p;
149 	int c;
150 	struct bf *b;
151 	struct tftphdr *dp;
152 
153 	b = &bfs[nextone];	/* look at "next" buffer */
154 	if (b->counter != BF_FREE)	/* nop if not free */
155 		return;
156 	nextone = !nextone;	/* "incr" next buffer ptr */
157 
158 	dp = &b->buf.tb_hdr;
159 
160 	if (!convert) {
161 		b->counter = fread(dp->th_data, sizeof (char), blocksize,
162 		    file);
163 		if (ferror(file))
164 			b->counter = -1;
165 		return;
166 	}
167 
168 	p = dp->th_data;
169 	for (i = 0; i < blocksize; i++) {
170 		if (newline) {
171 			if (prevchar == '\n')
172 				c = '\n';	/* lf to cr,lf */
173 			else    c = '\0';	/* cr to cr,nul */
174 			newline = 0;
175 		} else {
176 			c = getc(file);
177 			if (c == EOF) break;
178 			if (c == '\n' || c == '\r') {
179 				prevchar = c;
180 				c = '\r';
181 				newline = 1;
182 			}
183 		}
184 		*p++ = c;
185 	}
186 	b->counter = (int)(p - dp->th_data);
187 }
188 
189 /*
190  * Update count associated with the buffer, get new buffer
191  * from the queue.  Calls write_behind only if next buffer not
192  * available.
193  */
194 int
195 writeit(FILE *file, struct tftphdr **dpp, int ct, int convert)
196 {
197 	bfs[current].counter = ct;	/* set size of data to write */
198 	current = !current;		/* switch to other buffer */
199 	if (bfs[current].counter != BF_FREE)	/* if not free */
200 		if (write_behind(file, convert) < 0)	/* flush it */
201 			ct = -1;
202 	bfs[current].counter = BF_ALLOC;	/* mark as alloc'd */
203 	*dpp = &bfs[current].buf.tb_hdr;
204 	return (ct);	/* this is a lie of course */
205 }
206 
207 /*
208  * Output a buffer to a file, converting from netascii if requested.
209  * CR,NUL -> CR  and CR,LF => LF.
210  * Note spec is undefined if we get CR as last byte of file or a
211  * CR followed by anything else.  In this case we leave it alone.
212  */
213 int
214 write_behind(FILE *file, int convert)
215 {
216 	char *buf;
217 	int count;
218 	int ct;
219 	char *p;
220 	int c;	/* current character */
221 	struct bf *b;
222 	struct tftphdr *dp;
223 
224 	b = &bfs[nextone];
225 	if (b->counter < -1)	/* anything to flush? */
226 		return (0);	/* just nop if nothing to do */
227 
228 	count = b->counter;	/* remember byte count */
229 	b->counter = BF_FREE;	/* reset flag */
230 	dp = &b->buf.tb_hdr;
231 	nextone = !nextone;	/* incr for next time */
232 	buf = dp->th_data;
233 
234 	if (count <= 0)
235 		return (0);	/* nak logic? */
236 
237 	if (!convert) {
238 		size_t	left = count;
239 
240 		while (left > 0) {
241 			size_t	written;
242 
243 			written = fwrite(buf, sizeof (char), left, file);
244 			if (ferror(file)) {
245 				/* Retry if we were interrupted by a signal. */
246 				if (errno == EINTR)
247 					continue;
248 				return (-1);
249 			}
250 			if (written == 0)
251 				return (-1);
252 
253 			left -= written;
254 			buf += written;
255 		}
256 
257 		return (count);
258 	}
259 
260 	p = buf;
261 	ct = count;
262 	while (ct--) {	/* loop over the buffer */
263 		c = *p++;	/* pick up a character */
264 		if (prevchar == '\r') {	/* if prev char was cr */
265 			if (c == '\n') { /* if have cr,lf then just */
266 				/* smash lf on top of the cr */
267 				if (fseek(file, -1, SEEK_CUR) < 0)
268 					return (-1);
269 			} else {
270 				if (c == '\0') {
271 					/*
272 					 * If we have cr,nul then
273 					 * just skip over the putc.
274 					 */
275 					prevchar = 0;
276 					continue;
277 				}
278 			}
279 			/* else just fall through and allow it */
280 		}
281 		if (putc(c, file) == EOF)
282 			return (-1);
283 		prevchar = c;
284 	}
285 	return (count);
286 }
287 
288 
289 /*
290  * When an error has occurred, it is possible that the two sides
291  * are out of synch.  Ie: that what I think is the other side's
292  * response to packet N is really their response to packet N-1.
293  *
294  * So, to try to prevent that, we flush all the input queued up
295  * for us on the network connection on our host.
296  *
297  * We return the number of packets we flushed (mostly for reporting
298  * when trace is active) or -1 in case of an error.
299  */
300 
301 int
302 synchnet(int socket)
303 {
304 	struct pollfd	pfd;
305 	int 		packets;
306 
307 	pfd.fd = socket;
308 	pfd.events = POLLRDNORM;
309 	for (packets = 0; ; packets++) {
310 		char			buf;
311 		struct sockaddr_in6	from;
312 		socklen_t		fromlen;
313 
314 		if (poll(&pfd, 1, 0) <= 0)
315 			break;
316 
317 		/*
318 		 * A one byte buffer is enough because recvfrom() will
319 		 * discard the remaining data of the packet.
320 		 */
321 		fromlen = sizeof (from);
322 		if (recvfrom(socket, &buf, sizeof (buf), 0,
323 		    (struct sockaddr *)&from, &fromlen) < 0)
324 			return (-1);
325 	}
326 
327 	return (packets);
328 }
329 
330 /*
331  * Return a pointer to the next field in string s, or return NULL if no
332  * terminating NUL is found for the current field before end.
333  */
334 char *
335 next_field(const char *s, const char *end)
336 {
337 	if (s < end) {
338 		s = memchr(s, 0, end - s);
339 		if (s != NULL)
340 			return ((char *)s + 1);
341 	}
342 	return (NULL);
343 }
344 
345 /*
346  * Print to stream options in the format option_name=option_value
347  */
348 void
349 print_options(FILE *stream, char *opts, int len)
350 {
351 	char *cp, *optname, *optval;
352 	char *endopts = opts + len;
353 	int first = 1;
354 
355 	/*
356 	 * Ignore null padding, appended by broken TFTP clients to
357 	 * requests which don't include options.
358 	 */
359 	cp = opts;
360 	while ((cp < endopts) && (*cp == '\0'))
361 		cp++;
362 	if (cp == endopts)
363 		return;
364 
365 	while (opts < endopts) {
366 		optname = opts;
367 		if ((optval = next_field(optname, endopts)) == NULL) {
368 			(void) putc('?', stream);
369 			return;
370 		}
371 		if (first)
372 			first = 0;
373 		else
374 			(void) putc(' ', stream);
375 		(void) fputs(optname, stream);
376 		if ((opts = next_field(optval, endopts)) == NULL) {
377 			(void) putc('?', stream);
378 			return;
379 		}
380 		(void) fprintf(stream, "=%s", optval);
381 	}
382 }
383 
384 /*
385  * Turn off the alarm timer and ensure any pending SIGALRM signal is ignored.
386  */
387 void
388 cancel_alarm(void)
389 {
390 	(void) alarm(0);
391 	(void) signal(SIGALRM, SIG_IGN);
392 	(void) sigrelse(SIGALRM);
393 }
394