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
tftp_send(int peer,uint16_t * block,struct tftp_stats * ts)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
tftp_receive(int peer,uint16_t * block,struct tftp_stats * ts,struct tftphdr * firstblock,size_t fb_size)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