Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/libexec/tftpd/tftp-transfer.c
34822 views
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
448