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