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