1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (C) 2008 Edwin Groothuis. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/cdefs.h>
29__FBSDID("$FreeBSD$");
30
31#include <sys/types.h>
32#include <sys/ioctl.h>
33#include <sys/socket.h>
34#include <sys/stat.h>
35
36#include <netinet/in.h>
37#include <arpa/tftp.h>
38
39#include <assert.h>
40#include <errno.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <syslog.h>
45#include <unistd.h>
46
47#include "tftp-file.h"
48#include "tftp-utils.h"
49
50static FILE	*file;
51static int	convert;
52
53static char	convbuffer[66000];
54static int	gotcr = 0;
55
56static size_t
57convert_from_net(char *buffer, size_t count)
58{
59	size_t i, n;
60
61	/*
62	 * Convert all CR/LF to LF and all CR,NUL to CR
63	 */
64
65	n = 0;
66	for (i = 0; i < count; i++) {
67
68		if (gotcr == 0) {
69			convbuffer[n++] = buffer[i];
70			gotcr = (buffer[i] == '\r');
71			continue;
72		}
73
74		/* CR, NULL -> CR */
75		if (buffer[i] == '\0') {
76			gotcr = 0;
77			continue;
78		}
79
80		/* CR, LF -> LF */
81		if (buffer[i] == '\n') {
82			if (n == 0) {
83				if (ftell(file) != 0) {
84					int r = fseek(file, -1, SEEK_END);
85					assert(r == 0);
86					convbuffer[n++] = '\n';
87				} else {
88					/* This shouldn't happen */
89					tftp_log(LOG_ERR,
90					    "Received LF as first character");
91					abort();
92				}
93			} else
94				convbuffer[n-1] = '\n';
95			gotcr = 0;
96			continue;
97		}
98
99		/* Everything else just accept as is */
100		convbuffer[n++] = buffer[i];
101		gotcr = (buffer[i] == '\r');
102		continue;
103	}
104
105	return fwrite(convbuffer, 1, n, file);
106}
107
108static size_t
109convert_to_net(char *buffer, size_t count, int init)
110{
111	size_t i;
112	static size_t n = 0, in = 0;
113	static int newline = -1;
114
115	if (init) {
116		newline = -1;
117		n = 0;
118		in = 0;
119		return 0 ;
120	}
121
122	/*
123	 * Convert all LF to CR,LF and all CR to CR,NUL
124	 */
125	i = 0;
126
127	if (newline != -1) {
128		buffer[i++] = newline;
129		newline = -1;
130	}
131
132	while (i < count) {
133		if (n == in) {
134			/* When done we're done */
135			if (feof(file)) break;
136
137			/* Otherwise read another bunch */
138			in = fread(convbuffer, 1, count, file);
139			if (in == 0) break;
140			n = 0;
141		}
142
143		/* CR -> CR,NULL */
144		if (convbuffer[n] == '\r') {
145			buffer[i++] = '\r';
146			buffer[i++] = '\0';
147			n++;
148			continue;
149		}
150
151		/* LF -> CR,LF */
152		if (convbuffer[n] == '\n') {
153			buffer[i++] = '\r';
154			buffer[i++] = '\n';
155			n++;
156			continue;
157		}
158
159		buffer[i++] = convbuffer[n++];
160	}
161
162	if (i > count) {
163		/*
164		 * Whoops... that isn't allowed (but it will happen
165		 * when there is a CR or LF at the end of the buffer)
166		 */
167		newline = buffer[i-1];
168	}
169
170	if (i < count) {
171		/* We are done! */
172		return i;
173	} else
174		return count;
175
176}
177
178int
179write_init(int fd, FILE *f, const char *mode)
180{
181
182	if (f == NULL) {
183		file = fdopen(fd, "w");
184		if (file == NULL) {
185			int en = errno;
186			tftp_log(LOG_ERR, "fdopen() failed: %s",
187			    strerror(errno));
188			return en;
189		}
190	} else
191		file = f;
192	convert = !strcmp(mode, "netascii");
193	return 0;
194}
195
196size_t
197write_file(char *buffer, int count)
198{
199
200	if (convert == 0)
201		return fwrite(buffer, 1, count, file);
202
203	return convert_from_net(buffer, count);
204}
205
206int
207write_close(void)
208{
209
210	if (fclose(file) != 0) {
211		tftp_log(LOG_ERR, "fclose() failed: %s", strerror(errno));
212		return 1;
213	}
214	return 0;
215}
216
217off_t
218tell_file(void)
219{
220
221	return ftello(file);
222}
223
224int
225seek_file(off_t offset)
226{
227
228	return fseeko(file, offset, SEEK_SET);
229}
230
231int
232read_init(int fd, FILE *f, const char *mode)
233{
234
235	convert_to_net(NULL, 0, 1);
236	if (f == NULL) {
237		file = fdopen(fd, "r");
238		if (file == NULL) {
239			int en = errno;
240			tftp_log(LOG_ERR, "fdopen() failed: %s",
241			    strerror(errno));
242			return en;
243		}
244	} else
245		file = f;
246	convert = !strcmp(mode, "netascii");
247	return 0;
248}
249
250size_t
251read_file(char *buffer, int count)
252{
253
254	if (convert == 0)
255		return fread(buffer, 1, count, file);
256
257	return convert_to_net(buffer, count, 0);
258}
259
260int
261read_close(void)
262{
263
264	if (fclose(file) != 0) {
265		tftp_log(LOG_ERR, "fclose() failed: %s", strerror(errno));
266		return 1;
267	}
268	return 0;
269}
270
271
272/* When an error has occurred, it is possible that the two sides
273 * are out of synch.  Ie: that what I think is the other side's
274 * response to packet N is really their response to packet N-1.
275 *
276 * So, to try to prevent that, we flush all the input queued up
277 * for us on the network connection on our host.
278 *
279 * We return the number of packets we flushed (mostly for reporting
280 * when trace is active).
281 */
282
283int
284synchnet(int peer)			/* socket to flush */
285{
286	int i, j = 0;
287	char rbuf[MAXPKTSIZE];
288	struct sockaddr_storage from;
289	socklen_t fromlen;
290
291	while (1) {
292		(void) ioctl(peer, FIONREAD, &i);
293		if (i) {
294			j++;
295			fromlen = sizeof from;
296			(void) recvfrom(peer, rbuf, sizeof (rbuf), 0,
297				(struct sockaddr *)&from, &fromlen);
298		} else {
299			return(j);
300		}
301	}
302}
303