xref: /freebsd/libexec/tftpd/tftp-transfer.c (revision 7378015b694df3fbbc8cf51fe5ed352701f20e01)
1e6209940SPedro F. Giffuni /*-
2e6209940SPedro F. Giffuni  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3e6209940SPedro F. Giffuni  *
4e7ff5475SWarner Losh  * Copyright (C) 2008 Edwin Groothuis. All rights reserved.
5e7ff5475SWarner Losh  *
6e7ff5475SWarner Losh  * Redistribution and use in source and binary forms, with or without
7e7ff5475SWarner Losh  * modification, are permitted provided that the following conditions
8e7ff5475SWarner Losh  * are met:
9e7ff5475SWarner Losh  * 1. Redistributions of source code must retain the above copyright
10e7ff5475SWarner Losh  *    notice, this list of conditions and the following disclaimer.
11e7ff5475SWarner Losh  * 2. Redistributions in binary form must reproduce the above copyright
12e7ff5475SWarner Losh  *    notice, this list of conditions and the following disclaimer in the
13e7ff5475SWarner Losh  *    documentation and/or other materials provided with the distribution.
14e7ff5475SWarner Losh  *
15e7ff5475SWarner Losh  * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16e7ff5475SWarner Losh  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17e7ff5475SWarner Losh  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18e7ff5475SWarner Losh  * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
19e7ff5475SWarner Losh  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20e7ff5475SWarner Losh  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21e7ff5475SWarner Losh  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22e7ff5475SWarner Losh  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23e7ff5475SWarner Losh  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24e7ff5475SWarner Losh  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25e7ff5475SWarner Losh  * SUCH DAMAGE.
26e7ff5475SWarner Losh  */
27e7ff5475SWarner Losh 
28e7ff5475SWarner Losh #include <sys/cdefs.h>
29e7ff5475SWarner Losh __FBSDID("$FreeBSD$");
30e7ff5475SWarner Losh 
31e7ff5475SWarner Losh #include <sys/types.h>
32e7ff5475SWarner Losh #include <sys/param.h>
33e7ff5475SWarner Losh #include <sys/ioctl.h>
34e7ff5475SWarner Losh #include <sys/stat.h>
35e7ff5475SWarner Losh #include <sys/socket.h>
36e7ff5475SWarner Losh 
37e7ff5475SWarner Losh #include <netinet/in.h>
38e7ff5475SWarner Losh #include <arpa/tftp.h>
39e7ff5475SWarner Losh 
40e7ff5475SWarner Losh #include <errno.h>
41e7ff5475SWarner Losh #include <stdio.h>
42e7ff5475SWarner Losh #include <stdlib.h>
43e7ff5475SWarner Losh #include <syslog.h>
44e7ff5475SWarner Losh 
45e7ff5475SWarner Losh #include "tftp-file.h"
46e7ff5475SWarner Losh #include "tftp-io.h"
47e7ff5475SWarner Losh #include "tftp-utils.h"
48e7ff5475SWarner Losh #include "tftp-options.h"
49e7ff5475SWarner Losh #include "tftp-transfer.h"
50e7ff5475SWarner Losh 
51e7ff5475SWarner Losh /*
52e7ff5475SWarner Losh  * Send a file via the TFTP data session.
53e7ff5475SWarner Losh  */
54e7ff5475SWarner Losh void
55e7ff5475SWarner Losh tftp_send(int peer, uint16_t *block, struct tftp_stats *ts)
56e7ff5475SWarner Losh {
57e7ff5475SWarner Losh 	struct tftphdr *rp;
58e7ff5475SWarner Losh 	int size, n_data, n_ack, try;
59e7ff5475SWarner Losh 	uint16_t oldblock;
60e7ff5475SWarner Losh 	char sendbuffer[MAXPKTSIZE];
61e7ff5475SWarner Losh 	char recvbuffer[MAXPKTSIZE];
62e7ff5475SWarner Losh 
63e7ff5475SWarner Losh 	rp = (struct tftphdr *)recvbuffer;
64e7ff5475SWarner Losh 	*block = 1;
65e7ff5475SWarner Losh 	ts->amount = 0;
66e7ff5475SWarner Losh 	do {
67e7ff5475SWarner Losh 		if (debug&DEBUG_SIMPLE)
68e7ff5475SWarner Losh 			tftp_log(LOG_DEBUG, "Sending block %d", *block);
69e7ff5475SWarner Losh 
70e7ff5475SWarner Losh 		size = read_file(sendbuffer, segsize);
71e7ff5475SWarner Losh 		if (size < 0) {
72e7ff5475SWarner Losh 			tftp_log(LOG_ERR, "read_file returned %d", size);
73e7ff5475SWarner Losh 			send_error(peer, errno + 100);
74e7ff5475SWarner Losh 			goto abort;
75e7ff5475SWarner Losh 		}
76e7ff5475SWarner Losh 
77e7ff5475SWarner Losh 		for (try = 0; ; try++) {
78e7ff5475SWarner Losh 			n_data = send_data(peer, *block, sendbuffer, size);
79e7ff5475SWarner Losh 			if (n_data > 0) {
80e7ff5475SWarner Losh 				if (try == maxtimeouts) {
81e7ff5475SWarner Losh 					tftp_log(LOG_ERR,
82e7ff5475SWarner Losh 					    "Cannot send DATA packet #%d, "
83e7ff5475SWarner Losh 					    "giving up", *block);
84e7ff5475SWarner Losh 					return;
85e7ff5475SWarner Losh 				}
86e7ff5475SWarner Losh 				tftp_log(LOG_ERR,
87e7ff5475SWarner Losh 				    "Cannot send DATA packet #%d, trying again",
88e7ff5475SWarner Losh 				    *block);
89e7ff5475SWarner Losh 				continue;
90e7ff5475SWarner Losh 			}
91e7ff5475SWarner Losh 
92e7ff5475SWarner Losh 			n_ack = receive_packet(peer, recvbuffer,
93e7ff5475SWarner Losh 			    MAXPKTSIZE, NULL, timeoutpacket);
94e7ff5475SWarner Losh 			if (n_ack < 0) {
95e7ff5475SWarner Losh 				if (n_ack == RP_TIMEOUT) {
96e7ff5475SWarner Losh 					if (try == maxtimeouts) {
97e7ff5475SWarner Losh 						tftp_log(LOG_ERR,
98e7ff5475SWarner Losh 						    "Timeout #%d send ACK %d "
99e7ff5475SWarner Losh 						    "giving up", try, *block);
100e7ff5475SWarner Losh 						return;
101e7ff5475SWarner Losh 					}
102e7ff5475SWarner Losh 					tftp_log(LOG_WARNING,
103e7ff5475SWarner Losh 					    "Timeout #%d on ACK %d",
104e7ff5475SWarner Losh 					    try, *block);
105e7ff5475SWarner Losh 					continue;
106e7ff5475SWarner Losh 				}
107e7ff5475SWarner Losh 
108e7ff5475SWarner Losh 				/* Either read failure or ERROR packet */
109e7ff5475SWarner Losh 				if (debug&DEBUG_SIMPLE)
110e7ff5475SWarner Losh 					tftp_log(LOG_ERR, "Aborting: %s",
111e7ff5475SWarner Losh 					    rp_strerror(n_ack));
112e7ff5475SWarner Losh 				goto abort;
113e7ff5475SWarner Losh 			}
114e7ff5475SWarner Losh 			if (rp->th_opcode == ACK) {
115e7ff5475SWarner Losh 				ts->blocks++;
116e7ff5475SWarner Losh 				if (rp->th_block == *block) {
117e7ff5475SWarner Losh 					ts->amount += size;
118e7ff5475SWarner Losh 					break;
119e7ff5475SWarner Losh 				}
120e7ff5475SWarner Losh 
121e7ff5475SWarner Losh 				/* Re-synchronize with the other side */
122e7ff5475SWarner Losh 				(void) synchnet(peer);
123e7ff5475SWarner Losh 				if (rp->th_block == (*block - 1)) {
124e7ff5475SWarner Losh 					ts->retries++;
125e7ff5475SWarner Losh 					continue;
126e7ff5475SWarner Losh 				}
127e7ff5475SWarner Losh 			}
128e7ff5475SWarner Losh 
129e7ff5475SWarner Losh 		}
130e7ff5475SWarner Losh 		oldblock = *block;
131e7ff5475SWarner Losh 		(*block)++;
132e7ff5475SWarner Losh 		if (oldblock > *block) {
133e7ff5475SWarner Losh 			if (options[OPT_ROLLOVER].o_request == NULL) {
13438bd7db3SCraig Rodrigues 				/*
13538bd7db3SCraig Rodrigues 				 * "rollover" option not specified in
13638bd7db3SCraig Rodrigues 				 * tftp client.  Default to rolling block
13738bd7db3SCraig Rodrigues 				 * counter to 0.
13838bd7db3SCraig Rodrigues 				 */
13938bd7db3SCraig Rodrigues 				*block = 0;
14038bd7db3SCraig Rodrigues 			} else {
14138bd7db3SCraig Rodrigues 				*block = atoi(options[OPT_ROLLOVER].o_request);
142e7ff5475SWarner Losh 			}
143e7ff5475SWarner Losh 
144e7ff5475SWarner Losh 			ts->rollovers++;
145e7ff5475SWarner Losh 		}
146e7ff5475SWarner Losh 		gettimeofday(&(ts->tstop), NULL);
147e7ff5475SWarner Losh 	} while (size == segsize);
148e7ff5475SWarner Losh abort:
149e7ff5475SWarner Losh 	return;
150e7ff5475SWarner Losh }
151e7ff5475SWarner Losh 
152e7ff5475SWarner Losh /*
153e7ff5475SWarner Losh  * Receive a file via the TFTP data session.
154e7ff5475SWarner Losh  *
155e7ff5475SWarner Losh  * - It could be that the first block has already arrived while
156e7ff5475SWarner Losh  *   trying to figure out if we were receiving options or not. In
157e7ff5475SWarner Losh  *   that case it is passed to this function.
158e7ff5475SWarner Losh  */
159e7ff5475SWarner Losh void
160e7ff5475SWarner Losh tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
161e7ff5475SWarner Losh     struct tftphdr *firstblock, size_t fb_size)
162e7ff5475SWarner Losh {
163e7ff5475SWarner Losh 	struct tftphdr *rp;
164e7ff5475SWarner Losh 	uint16_t oldblock;
165e7ff5475SWarner Losh 	int n_data, n_ack, writesize, i, retry;
166e7ff5475SWarner Losh 	char recvbuffer[MAXPKTSIZE];
167e7ff5475SWarner Losh 
168e7ff5475SWarner Losh 	ts->amount = 0;
169e7ff5475SWarner Losh 
170e7ff5475SWarner Losh 	if (firstblock != NULL) {
171e7ff5475SWarner Losh 		writesize = write_file(firstblock->th_data, fb_size);
172e7ff5475SWarner Losh 		ts->amount += writesize;
173e7ff5475SWarner Losh 		for (i = 0; ; i++) {
174e7ff5475SWarner Losh 			n_ack = send_ack(peer, *block);
175e7ff5475SWarner Losh 			if (n_ack > 0) {
176e7ff5475SWarner Losh 				if (i == maxtimeouts) {
177e7ff5475SWarner Losh 					tftp_log(LOG_ERR,
178e7ff5475SWarner Losh 					    "Cannot send ACK packet #%d, "
179e7ff5475SWarner Losh 					    "giving up", *block);
180e7ff5475SWarner Losh 					return;
181e7ff5475SWarner Losh 				}
182e7ff5475SWarner Losh 				tftp_log(LOG_ERR,
183e7ff5475SWarner Losh 				    "Cannot send ACK packet #%d, trying again",
184e7ff5475SWarner Losh 				    *block);
185e7ff5475SWarner Losh 				continue;
186e7ff5475SWarner Losh 			}
187e7ff5475SWarner Losh 
188e7ff5475SWarner Losh 			break;
189e7ff5475SWarner Losh 		}
190e7ff5475SWarner Losh 
191e7ff5475SWarner Losh 		if (fb_size != segsize) {
192e7ff5475SWarner Losh 			gettimeofday(&(ts->tstop), NULL);
193e7ff5475SWarner Losh 			return;
194e7ff5475SWarner Losh 		}
195e7ff5475SWarner Losh 	}
196e7ff5475SWarner Losh 
197e7ff5475SWarner Losh 	rp = (struct tftphdr *)recvbuffer;
198e7ff5475SWarner Losh 	do {
199e7ff5475SWarner Losh 		oldblock = *block;
200e7ff5475SWarner Losh 		(*block)++;
201e7ff5475SWarner Losh 		if (oldblock > *block) {
202e7ff5475SWarner Losh 			if (options[OPT_ROLLOVER].o_request == NULL) {
20338bd7db3SCraig Rodrigues 				/*
20438bd7db3SCraig Rodrigues 				 * "rollover" option not specified in
20538bd7db3SCraig Rodrigues 				 * tftp client.  Default to rolling block
20638bd7db3SCraig Rodrigues 				 * counter to 0.
20738bd7db3SCraig Rodrigues 				 */
20838bd7db3SCraig Rodrigues 				*block = 0;
20938bd7db3SCraig Rodrigues 			} else {
21038bd7db3SCraig Rodrigues 				*block = atoi(options[OPT_ROLLOVER].o_request);
211e7ff5475SWarner Losh 			}
212e7ff5475SWarner Losh 
213e7ff5475SWarner Losh 			ts->rollovers++;
214e7ff5475SWarner Losh 		}
215e7ff5475SWarner Losh 
216e7ff5475SWarner Losh 		for (retry = 0; ; retry++) {
217e7ff5475SWarner Losh 			if (debug&DEBUG_SIMPLE)
218e7ff5475SWarner Losh 				tftp_log(LOG_DEBUG,
219e7ff5475SWarner Losh 				    "Receiving DATA block %d", *block);
220e7ff5475SWarner Losh 
221e7ff5475SWarner Losh 			n_data = receive_packet(peer, recvbuffer,
222e7ff5475SWarner Losh 			    MAXPKTSIZE, NULL, timeoutpacket);
223e7ff5475SWarner Losh 			if (n_data < 0) {
224e7ff5475SWarner Losh 				if (retry == maxtimeouts) {
225e7ff5475SWarner Losh 					tftp_log(LOG_ERR,
226e7ff5475SWarner Losh 					    "Timeout #%d on DATA block %d, "
227e7ff5475SWarner Losh 					    "giving up", retry, *block);
228e7ff5475SWarner Losh 					return;
229e7ff5475SWarner Losh 				}
230e7ff5475SWarner Losh 				if (n_data == RP_TIMEOUT) {
231e7ff5475SWarner Losh 					tftp_log(LOG_WARNING,
232e7ff5475SWarner Losh 					    "Timeout #%d on DATA block %d",
233e7ff5475SWarner Losh 					    retry, *block);
234e7ff5475SWarner Losh 					send_ack(peer, oldblock);
235e7ff5475SWarner Losh 					continue;
236e7ff5475SWarner Losh 				}
237e7ff5475SWarner Losh 
238e7ff5475SWarner Losh 				/* Either read failure or ERROR packet */
239e7ff5475SWarner Losh 				if (debug&DEBUG_SIMPLE)
240e7ff5475SWarner Losh 					tftp_log(LOG_DEBUG, "Aborting: %s",
241e7ff5475SWarner Losh 					    rp_strerror(n_data));
242e7ff5475SWarner Losh 				goto abort;
243e7ff5475SWarner Losh 			}
244e7ff5475SWarner Losh 			if (rp->th_opcode == DATA) {
245e7ff5475SWarner Losh 				ts->blocks++;
246e7ff5475SWarner Losh 
247e7ff5475SWarner Losh 				if (rp->th_block == *block)
248e7ff5475SWarner Losh 					break;
249e7ff5475SWarner Losh 
250e7ff5475SWarner Losh 				tftp_log(LOG_WARNING,
251e7ff5475SWarner Losh 				    "Expected DATA block %d, got block %d",
252e7ff5475SWarner Losh 				    *block, rp->th_block);
253e7ff5475SWarner Losh 
254e7ff5475SWarner Losh 				/* Re-synchronize with the other side */
255e7ff5475SWarner Losh 				(void) synchnet(peer);
256e7ff5475SWarner Losh 				if (rp->th_block == (*block-1)) {
257e7ff5475SWarner Losh 					tftp_log(LOG_INFO, "Trying to sync");
258e7ff5475SWarner Losh 					*block = oldblock;
259e7ff5475SWarner Losh 					ts->retries++;
260e7ff5475SWarner Losh 					goto send_ack;	/* rexmit */
261e7ff5475SWarner Losh 				}
262e7ff5475SWarner Losh 
263e7ff5475SWarner Losh 			} else {
264e7ff5475SWarner Losh 				tftp_log(LOG_WARNING,
265e7ff5475SWarner Losh 				    "Expected DATA block, got %s block",
266e7ff5475SWarner Losh 				    packettype(rp->th_opcode));
267e7ff5475SWarner Losh 			}
268e7ff5475SWarner Losh 		}
269e7ff5475SWarner Losh 
270e7ff5475SWarner Losh 		if (n_data > 0) {
271e7ff5475SWarner Losh 			writesize = write_file(rp->th_data, n_data);
272e7ff5475SWarner Losh 			ts->amount += writesize;
273e7ff5475SWarner Losh 			if (writesize <= 0) {
274e7ff5475SWarner Losh 				tftp_log(LOG_ERR,
275e7ff5475SWarner Losh 				    "write_file returned %d", writesize);
276e7ff5475SWarner Losh 				if (writesize < 0)
277e7ff5475SWarner Losh 					send_error(peer, errno + 100);
278e7ff5475SWarner Losh 				else
279e7ff5475SWarner Losh 					send_error(peer, ENOSPACE);
280e7ff5475SWarner Losh 				goto abort;
281e7ff5475SWarner Losh 			}
282*7378015bSAlan Somers 			if (n_data != segsize)
283*7378015bSAlan Somers 				write_close();
284e7ff5475SWarner Losh 		}
285e7ff5475SWarner Losh 
286e7ff5475SWarner Losh send_ack:
287e7ff5475SWarner Losh 		for (i = 0; ; i++) {
288e7ff5475SWarner Losh 			n_ack = send_ack(peer, *block);
289e7ff5475SWarner Losh 			if (n_ack > 0) {
290e7ff5475SWarner Losh 
291e7ff5475SWarner Losh 				if (i == maxtimeouts) {
292e7ff5475SWarner Losh 					tftp_log(LOG_ERR,
293e7ff5475SWarner Losh 					    "Cannot send ACK packet #%d, "
294e7ff5475SWarner Losh 					    "giving up", *block);
295e7ff5475SWarner Losh 					return;
296e7ff5475SWarner Losh 				}
297e7ff5475SWarner Losh 
298e7ff5475SWarner Losh 				tftp_log(LOG_ERR,
299e7ff5475SWarner Losh 				    "Cannot send ACK packet #%d, trying again",
300e7ff5475SWarner Losh 				    *block);
301e7ff5475SWarner Losh 				continue;
302e7ff5475SWarner Losh 			}
303e7ff5475SWarner Losh 
304e7ff5475SWarner Losh 			break;
305e7ff5475SWarner Losh 		}
306e7ff5475SWarner Losh 		gettimeofday(&(ts->tstop), NULL);
307e7ff5475SWarner Losh 	} while (n_data == segsize);
308e7ff5475SWarner Losh 
309e7ff5475SWarner Losh 	/* Don't do late packet management for the client implementation */
310e7ff5475SWarner Losh 	if (acting_as_client)
311e7ff5475SWarner Losh 		return;
312e7ff5475SWarner Losh 
313e7ff5475SWarner Losh 	for (i = 0; ; i++) {
314e7ff5475SWarner Losh 		n_data = receive_packet(peer, (char *)rp, pktsize,
315e7ff5475SWarner Losh 		    NULL, timeoutpacket);
316e7ff5475SWarner Losh 		if (n_data <= 0)
317e7ff5475SWarner Losh 			break;
318e7ff5475SWarner Losh 		if (n_data > 0 &&
319e7ff5475SWarner Losh 		    rp->th_opcode == DATA &&	/* and got a data block */
320e7ff5475SWarner Losh 		    *block == rp->th_block)	/* then my last ack was lost */
321e7ff5475SWarner Losh 			send_ack(peer, *block);	/* resend final ack */
322e7ff5475SWarner Losh 	}
323e7ff5475SWarner Losh 
324e7ff5475SWarner Losh abort:
325e7ff5475SWarner Losh 	return;
326e7ff5475SWarner Losh }
327