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