1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
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/param.h>
29#include <sys/ioctl.h>
30#include <sys/socket.h>
31#include <sys/stat.h>
32#include <sys/time.h>
33
34#include <netinet/in.h>
35#include <arpa/tftp.h>
36
37#include <errno.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <syslog.h>
42
43#include "tftp-file.h"
44#include "tftp-io.h"
45#include "tftp-utils.h"
46#include "tftp-options.h"
47#include "tftp-transfer.h"
48
49struct block_data {
50	off_t offset;
51	uint16_t block;
52	int size;
53};
54
55/*
56 * Send a file via the TFTP data session.
57 */
58int
59tftp_send(int peer, uint16_t *block, struct tftp_stats *ts)
60{
61	struct tftphdr *rp;
62	int size, n_data, n_ack, sendtry, acktry;
63	u_int i, j;
64	uint16_t oldblock, windowblock;
65	char sendbuffer[MAXPKTSIZE];
66	char recvbuffer[MAXPKTSIZE];
67	struct block_data window[WINDOWSIZE_MAX];
68
69	rp = (struct tftphdr *)recvbuffer;
70	*block = 1;
71	ts->amount = 0;
72	windowblock = 0;
73	acktry = 0;
74	do {
75read_block:
76		if (debug & DEBUG_SIMPLE)
77			tftp_log(LOG_DEBUG, "Sending block %d (window block %d)",
78			    *block, windowblock);
79
80		window[windowblock].offset = tell_file();
81		window[windowblock].block = *block;
82		size = read_file(sendbuffer, segsize);
83		if (size < 0) {
84			tftp_log(LOG_ERR, "read_file returned %d", size);
85			send_error(peer, errno + 100);
86			return -1;
87		}
88		window[windowblock].size = size;
89		windowblock++;
90
91		for (sendtry = 0; ; sendtry++) {
92			n_data = send_data(peer, *block, sendbuffer, size);
93			if (n_data == 0)
94				break;
95
96			if (sendtry == maxtimeouts) {
97				tftp_log(LOG_ERR,
98				    "Cannot send DATA packet #%d, "
99				    "giving up", *block);
100				return -1;
101			}
102			tftp_log(LOG_ERR,
103			    "Cannot send DATA packet #%d, trying again",
104			    *block);
105		}
106
107		/* Only check for ACK for last block in window. */
108		if (windowblock == windowsize || size != segsize) {
109			n_ack = receive_packet(peer, recvbuffer,
110			    MAXPKTSIZE, NULL, timeoutpacket);
111			if (n_ack < 0) {
112				if (n_ack == RP_TIMEOUT) {
113					if (acktry == maxtimeouts) {
114						tftp_log(LOG_ERR,
115						    "Timeout #%d send ACK %d "
116						    "giving up", acktry, *block);
117						return -1;
118					}
119					tftp_log(LOG_WARNING,
120					    "Timeout #%d on ACK %d",
121					    acktry, *block);
122
123					acktry++;
124					ts->retries++;
125					if (seek_file(window[0].offset) != 0) {
126						tftp_log(LOG_ERR,
127						    "seek_file failed: %s",
128						    strerror(errno));
129						send_error(peer, errno + 100);
130						return -1;
131					}
132					*block = window[0].block;
133					windowblock = 0;
134					goto read_block;
135				}
136
137				/* Either read failure or ERROR packet */
138				if (debug & DEBUG_SIMPLE)
139					tftp_log(LOG_ERR, "Aborting: %s",
140					    rp_strerror(n_ack));
141				return -1;
142			}
143			if (rp->th_opcode == ACK) {
144				/*
145				 * Look for the ACKed block in our open
146				 * window.
147				 */
148				for (i = 0; i < windowblock; i++) {
149					if (rp->th_block == window[i].block)
150						break;
151				}
152
153				if (i == windowblock) {
154					/* Did not recognize ACK. */
155					if (debug & DEBUG_SIMPLE)
156						tftp_log(LOG_DEBUG,
157						    "ACK %d out of window",
158						    rp->th_block);
159
160					/* Re-synchronize with the other side */
161					(void) synchnet(peer);
162
163					/* Resend the current window. */
164					ts->retries++;
165					if (seek_file(window[0].offset) != 0) {
166						tftp_log(LOG_ERR,
167						    "seek_file failed: %s",
168						    strerror(errno));
169						send_error(peer, errno + 100);
170						return -1;
171					}
172					*block = window[0].block;
173					windowblock = 0;
174					goto read_block;
175				}
176
177				/* ACKed at least some data. */
178				acktry = 0;
179				for (j = 0; j <= i; j++) {
180					if (debug & DEBUG_SIMPLE)
181						tftp_log(LOG_DEBUG,
182						    "ACKed block %d",
183						    window[j].block);
184					ts->blocks++;
185					ts->amount += window[j].size;
186				}
187
188				/*
189				 * Partial ACK.  Rewind state to first
190				 * un-ACKed block.
191				 */
192				if (i + 1 != windowblock) {
193					if (debug & DEBUG_SIMPLE)
194						tftp_log(LOG_DEBUG,
195						    "Partial ACK");
196					if (seek_file(window[i + 1].offset) !=
197					    0) {
198						tftp_log(LOG_ERR,
199						    "seek_file failed: %s",
200						    strerror(errno));
201						send_error(peer, errno + 100);
202						return -1;
203					}
204					*block = window[i + 1].block;
205					windowblock = 0;
206					ts->retries++;
207					goto read_block;
208				}
209
210				windowblock = 0;
211			}
212
213		}
214		oldblock = *block;
215		(*block)++;
216		if (oldblock > *block) {
217			if (options[OPT_ROLLOVER].o_request == NULL) {
218				/*
219				 * "rollover" option not specified in
220				 * tftp client.  Default to rolling block
221				 * counter to 0.
222				 */
223				*block = 0;
224			} else {
225				*block = atoi(options[OPT_ROLLOVER].o_request);
226			}
227
228			ts->rollovers++;
229		}
230		gettimeofday(&(ts->tstop), NULL);
231	} while (size == segsize);
232	return 0;
233}
234
235/*
236 * Receive a file via the TFTP data session.
237 *
238 * - It could be that the first block has already arrived while
239 *   trying to figure out if we were receiving options or not. In
240 *   that case it is passed to this function.
241 */
242int
243tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
244    struct tftphdr *firstblock, size_t fb_size)
245{
246	struct tftphdr *rp;
247	uint16_t oldblock, windowstart;
248	int n_data, n_ack, writesize, i, retry, windowblock;
249	char recvbuffer[MAXPKTSIZE];
250
251	ts->amount = 0;
252	windowblock = 0;
253
254	if (firstblock != NULL) {
255		writesize = write_file(firstblock->th_data, fb_size);
256		ts->amount += writesize;
257		ts->blocks++;
258		windowblock++;
259		if (windowsize == 1 || fb_size != segsize) {
260			for (i = 0; ; i++) {
261				n_ack = send_ack(peer, *block);
262				if (n_ack > 0) {
263					if (i == maxtimeouts) {
264						tftp_log(LOG_ERR,
265						    "Cannot send ACK packet #%d, "
266						    "giving up", *block);
267						return -1;
268					}
269					tftp_log(LOG_ERR,
270					    "Cannot send ACK packet #%d, trying again",
271					    *block);
272					continue;
273				}
274
275				break;
276			}
277		}
278
279		if (fb_size != segsize) {
280			write_close();
281			gettimeofday(&(ts->tstop), NULL);
282			return 0;
283		}
284	}
285
286	rp = (struct tftphdr *)recvbuffer;
287	do {
288		oldblock = *block;
289		(*block)++;
290		if (oldblock > *block) {
291			if (options[OPT_ROLLOVER].o_request == NULL) {
292				/*
293				 * "rollover" option not specified in
294				 * tftp client.  Default to rolling block
295				 * counter to 0.
296				 */
297				*block = 0;
298			} else {
299				*block = atoi(options[OPT_ROLLOVER].o_request);
300			}
301
302			ts->rollovers++;
303		}
304
305		for (retry = 0; ; retry++) {
306			if (debug & DEBUG_SIMPLE)
307				tftp_log(LOG_DEBUG,
308				    "Receiving DATA block %d (window block %d)",
309				    *block, windowblock);
310
311			n_data = receive_packet(peer, recvbuffer,
312			    MAXPKTSIZE, NULL, timeoutpacket);
313			if (n_data < 0) {
314				if (retry == maxtimeouts) {
315					tftp_log(LOG_ERR,
316					    "Timeout #%d on DATA block %d, "
317					    "giving up", retry, *block);
318					return -1;
319				}
320				if (n_data == RP_TIMEOUT) {
321					tftp_log(LOG_WARNING,
322					    "Timeout #%d on DATA block %d",
323					    retry, *block);
324					send_ack(peer, oldblock);
325					windowblock = 0;
326					continue;
327				}
328
329				/* Either read failure or ERROR packet */
330				if (debug & DEBUG_SIMPLE)
331					tftp_log(LOG_DEBUG, "Aborting: %s",
332					    rp_strerror(n_data));
333				return -1;
334			}
335			if (rp->th_opcode == DATA) {
336				ts->blocks++;
337
338				if (rp->th_block == *block)
339					break;
340
341				/*
342				 * Ignore duplicate blocks within the
343				 * window.
344				 *
345				 * This does not handle duplicate
346				 * blocks during a rollover as
347				 * gracefully, but that should still
348				 * recover eventually.
349				 */
350				if (*block > windowsize)
351					windowstart = *block - windowsize;
352				else
353					windowstart = 0;
354				if (rp->th_block > windowstart &&
355				    rp->th_block < *block) {
356					if (debug & DEBUG_SIMPLE)
357						tftp_log(LOG_DEBUG,
358					    "Ignoring duplicate DATA block %d",
359						    rp->th_block);
360					windowblock++;
361					retry = 0;
362					continue;
363				}
364
365				tftp_log(LOG_WARNING,
366				    "Expected DATA block %d, got block %d",
367				    *block, rp->th_block);
368
369				/* Re-synchronize with the other side */
370				(void) synchnet(peer);
371
372				tftp_log(LOG_INFO, "Trying to sync");
373				*block = oldblock;
374				ts->retries++;
375				goto send_ack;	/* rexmit */
376
377			} else {
378				tftp_log(LOG_WARNING,
379				    "Expected DATA block, got %s block",
380				    packettype(rp->th_opcode));
381			}
382		}
383
384		if (n_data > 0) {
385			writesize = write_file(rp->th_data, n_data);
386			ts->amount += writesize;
387			if (writesize <= 0) {
388				tftp_log(LOG_ERR,
389				    "write_file returned %d", writesize);
390				if (writesize < 0)
391					send_error(peer, errno + 100);
392				else
393					send_error(peer, ENOSPACE);
394				return -1;
395			}
396		}
397		if (n_data != segsize)
398			write_close();
399		windowblock++;
400
401		/* Only send ACKs for the last block in the window. */
402		if (windowblock < windowsize && n_data == segsize)
403			continue;
404send_ack:
405		for (i = 0; ; i++) {
406			n_ack = send_ack(peer, *block);
407			if (n_ack > 0) {
408
409				if (i == maxtimeouts) {
410					tftp_log(LOG_ERR,
411					    "Cannot send ACK packet #%d, "
412					    "giving up", *block);
413					return -1;
414				}
415
416				tftp_log(LOG_ERR,
417				    "Cannot send ACK packet #%d, trying again",
418				    *block);
419				continue;
420			}
421
422			if (debug & DEBUG_SIMPLE)
423				tftp_log(LOG_DEBUG, "Sent ACK for %d", *block);
424			windowblock = 0;
425			break;
426		}
427		gettimeofday(&(ts->tstop), NULL);
428	} while (n_data == segsize);
429
430	/* Don't do late packet management for the client implementation */
431	if (acting_as_client)
432		return 0;
433
434	for (i = 0; ; i++) {
435		n_data = receive_packet(peer, (char *)rp, pktsize,
436		    NULL, -timeoutpacket);
437		if (n_data <= 0)
438			break;
439		if (n_data > 0 &&
440		    rp->th_opcode == DATA &&	/* and got a data block */
441		    *block == rp->th_block)	/* then my last ack was lost */
442			send_ack(peer, *block);	/* resend final ack */
443	}
444
445	return 0;
446}
447