xref: /freebsd/libexec/tftpd/tftp-transfer.c (revision 06c3fb2749bda94cb5201f81ffdb8fa6c3161b2e)
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 
49 struct 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  */
58 int
59 tftp_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 {
75 read_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  */
242 int
243 tftp_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;
404 send_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