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