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