1/* vi: set sw=4 ts=4: */
2/*
3 * ftpget
4 *
5 * Mini implementation of FTP to retrieve a remote file.
6 *
7 * Copyright (C) 2002 Jeff Angielski, The PTR Group <jeff@theptrgroup.com>
8 * Copyright (C) 2002 Glenn McGrath <bug1@iinet.net.au>
9 *
10 * Based on wget.c by Chip Rosenthal Covad Communications
11 * <chip@laserlink.net>
12 *
13 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
14 */
15
16#include <getopt.h>
17#include "libbb.h"
18
19typedef struct ftp_host_info_s {
20	const char *user;
21	const char *password;
22	struct len_and_sockaddr *lsa;
23} ftp_host_info_t;
24
25static smallint verbose_flag;
26static smallint do_continue;
27
28static void ftp_die(const char *msg, const char *remote) ATTRIBUTE_NORETURN;
29static void ftp_die(const char *msg, const char *remote)
30{
31	/* Guard against garbage from remote server */
32	const char *cp = remote;
33	while (*cp >= ' ' && *cp < '\x7f') cp++;
34	bb_error_msg_and_die("unexpected server response%s%s: %.*s",
35			msg ? " to " : "", msg ? msg : "",
36			(int)(cp - remote), remote);
37}
38
39
40static int ftpcmd(const char *s1, const char *s2, FILE *stream, char *buf)
41{
42	unsigned n;
43	if (verbose_flag) {
44		bb_error_msg("cmd %s %s", s1, s2);
45	}
46
47	if (s1) {
48		if (s2) {
49			fprintf(stream, "%s %s\r\n", s1, s2);
50		} else {
51			fprintf(stream, "%s\r\n", s1);
52		}
53	}
54	do {
55		char *buf_ptr;
56
57		if (fgets(buf, 510, stream) == NULL) {
58			bb_perror_msg_and_die("fgets");
59		}
60		buf_ptr = strstr(buf, "\r\n");
61		if (buf_ptr) {
62			*buf_ptr = '\0';
63		}
64	} while (!isdigit(buf[0]) || buf[3] != ' ');
65
66	buf[3] = '\0';
67	n = xatou(buf);
68	buf[3] = ' ';
69	return n;
70}
71
72static int xconnect_ftpdata(ftp_host_info_t *server, char *buf)
73{
74	char *buf_ptr;
75	unsigned short port_num;
76
77	/* Response is "NNN garbageN1,N2,N3,N4,P1,P2[)garbage]
78	 * Server's IP is N1.N2.N3.N4 (we ignore it)
79	 * Server's port for data connection is P1*256+P2 */
80	buf_ptr = strrchr(buf, ')');
81	if (buf_ptr) *buf_ptr = '\0';
82
83	buf_ptr = strrchr(buf, ',');
84	*buf_ptr = '\0';
85	port_num = xatoul_range(buf_ptr + 1, 0, 255);
86
87	buf_ptr = strrchr(buf, ',');
88	*buf_ptr = '\0';
89	port_num += xatoul_range(buf_ptr + 1, 0, 255) * 256;
90
91	set_nport(server->lsa, htons(port_num));
92	return xconnect_stream(server->lsa);
93}
94
95static FILE *ftp_login(ftp_host_info_t *server)
96{
97	FILE *control_stream;
98	char buf[512];
99
100	/* Connect to the command socket */
101	control_stream = fdopen(xconnect_stream(server->lsa), "r+");
102	if (control_stream == NULL) {
103		/* fdopen failed - extremely unlikely */
104		bb_perror_nomsg_and_die();
105	}
106
107	if (ftpcmd(NULL, NULL, control_stream, buf) != 220) {
108		ftp_die(NULL, buf);
109	}
110
111	/*  Login to the server */
112	switch (ftpcmd("USER", server->user, control_stream, buf)) {
113	case 230:
114		break;
115	case 331:
116		if (ftpcmd("PASS", server->password, control_stream, buf) != 230) {
117			ftp_die("PASS", buf);
118		}
119		break;
120	default:
121		ftp_die("USER", buf);
122	}
123
124	ftpcmd("TYPE I", NULL, control_stream, buf);
125
126	return control_stream;
127}
128
129#if !ENABLE_FTPGET
130int ftp_receive(ftp_host_info_t *server, FILE *control_stream,
131		const char *local_path, char *server_path);
132#else
133static
134int ftp_receive(ftp_host_info_t *server, FILE *control_stream,
135		const char *local_path, char *server_path)
136{
137	char buf[512];
138/* I think 'filesize' usage here is bogus. Let's see... */
139	//off_t filesize = -1;
140#define filesize ((off_t)-1)
141	int fd_data;
142	int fd_local = -1;
143	off_t beg_range = 0;
144
145	/* Connect to the data socket */
146	if (ftpcmd("PASV", NULL, control_stream, buf) != 227) {
147		ftp_die("PASV", buf);
148	}
149	fd_data = xconnect_ftpdata(server, buf);
150
151	if (ftpcmd("SIZE", server_path, control_stream, buf) == 213) {
152		//filesize = BB_STRTOOFF(buf + 4, NULL, 10);
153		//if (errno || filesize < 0)
154		//	ftp_die("SIZE", buf);
155	} else {
156		do_continue = 0;
157	}
158
159	if (LONE_DASH(local_path)) {
160		fd_local = STDOUT_FILENO;
161		do_continue = 0;
162	}
163
164	if (do_continue) {
165		struct stat sbuf;
166		if (lstat(local_path, &sbuf) < 0) {
167			bb_perror_msg_and_die("lstat");
168		}
169		if (sbuf.st_size > 0) {
170			beg_range = sbuf.st_size;
171		} else {
172			do_continue = 0;
173		}
174	}
175
176	if (do_continue) {
177		sprintf(buf, "REST %"OFF_FMT"d", beg_range);
178		if (ftpcmd(buf, NULL, control_stream, buf) != 350) {
179			do_continue = 0;
180		} else {
181			//if (filesize != -1)
182			//	filesize -= beg_range;
183		}
184	}
185
186	if (ftpcmd("RETR", server_path, control_stream, buf) > 150) {
187		ftp_die("RETR", buf);
188	}
189
190	/* only make a local file if we know that one exists on the remote server */
191	if (fd_local == -1) {
192		if (do_continue) {
193			fd_local = xopen(local_path, O_APPEND | O_WRONLY);
194		} else {
195			fd_local = xopen(local_path, O_CREAT | O_TRUNC | O_WRONLY);
196		}
197	}
198
199	/* Copy the file */
200	if (filesize != -1) {
201		if (bb_copyfd_size(fd_data, fd_local, filesize) == -1)
202			return EXIT_FAILURE;
203	} else {
204		if (bb_copyfd_eof(fd_data, fd_local) == -1)
205			return EXIT_FAILURE;
206	}
207
208	/* close it all down */
209	close(fd_data);
210	if (ftpcmd(NULL, NULL, control_stream, buf) != 226) {
211		ftp_die(NULL, buf);
212	}
213	ftpcmd("QUIT", NULL, control_stream, buf);
214
215	return EXIT_SUCCESS;
216}
217#endif
218
219#if !ENABLE_FTPPUT
220int ftp_send(ftp_host_info_t *server, FILE *control_stream,
221		const char *server_path, char *local_path);
222#else
223static
224int ftp_send(ftp_host_info_t *server, FILE *control_stream,
225		const char *server_path, char *local_path)
226{
227	struct stat sbuf;
228	char buf[512];
229	int fd_data;
230	int fd_local;
231	int response;
232
233	/*  Connect to the data socket */
234	if (ftpcmd("PASV", NULL, control_stream, buf) != 227) {
235		ftp_die("PASV", buf);
236	}
237	fd_data = xconnect_ftpdata(server, buf);
238
239	/* get the local file */
240	fd_local = STDIN_FILENO;
241	if (NOT_LONE_DASH(local_path)) {
242		fd_local = xopen(local_path, O_RDONLY);
243		fstat(fd_local, &sbuf);
244
245		sprintf(buf, "ALLO %"OFF_FMT"u", sbuf.st_size);
246		response = ftpcmd(buf, NULL, control_stream, buf);
247		switch (response) {
248		case 200:
249		case 202:
250			break;
251		default:
252			close(fd_local);
253			ftp_die("ALLO", buf);
254			break;
255		}
256	}
257	response = ftpcmd("STOR", server_path, control_stream, buf);
258	switch (response) {
259	case 125:
260	case 150:
261		break;
262	default:
263		close(fd_local);
264		ftp_die("STOR", buf);
265	}
266
267	/* transfer the file  */
268	if (bb_copyfd_eof(fd_local, fd_data) == -1) {
269		exit(EXIT_FAILURE);
270	}
271
272	/* close it all down */
273	close(fd_data);
274	if (ftpcmd(NULL, NULL, control_stream, buf) != 226) {
275		ftp_die("close", buf);
276	}
277	ftpcmd("QUIT", NULL, control_stream, buf);
278
279	return EXIT_SUCCESS;
280}
281#endif
282
283#define FTPGETPUT_OPT_CONTINUE	1
284#define FTPGETPUT_OPT_VERBOSE	2
285#define FTPGETPUT_OPT_USER	4
286#define FTPGETPUT_OPT_PASSWORD	8
287#define FTPGETPUT_OPT_PORT	16
288
289#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS
290static const char ftpgetput_longopts[] ALIGN1 =
291	"continue\0" Required_argument "c"
292	"verbose\0"  No_argument       "v"
293	"username\0" Required_argument "u"
294	"password\0" Required_argument "p"
295	"port\0"     Required_argument "P"
296	;
297#endif
298
299int ftpgetput_main(int argc, char **argv);
300int ftpgetput_main(int argc, char **argv)
301{
302	/* content-length of the file */
303	unsigned opt;
304	const char *port = "ftp";
305	/* socket to ftp server */
306	FILE *control_stream;
307	/* continue previous transfer (-c) */
308	ftp_host_info_t *server;
309
310#if ENABLE_FTPPUT && !ENABLE_FTPGET
311# define ftp_action ftp_send
312#elif ENABLE_FTPGET && !ENABLE_FTPPUT
313# define ftp_action ftp_receive
314#else
315	int (*ftp_action)(ftp_host_info_t *, FILE *, const char *, char *) = ftp_send;
316	/* Check to see if the command is ftpget or ftput */
317	if (applet_name[3] == 'g') {
318		ftp_action = ftp_receive;
319	}
320#endif
321
322	/* Set default values */
323	server = xmalloc(sizeof(*server));
324	server->user = "anonymous";
325	server->password = "busybox@";
326
327	/*
328	 * Decipher the command line
329	 */
330#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS
331	applet_long_options = ftpgetput_longopts;
332#endif
333	opt_complementary = "=3"; /* must have 3 params */
334	opt = getopt32(argv, "cvu:p:P:", &server->user, &server->password, &port);
335	argv += optind;
336
337	/* Process the non-option command line arguments */
338	if (opt & FTPGETPUT_OPT_CONTINUE) {
339		do_continue = 1;
340	}
341	if (opt & FTPGETPUT_OPT_VERBOSE) {
342		verbose_flag = 1;
343	}
344
345	/* We want to do exactly _one_ DNS lookup, since some
346	 * sites (i.e. ftp.us.debian.org) use round-robin DNS
347	 * and we want to connect to only one IP... */
348	server->lsa = xhost2sockaddr(argv[0], bb_lookup_port(port, "tcp", 21));
349	if (verbose_flag) {
350		printf("Connecting to %s (%s)\n", argv[0],
351			xmalloc_sockaddr2dotted(&server->lsa->sa));
352	}
353
354	/*  Connect/Setup/Configure the FTP session */
355	control_stream = ftp_login(server);
356
357	return ftp_action(server, control_stream, argv[1], argv[2]);
358}
359