]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - libexec/tftpd/tftp-transfer.c
kldxref: Fix bootstrapping on macOS with Clang 16 / Apple Clang 15
[FreeBSD/FreeBSD.git] / libexec / tftpd / tftp-transfer.c
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 }