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