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