1207614Simp/*
2207614Simp * Copyright (C) 2008 Edwin Groothuis. All rights reserved.
3207614Simp *
4207614Simp * Redistribution and use in source and binary forms, with or without
5207614Simp * modification, are permitted provided that the following conditions
6207614Simp * are met:
7207614Simp * 1. Redistributions of source code must retain the above copyright
8207614Simp *    notice, this list of conditions and the following disclaimer.
9207614Simp * 2. Redistributions in binary form must reproduce the above copyright
10207614Simp *    notice, this list of conditions and the following disclaimer in the
11207614Simp *    documentation and/or other materials provided with the distribution.
12207614Simp *
13207614Simp * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14207614Simp * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15207614Simp * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16207614Simp * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
17207614Simp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18207614Simp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19207614Simp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20207614Simp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21207614Simp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22207614Simp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23207614Simp * SUCH DAMAGE.
24207614Simp */
25207614Simp
26207614Simp#include <sys/cdefs.h>
27207614Simp__FBSDID("$FreeBSD$");
28207614Simp
29207614Simp#include <sys/types.h>
30207614Simp#include <sys/param.h>
31207614Simp#include <sys/ioctl.h>
32207614Simp#include <sys/stat.h>
33207614Simp#include <sys/socket.h>
34207614Simp
35207614Simp#include <netinet/in.h>
36207614Simp#include <arpa/tftp.h>
37207614Simp
38207614Simp#include <errno.h>
39207614Simp#include <stdio.h>
40207614Simp#include <stdlib.h>
41207614Simp#include <syslog.h>
42207614Simp
43207614Simp#include "tftp-file.h"
44207614Simp#include "tftp-io.h"
45207614Simp#include "tftp-utils.h"
46207614Simp#include "tftp-options.h"
47207614Simp#include "tftp-transfer.h"
48207614Simp
49207614Simp/*
50207614Simp * Send a file via the TFTP data session.
51207614Simp */
52207614Simpvoid
53207614Simptftp_send(int peer, uint16_t *block, struct tftp_stats *ts)
54207614Simp{
55207614Simp	struct tftphdr *rp;
56207614Simp	int size, n_data, n_ack, try;
57207614Simp	uint16_t oldblock;
58207614Simp	char sendbuffer[MAXPKTSIZE];
59207614Simp	char recvbuffer[MAXPKTSIZE];
60207614Simp
61207614Simp	rp = (struct tftphdr *)recvbuffer;
62207614Simp	*block = 1;
63207614Simp	ts->amount = 0;
64207614Simp	do {
65207614Simp		if (debug&DEBUG_SIMPLE)
66207614Simp			tftp_log(LOG_DEBUG, "Sending block %d", *block);
67207614Simp
68207614Simp		size = read_file(sendbuffer, segsize);
69207614Simp		if (size < 0) {
70207614Simp			tftp_log(LOG_ERR, "read_file returned %d", size);
71207614Simp			send_error(peer, errno + 100);
72207614Simp			goto abort;
73207614Simp		}
74207614Simp
75207614Simp		for (try = 0; ; try++) {
76207614Simp			n_data = send_data(peer, *block, sendbuffer, size);
77207614Simp			if (n_data > 0) {
78207614Simp				if (try == maxtimeouts) {
79207614Simp					tftp_log(LOG_ERR,
80207614Simp					    "Cannot send DATA packet #%d, "
81207614Simp					    "giving up", *block);
82207614Simp					return;
83207614Simp				}
84207614Simp				tftp_log(LOG_ERR,
85207614Simp				    "Cannot send DATA packet #%d, trying again",
86207614Simp				    *block);
87207614Simp				continue;
88207614Simp			}
89207614Simp
90207614Simp			n_ack = receive_packet(peer, recvbuffer,
91207614Simp			    MAXPKTSIZE, NULL, timeoutpacket);
92207614Simp			if (n_ack < 0) {
93207614Simp				if (n_ack == RP_TIMEOUT) {
94207614Simp					if (try == maxtimeouts) {
95207614Simp						tftp_log(LOG_ERR,
96207614Simp						    "Timeout #%d send ACK %d "
97207614Simp						    "giving up", try, *block);
98207614Simp						return;
99207614Simp					}
100207614Simp					tftp_log(LOG_WARNING,
101207614Simp					    "Timeout #%d on ACK %d",
102207614Simp					    try, *block);
103207614Simp					continue;
104207614Simp				}
105207614Simp
106207614Simp				/* Either read failure or ERROR packet */
107207614Simp				if (debug&DEBUG_SIMPLE)
108207614Simp					tftp_log(LOG_ERR, "Aborting: %s",
109207614Simp					    rp_strerror(n_ack));
110207614Simp				goto abort;
111207614Simp			}
112207614Simp			if (rp->th_opcode == ACK) {
113207614Simp				ts->blocks++;
114207614Simp				if (rp->th_block == *block) {
115207614Simp					ts->amount += size;
116207614Simp					break;
117207614Simp				}
118207614Simp
119207614Simp				/* Re-synchronize with the other side */
120207614Simp				(void) synchnet(peer);
121207614Simp				if (rp->th_block == (*block - 1)) {
122207614Simp					ts->retries++;
123207614Simp					continue;
124207614Simp				}
125207614Simp			}
126207614Simp
127207614Simp		}
128207614Simp		oldblock = *block;
129207614Simp		(*block)++;
130207614Simp		if (oldblock > *block) {
131207614Simp			if (options[OPT_ROLLOVER].o_request == NULL) {
132224536Srodrigc				/*
133224536Srodrigc				 * "rollover" option not specified in
134224536Srodrigc				 * tftp client.  Default to rolling block
135224536Srodrigc				 * counter to 0.
136224536Srodrigc				 */
137224536Srodrigc				*block = 0;
138224536Srodrigc			} else {
139224536Srodrigc				*block = atoi(options[OPT_ROLLOVER].o_request);
140207614Simp			}
141207614Simp
142207614Simp			ts->rollovers++;
143207614Simp		}
144207614Simp		gettimeofday(&(ts->tstop), NULL);
145207614Simp	} while (size == segsize);
146207614Simpabort:
147207614Simp	return;
148207614Simp}
149207614Simp
150207614Simp/*
151207614Simp * Receive a file via the TFTP data session.
152207614Simp *
153207614Simp * - It could be that the first block has already arrived while
154207614Simp *   trying to figure out if we were receiving options or not. In
155207614Simp *   that case it is passed to this function.
156207614Simp */
157207614Simpvoid
158207614Simptftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
159207614Simp    struct tftphdr *firstblock, size_t fb_size)
160207614Simp{
161207614Simp	struct tftphdr *rp;
162207614Simp	uint16_t oldblock;
163207614Simp	int n_data, n_ack, writesize, i, retry;
164207614Simp	char recvbuffer[MAXPKTSIZE];
165207614Simp
166207614Simp	ts->amount = 0;
167207614Simp
168207614Simp	if (firstblock != NULL) {
169207614Simp		writesize = write_file(firstblock->th_data, fb_size);
170207614Simp		ts->amount += writesize;
171207614Simp		for (i = 0; ; i++) {
172207614Simp			n_ack = send_ack(peer, *block);
173207614Simp			if (n_ack > 0) {
174207614Simp				if (i == maxtimeouts) {
175207614Simp					tftp_log(LOG_ERR,
176207614Simp					    "Cannot send ACK packet #%d, "
177207614Simp					    "giving up", *block);
178207614Simp					return;
179207614Simp				}
180207614Simp				tftp_log(LOG_ERR,
181207614Simp				    "Cannot send ACK packet #%d, trying again",
182207614Simp				    *block);
183207614Simp				continue;
184207614Simp			}
185207614Simp
186207614Simp			break;
187207614Simp		}
188207614Simp
189207614Simp		if (fb_size != segsize) {
190207614Simp			gettimeofday(&(ts->tstop), NULL);
191207614Simp			return;
192207614Simp		}
193207614Simp	}
194207614Simp
195207614Simp	rp = (struct tftphdr *)recvbuffer;
196207614Simp	do {
197207614Simp		oldblock = *block;
198207614Simp		(*block)++;
199207614Simp		if (oldblock > *block) {
200207614Simp			if (options[OPT_ROLLOVER].o_request == NULL) {
201224536Srodrigc				/*
202224536Srodrigc				 * "rollover" option not specified in
203224536Srodrigc				 * tftp client.  Default to rolling block
204224536Srodrigc				 * counter to 0.
205224536Srodrigc				 */
206224536Srodrigc				*block = 0;
207224536Srodrigc			} else {
208224536Srodrigc				*block = atoi(options[OPT_ROLLOVER].o_request);
209207614Simp			}
210207614Simp
211207614Simp			ts->rollovers++;
212207614Simp		}
213207614Simp
214207614Simp		for (retry = 0; ; retry++) {
215207614Simp			if (debug&DEBUG_SIMPLE)
216207614Simp				tftp_log(LOG_DEBUG,
217207614Simp				    "Receiving DATA block %d", *block);
218207614Simp
219207614Simp			n_data = receive_packet(peer, recvbuffer,
220207614Simp			    MAXPKTSIZE, NULL, timeoutpacket);
221207614Simp			if (n_data < 0) {
222207614Simp				if (retry == maxtimeouts) {
223207614Simp					tftp_log(LOG_ERR,
224207614Simp					    "Timeout #%d on DATA block %d, "
225207614Simp					    "giving up", retry, *block);
226207614Simp					return;
227207614Simp				}
228207614Simp				if (n_data == RP_TIMEOUT) {
229207614Simp					tftp_log(LOG_WARNING,
230207614Simp					    "Timeout #%d on DATA block %d",
231207614Simp					    retry, *block);
232207614Simp					send_ack(peer, oldblock);
233207614Simp					continue;
234207614Simp				}
235207614Simp
236207614Simp				/* Either read failure or ERROR packet */
237207614Simp				if (debug&DEBUG_SIMPLE)
238207614Simp					tftp_log(LOG_DEBUG, "Aborting: %s",
239207614Simp					    rp_strerror(n_data));
240207614Simp				goto abort;
241207614Simp			}
242207614Simp			if (rp->th_opcode == DATA) {
243207614Simp				ts->blocks++;
244207614Simp
245207614Simp				if (rp->th_block == *block)
246207614Simp					break;
247207614Simp
248207614Simp				tftp_log(LOG_WARNING,
249207614Simp				    "Expected DATA block %d, got block %d",
250207614Simp				    *block, rp->th_block);
251207614Simp
252207614Simp				/* Re-synchronize with the other side */
253207614Simp				(void) synchnet(peer);
254207614Simp				if (rp->th_block == (*block-1)) {
255207614Simp					tftp_log(LOG_INFO, "Trying to sync");
256207614Simp					*block = oldblock;
257207614Simp					ts->retries++;
258207614Simp					goto send_ack;	/* rexmit */
259207614Simp				}
260207614Simp
261207614Simp			} else {
262207614Simp				tftp_log(LOG_WARNING,
263207614Simp				    "Expected DATA block, got %s block",
264207614Simp				    packettype(rp->th_opcode));
265207614Simp			}
266207614Simp		}
267207614Simp
268207614Simp		if (n_data > 0) {
269207614Simp			writesize = write_file(rp->th_data, n_data);
270207614Simp			ts->amount += writesize;
271207614Simp			if (writesize <= 0) {
272207614Simp				tftp_log(LOG_ERR,
273207614Simp				    "write_file returned %d", writesize);
274207614Simp				if (writesize < 0)
275207614Simp					send_error(peer, errno + 100);
276207614Simp				else
277207614Simp					send_error(peer, ENOSPACE);
278207614Simp				goto abort;
279207614Simp			}
280207614Simp		}
281207614Simp
282207614Simpsend_ack:
283207614Simp		for (i = 0; ; i++) {
284207614Simp			n_ack = send_ack(peer, *block);
285207614Simp			if (n_ack > 0) {
286207614Simp
287207614Simp				if (i == maxtimeouts) {
288207614Simp					tftp_log(LOG_ERR,
289207614Simp					    "Cannot send ACK packet #%d, "
290207614Simp					    "giving up", *block);
291207614Simp					return;
292207614Simp				}
293207614Simp
294207614Simp				tftp_log(LOG_ERR,
295207614Simp				    "Cannot send ACK packet #%d, trying again",
296207614Simp				    *block);
297207614Simp				continue;
298207614Simp			}
299207614Simp
300207614Simp			break;
301207614Simp		}
302207614Simp		gettimeofday(&(ts->tstop), NULL);
303207614Simp	} while (n_data == segsize);
304207614Simp
305207614Simp	/* Don't do late packet management for the client implementation */
306207614Simp	if (acting_as_client)
307207614Simp		return;
308207614Simp
309207614Simp	for (i = 0; ; i++) {
310207614Simp		n_data = receive_packet(peer, (char *)rp, pktsize,
311207614Simp		    NULL, timeoutpacket);
312207614Simp		if (n_data <= 0)
313207614Simp			break;
314207614Simp		if (n_data > 0 &&
315207614Simp		    rp->th_opcode == DATA &&	/* and got a data block */
316207614Simp		    *block == rp->th_block)	/* then my last ack was lost */
317207614Simp			send_ack(peer, *block);	/* resend final ack */
318207614Simp	}
319207614Simp
320207614Simpabort:
321207614Simp	return;
322207614Simp}
323