1cc361f65SGavin Atkinson /* $NetBSD: fetch.c,v 1.18 2009/11/15 10:12:37 lukem Exp $ */
2cc361f65SGavin Atkinson /* from NetBSD: fetch.c,v 1.191 2009/08/17 09:08:16 christos Exp */
3f982db4aSGavin Atkinson
4f982db4aSGavin Atkinson /*-
5cc361f65SGavin Atkinson * Copyright (c) 1997-2009 The NetBSD Foundation, Inc.
6f982db4aSGavin Atkinson * All rights reserved.
7f982db4aSGavin Atkinson *
8f982db4aSGavin Atkinson * This code is derived from software contributed to The NetBSD Foundation
9f982db4aSGavin Atkinson * by Luke Mewburn.
10f982db4aSGavin Atkinson *
11f982db4aSGavin Atkinson * This code is derived from software contributed to The NetBSD Foundation
12f982db4aSGavin Atkinson * by Scott Aaron Bamford.
13f982db4aSGavin Atkinson *
14f982db4aSGavin Atkinson * Redistribution and use in source and binary forms, with or without
15f982db4aSGavin Atkinson * modification, are permitted provided that the following conditions
16f982db4aSGavin Atkinson * are met:
17f982db4aSGavin Atkinson * 1. Redistributions of source code must retain the above copyright
18f982db4aSGavin Atkinson * notice, this list of conditions and the following disclaimer.
19f982db4aSGavin Atkinson * 2. Redistributions in binary form must reproduce the above copyright
20f982db4aSGavin Atkinson * notice, this list of conditions and the following disclaimer in the
21f982db4aSGavin Atkinson * documentation and/or other materials provided with the distribution.
22f982db4aSGavin Atkinson *
23f982db4aSGavin Atkinson * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
24f982db4aSGavin Atkinson * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
25f982db4aSGavin Atkinson * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26f982db4aSGavin Atkinson * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
27f982db4aSGavin Atkinson * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28f982db4aSGavin Atkinson * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29f982db4aSGavin Atkinson * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30f982db4aSGavin Atkinson * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31f982db4aSGavin Atkinson * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32f982db4aSGavin Atkinson * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33f982db4aSGavin Atkinson * POSSIBILITY OF SUCH DAMAGE.
34f982db4aSGavin Atkinson */
35f982db4aSGavin Atkinson
36cc361f65SGavin Atkinson #include "tnftp.h"
37cc361f65SGavin Atkinson
38cc361f65SGavin Atkinson #if 0 /* tnftp */
39cc361f65SGavin Atkinson
40f982db4aSGavin Atkinson #include <sys/cdefs.h>
41f982db4aSGavin Atkinson #ifndef lint
42cc361f65SGavin Atkinson __RCSID(" NetBSD: fetch.c,v 1.191 2009/08/17 09:08:16 christos Exp ");
43f982db4aSGavin Atkinson #endif /* not lint */
44f982db4aSGavin Atkinson
45f982db4aSGavin Atkinson /*
46f982db4aSGavin Atkinson * FTP User Program -- Command line file retrieval
47f982db4aSGavin Atkinson */
48f982db4aSGavin Atkinson
49f982db4aSGavin Atkinson #include <sys/types.h>
50f982db4aSGavin Atkinson #include <sys/param.h>
51f982db4aSGavin Atkinson #include <sys/socket.h>
52f982db4aSGavin Atkinson #include <sys/stat.h>
53f982db4aSGavin Atkinson #include <sys/time.h>
54f982db4aSGavin Atkinson
55f982db4aSGavin Atkinson #include <netinet/in.h>
56f982db4aSGavin Atkinson
57f982db4aSGavin Atkinson #include <arpa/ftp.h>
58f982db4aSGavin Atkinson #include <arpa/inet.h>
59f982db4aSGavin Atkinson
60f982db4aSGavin Atkinson #include <ctype.h>
61f982db4aSGavin Atkinson #include <err.h>
62f982db4aSGavin Atkinson #include <errno.h>
63f982db4aSGavin Atkinson #include <netdb.h>
64f982db4aSGavin Atkinson #include <fcntl.h>
65f982db4aSGavin Atkinson #include <stdio.h>
66f982db4aSGavin Atkinson #include <stdlib.h>
67f982db4aSGavin Atkinson #include <string.h>
68f982db4aSGavin Atkinson #include <unistd.h>
69f982db4aSGavin Atkinson #include <time.h>
70cc361f65SGavin Atkinson
71cc361f65SGavin Atkinson #endif /* tnftp */
72f982db4aSGavin Atkinson
73f982db4aSGavin Atkinson #include "ftp_var.h"
74f982db4aSGavin Atkinson #include "version.h"
75f982db4aSGavin Atkinson
76f982db4aSGavin Atkinson typedef enum {
77f982db4aSGavin Atkinson UNKNOWN_URL_T=-1,
78f982db4aSGavin Atkinson HTTP_URL_T,
79f982db4aSGavin Atkinson FTP_URL_T,
80f982db4aSGavin Atkinson FILE_URL_T,
81f982db4aSGavin Atkinson CLASSIC_URL_T
82f982db4aSGavin Atkinson } url_t;
83f982db4aSGavin Atkinson
84f982db4aSGavin Atkinson void aborthttp(int);
85f982db4aSGavin Atkinson #ifndef NO_AUTH
86f982db4aSGavin Atkinson static int auth_url(const char *, char **, const char *, const char *);
87f982db4aSGavin Atkinson static void base64_encode(const unsigned char *, size_t, unsigned char *);
88f982db4aSGavin Atkinson #endif
89f982db4aSGavin Atkinson static int go_fetch(const char *);
90f982db4aSGavin Atkinson static int fetch_ftp(const char *);
91f982db4aSGavin Atkinson static int fetch_url(const char *, const char *, char *, char *);
92f982db4aSGavin Atkinson static const char *match_token(const char **, const char *);
93f982db4aSGavin Atkinson static int parse_url(const char *, const char *, url_t *, char **,
94f982db4aSGavin Atkinson char **, char **, char **, in_port_t *, char **);
95f982db4aSGavin Atkinson static void url_decode(char *);
96f982db4aSGavin Atkinson
97f982db4aSGavin Atkinson static int redirect_loop;
98f982db4aSGavin Atkinson
99f982db4aSGavin Atkinson
100f982db4aSGavin Atkinson #define STRNEQUAL(a,b) (strncasecmp((a), (b), sizeof((b))-1) == 0)
101f982db4aSGavin Atkinson #define ISLWS(x) ((x)=='\r' || (x)=='\n' || (x)==' ' || (x)=='\t')
102f982db4aSGavin Atkinson #define SKIPLWS(x) do { while (ISLWS((*x))) x++; } while (0)
103f982db4aSGavin Atkinson
104f982db4aSGavin Atkinson
105f982db4aSGavin Atkinson #define ABOUT_URL "about:" /* propaganda */
106f982db4aSGavin Atkinson #define FILE_URL "file://" /* file URL prefix */
107f982db4aSGavin Atkinson #define FTP_URL "ftp://" /* ftp URL prefix */
108f982db4aSGavin Atkinson #define HTTP_URL "http://" /* http URL prefix */
109f982db4aSGavin Atkinson
110f982db4aSGavin Atkinson
111f982db4aSGavin Atkinson /*
112f982db4aSGavin Atkinson * Determine if token is the next word in buf (case insensitive).
113f982db4aSGavin Atkinson * If so, advance buf past the token and any trailing LWS, and
114f982db4aSGavin Atkinson * return a pointer to the token (in buf). Otherwise, return NULL.
115cc361f65SGavin Atkinson * token may be preceded by LWS.
116f982db4aSGavin Atkinson * token must be followed by LWS or NUL. (I.e, don't partial match).
117f982db4aSGavin Atkinson */
118f982db4aSGavin Atkinson static const char *
match_token(const char ** buf,const char * token)119f982db4aSGavin Atkinson match_token(const char **buf, const char *token)
120f982db4aSGavin Atkinson {
121f982db4aSGavin Atkinson const char *p, *orig;
122f982db4aSGavin Atkinson size_t tlen;
123f982db4aSGavin Atkinson
124f982db4aSGavin Atkinson tlen = strlen(token);
125f982db4aSGavin Atkinson p = *buf;
126f982db4aSGavin Atkinson SKIPLWS(p);
127f982db4aSGavin Atkinson orig = p;
128f982db4aSGavin Atkinson if (strncasecmp(p, token, tlen) != 0)
129f982db4aSGavin Atkinson return NULL;
130f982db4aSGavin Atkinson p += tlen;
131f982db4aSGavin Atkinson if (*p != '\0' && !ISLWS(*p))
132f982db4aSGavin Atkinson return NULL;
133f982db4aSGavin Atkinson SKIPLWS(p);
134f982db4aSGavin Atkinson orig = *buf;
135f982db4aSGavin Atkinson *buf = p;
136f982db4aSGavin Atkinson return orig;
137f982db4aSGavin Atkinson }
138f982db4aSGavin Atkinson
139f982db4aSGavin Atkinson #ifndef NO_AUTH
140f982db4aSGavin Atkinson /*
141f982db4aSGavin Atkinson * Generate authorization response based on given authentication challenge.
142f982db4aSGavin Atkinson * Returns -1 if an error occurred, otherwise 0.
143f982db4aSGavin Atkinson * Sets response to a malloc(3)ed string; caller should free.
144f982db4aSGavin Atkinson */
145f982db4aSGavin Atkinson static int
auth_url(const char * challenge,char ** response,const char * guser,const char * gpass)146f982db4aSGavin Atkinson auth_url(const char *challenge, char **response, const char *guser,
147f982db4aSGavin Atkinson const char *gpass)
148f982db4aSGavin Atkinson {
149cc361f65SGavin Atkinson const char *cp, *scheme, *errormsg;
150f982db4aSGavin Atkinson char *ep, *clear, *realm;
151cc361f65SGavin Atkinson char uuser[BUFSIZ], *gotpass;
152cc361f65SGavin Atkinson const char *upass;
153f982db4aSGavin Atkinson int rval;
154f982db4aSGavin Atkinson size_t len, clen, rlen;
155f982db4aSGavin Atkinson
156f982db4aSGavin Atkinson *response = NULL;
157f982db4aSGavin Atkinson clear = realm = NULL;
158f982db4aSGavin Atkinson rval = -1;
159f982db4aSGavin Atkinson cp = challenge;
160f982db4aSGavin Atkinson scheme = "Basic"; /* only support Basic authentication */
161cc361f65SGavin Atkinson gotpass = NULL;
162f982db4aSGavin Atkinson
163cc361f65SGavin Atkinson DPRINTF("auth_url: challenge `%s'\n", challenge);
164f982db4aSGavin Atkinson
165f982db4aSGavin Atkinson if (! match_token(&cp, scheme)) {
166cc361f65SGavin Atkinson warnx("Unsupported authentication challenge `%s'",
167f982db4aSGavin Atkinson challenge);
168f982db4aSGavin Atkinson goto cleanup_auth_url;
169f982db4aSGavin Atkinson }
170f982db4aSGavin Atkinson
171f982db4aSGavin Atkinson #define REALM "realm=\""
172f982db4aSGavin Atkinson if (STRNEQUAL(cp, REALM))
173f982db4aSGavin Atkinson cp += sizeof(REALM) - 1;
174f982db4aSGavin Atkinson else {
175cc361f65SGavin Atkinson warnx("Unsupported authentication challenge `%s'",
176f982db4aSGavin Atkinson challenge);
177f982db4aSGavin Atkinson goto cleanup_auth_url;
178f982db4aSGavin Atkinson }
179f982db4aSGavin Atkinson /* XXX: need to improve quoted-string parsing to support \ quoting, etc. */
180f982db4aSGavin Atkinson if ((ep = strchr(cp, '\"')) != NULL) {
181cc361f65SGavin Atkinson len = ep - cp;
182cc361f65SGavin Atkinson realm = (char *)ftp_malloc(len + 1);
183f982db4aSGavin Atkinson (void)strlcpy(realm, cp, len + 1);
184f982db4aSGavin Atkinson } else {
185cc361f65SGavin Atkinson warnx("Unsupported authentication challenge `%s'",
186f982db4aSGavin Atkinson challenge);
187f982db4aSGavin Atkinson goto cleanup_auth_url;
188f982db4aSGavin Atkinson }
189f982db4aSGavin Atkinson
190f982db4aSGavin Atkinson fprintf(ttyout, "Username for `%s': ", realm);
191f982db4aSGavin Atkinson if (guser != NULL) {
192cc361f65SGavin Atkinson (void)strlcpy(uuser, guser, sizeof(uuser));
193cc361f65SGavin Atkinson fprintf(ttyout, "%s\n", uuser);
194f982db4aSGavin Atkinson } else {
195f982db4aSGavin Atkinson (void)fflush(ttyout);
196cc361f65SGavin Atkinson if (get_line(stdin, uuser, sizeof(uuser), &errormsg) < 0) {
197cc361f65SGavin Atkinson warnx("%s; can't authenticate", errormsg);
198f982db4aSGavin Atkinson goto cleanup_auth_url;
199f982db4aSGavin Atkinson }
200f982db4aSGavin Atkinson }
201f982db4aSGavin Atkinson if (gpass != NULL)
202cc361f65SGavin Atkinson upass = gpass;
203cc361f65SGavin Atkinson else {
204cc361f65SGavin Atkinson gotpass = getpass("Password: ");
205cc361f65SGavin Atkinson if (gotpass == NULL) {
206cc361f65SGavin Atkinson warnx("Can't read password");
207cc361f65SGavin Atkinson goto cleanup_auth_url;
208cc361f65SGavin Atkinson }
209cc361f65SGavin Atkinson upass = gotpass;
210cc361f65SGavin Atkinson }
211f982db4aSGavin Atkinson
212cc361f65SGavin Atkinson clen = strlen(uuser) + strlen(upass) + 2; /* user + ":" + pass + "\0" */
213cc361f65SGavin Atkinson clear = (char *)ftp_malloc(clen);
214cc361f65SGavin Atkinson (void)strlcpy(clear, uuser, clen);
215f982db4aSGavin Atkinson (void)strlcat(clear, ":", clen);
216cc361f65SGavin Atkinson (void)strlcat(clear, upass, clen);
217cc361f65SGavin Atkinson if (gotpass)
218cc361f65SGavin Atkinson memset(gotpass, 0, strlen(gotpass));
219f982db4aSGavin Atkinson
220f982db4aSGavin Atkinson /* scheme + " " + enc + "\0" */
221f982db4aSGavin Atkinson rlen = strlen(scheme) + 1 + (clen + 2) * 4 / 3 + 1;
222cc361f65SGavin Atkinson *response = (char *)ftp_malloc(rlen);
223f982db4aSGavin Atkinson (void)strlcpy(*response, scheme, rlen);
224f982db4aSGavin Atkinson len = strlcat(*response, " ", rlen);
225f982db4aSGavin Atkinson /* use `clen - 1' to not encode the trailing NUL */
226f982db4aSGavin Atkinson base64_encode((unsigned char *)clear, clen - 1,
227f982db4aSGavin Atkinson (unsigned char *)*response + len);
228f982db4aSGavin Atkinson memset(clear, 0, clen);
229f982db4aSGavin Atkinson rval = 0;
230f982db4aSGavin Atkinson
231f982db4aSGavin Atkinson cleanup_auth_url:
232f982db4aSGavin Atkinson FREEPTR(clear);
233f982db4aSGavin Atkinson FREEPTR(realm);
234f982db4aSGavin Atkinson return (rval);
235f982db4aSGavin Atkinson }
236f982db4aSGavin Atkinson
237f982db4aSGavin Atkinson /*
238f982db4aSGavin Atkinson * Encode len bytes starting at clear using base64 encoding into encoded,
239f982db4aSGavin Atkinson * which should be at least ((len + 2) * 4 / 3 + 1) in size.
240f982db4aSGavin Atkinson */
241f982db4aSGavin Atkinson static void
base64_encode(const unsigned char * clear,size_t len,unsigned char * encoded)242f982db4aSGavin Atkinson base64_encode(const unsigned char *clear, size_t len, unsigned char *encoded)
243f982db4aSGavin Atkinson {
244f982db4aSGavin Atkinson static const unsigned char enc[] =
245f982db4aSGavin Atkinson "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
246f982db4aSGavin Atkinson unsigned char *cp;
247cc361f65SGavin Atkinson size_t i;
248f982db4aSGavin Atkinson
249f982db4aSGavin Atkinson cp = encoded;
250f982db4aSGavin Atkinson for (i = 0; i < len; i += 3) {
251f982db4aSGavin Atkinson *(cp++) = enc[((clear[i + 0] >> 2))];
252f982db4aSGavin Atkinson *(cp++) = enc[((clear[i + 0] << 4) & 0x30)
253f982db4aSGavin Atkinson | ((clear[i + 1] >> 4) & 0x0f)];
254f982db4aSGavin Atkinson *(cp++) = enc[((clear[i + 1] << 2) & 0x3c)
255f982db4aSGavin Atkinson | ((clear[i + 2] >> 6) & 0x03)];
256f982db4aSGavin Atkinson *(cp++) = enc[((clear[i + 2] ) & 0x3f)];
257f982db4aSGavin Atkinson }
258f982db4aSGavin Atkinson *cp = '\0';
259f982db4aSGavin Atkinson while (i-- > len)
260f982db4aSGavin Atkinson *(--cp) = '=';
261f982db4aSGavin Atkinson }
262f982db4aSGavin Atkinson #endif
263f982db4aSGavin Atkinson
264f982db4aSGavin Atkinson /*
265f982db4aSGavin Atkinson * Decode %xx escapes in given string, `in-place'.
266f982db4aSGavin Atkinson */
267f982db4aSGavin Atkinson static void
url_decode(char * url)268f982db4aSGavin Atkinson url_decode(char *url)
269f982db4aSGavin Atkinson {
270f982db4aSGavin Atkinson unsigned char *p, *q;
271f982db4aSGavin Atkinson
272f982db4aSGavin Atkinson if (EMPTYSTRING(url))
273f982db4aSGavin Atkinson return;
274f982db4aSGavin Atkinson p = q = (unsigned char *)url;
275f982db4aSGavin Atkinson
276f982db4aSGavin Atkinson #define HEXTOINT(x) (x - (isdigit(x) ? '0' : (islower(x) ? 'a' : 'A') - 10))
277f982db4aSGavin Atkinson while (*p) {
278f982db4aSGavin Atkinson if (p[0] == '%'
279f982db4aSGavin Atkinson && p[1] && isxdigit((unsigned char)p[1])
280f982db4aSGavin Atkinson && p[2] && isxdigit((unsigned char)p[2])) {
281f982db4aSGavin Atkinson *q++ = HEXTOINT(p[1]) * 16 + HEXTOINT(p[2]);
282f982db4aSGavin Atkinson p+=3;
283f982db4aSGavin Atkinson } else
284f982db4aSGavin Atkinson *q++ = *p++;
285f982db4aSGavin Atkinson }
286f982db4aSGavin Atkinson *q = '\0';
287f982db4aSGavin Atkinson }
288f982db4aSGavin Atkinson
289f982db4aSGavin Atkinson
290f982db4aSGavin Atkinson /*
291cc361f65SGavin Atkinson * Parse URL of form (per RFC3986):
292f982db4aSGavin Atkinson * <type>://[<user>[:<password>]@]<host>[:<port>][/<path>]
293f982db4aSGavin Atkinson * Returns -1 if a parse error occurred, otherwise 0.
294f982db4aSGavin Atkinson * It's the caller's responsibility to url_decode() the returned
295f982db4aSGavin Atkinson * user, pass and path.
296f982db4aSGavin Atkinson *
297f982db4aSGavin Atkinson * Sets type to url_t, each of the given char ** pointers to a
298f982db4aSGavin Atkinson * malloc(3)ed strings of the relevant section, and port to
299f982db4aSGavin Atkinson * the number given, or ftpport if ftp://, or httpport if http://.
300f982db4aSGavin Atkinson *
301cc361f65SGavin Atkinson * XXX: this is not totally RFC3986 compliant; <path> will have the
302f982db4aSGavin Atkinson * leading `/' unless it's an ftp:// URL, as this makes things easier
303f982db4aSGavin Atkinson * for file:// and http:// URLs. ftp:// URLs have the `/' between the
304f982db4aSGavin Atkinson * host and the URL-path removed, but any additional leading slashes
305f982db4aSGavin Atkinson * in the URL-path are retained (because they imply that we should
306f982db4aSGavin Atkinson * later do "CWD" with a null argument).
307f982db4aSGavin Atkinson *
308f982db4aSGavin Atkinson * Examples:
309f982db4aSGavin Atkinson * input URL output path
310f982db4aSGavin Atkinson * --------- -----------
311cc361f65SGavin Atkinson * "http://host" "/"
312cc361f65SGavin Atkinson * "http://host/" "/"
313cc361f65SGavin Atkinson * "http://host/path" "/path"
314f982db4aSGavin Atkinson * "file://host/dir/file" "dir/file"
315cc361f65SGavin Atkinson * "ftp://host" ""
316f982db4aSGavin Atkinson * "ftp://host/" ""
317cc361f65SGavin Atkinson * "ftp://host//" "/"
318cc361f65SGavin Atkinson * "ftp://host/dir/file" "dir/file"
319f982db4aSGavin Atkinson * "ftp://host//dir/file" "/dir/file"
320f982db4aSGavin Atkinson */
321f982db4aSGavin Atkinson static int
parse_url(const char * url,const char * desc,url_t * utype,char ** uuser,char ** pass,char ** host,char ** port,in_port_t * portnum,char ** path)322cc361f65SGavin Atkinson parse_url(const char *url, const char *desc, url_t *utype,
323cc361f65SGavin Atkinson char **uuser, char **pass, char **host, char **port,
324f982db4aSGavin Atkinson in_port_t *portnum, char **path)
325f982db4aSGavin Atkinson {
326cc361f65SGavin Atkinson const char *origurl, *tport;
327cc361f65SGavin Atkinson char *cp, *ep, *thost;
328f982db4aSGavin Atkinson size_t len;
329f982db4aSGavin Atkinson
330cc361f65SGavin Atkinson if (url == NULL || desc == NULL || utype == NULL || uuser == NULL
331f982db4aSGavin Atkinson || pass == NULL || host == NULL || port == NULL || portnum == NULL
332f982db4aSGavin Atkinson || path == NULL)
333f982db4aSGavin Atkinson errx(1, "parse_url: invoked with NULL argument!");
334cc361f65SGavin Atkinson DPRINTF("parse_url: %s `%s'\n", desc, url);
335f982db4aSGavin Atkinson
336f982db4aSGavin Atkinson origurl = url;
337cc361f65SGavin Atkinson *utype = UNKNOWN_URL_T;
338cc361f65SGavin Atkinson *uuser = *pass = *host = *port = *path = NULL;
339f982db4aSGavin Atkinson *portnum = 0;
340f982db4aSGavin Atkinson tport = NULL;
341f982db4aSGavin Atkinson
342f982db4aSGavin Atkinson if (STRNEQUAL(url, HTTP_URL)) {
343f982db4aSGavin Atkinson url += sizeof(HTTP_URL) - 1;
344cc361f65SGavin Atkinson *utype = HTTP_URL_T;
345f982db4aSGavin Atkinson *portnum = HTTP_PORT;
346f982db4aSGavin Atkinson tport = httpport;
347f982db4aSGavin Atkinson } else if (STRNEQUAL(url, FTP_URL)) {
348f982db4aSGavin Atkinson url += sizeof(FTP_URL) - 1;
349cc361f65SGavin Atkinson *utype = FTP_URL_T;
350f982db4aSGavin Atkinson *portnum = FTP_PORT;
351f982db4aSGavin Atkinson tport = ftpport;
352f982db4aSGavin Atkinson } else if (STRNEQUAL(url, FILE_URL)) {
353f982db4aSGavin Atkinson url += sizeof(FILE_URL) - 1;
354cc361f65SGavin Atkinson *utype = FILE_URL_T;
355f982db4aSGavin Atkinson } else {
356f982db4aSGavin Atkinson warnx("Invalid %s `%s'", desc, url);
357f982db4aSGavin Atkinson cleanup_parse_url:
358cc361f65SGavin Atkinson FREEPTR(*uuser);
359cc361f65SGavin Atkinson if (*pass != NULL)
360cc361f65SGavin Atkinson memset(*pass, 0, strlen(*pass));
361f982db4aSGavin Atkinson FREEPTR(*pass);
362f982db4aSGavin Atkinson FREEPTR(*host);
363f982db4aSGavin Atkinson FREEPTR(*port);
364f982db4aSGavin Atkinson FREEPTR(*path);
365f982db4aSGavin Atkinson return (-1);
366f982db4aSGavin Atkinson }
367f982db4aSGavin Atkinson
368f982db4aSGavin Atkinson if (*url == '\0')
369f982db4aSGavin Atkinson return (0);
370f982db4aSGavin Atkinson
371f982db4aSGavin Atkinson /* find [user[:pass]@]host[:port] */
372f982db4aSGavin Atkinson ep = strchr(url, '/');
373f982db4aSGavin Atkinson if (ep == NULL)
374cc361f65SGavin Atkinson thost = ftp_strdup(url);
375f982db4aSGavin Atkinson else {
376f982db4aSGavin Atkinson len = ep - url;
377cc361f65SGavin Atkinson thost = (char *)ftp_malloc(len + 1);
378f982db4aSGavin Atkinson (void)strlcpy(thost, url, len + 1);
379cc361f65SGavin Atkinson if (*utype == FTP_URL_T) /* skip first / for ftp URLs */
380f982db4aSGavin Atkinson ep++;
381cc361f65SGavin Atkinson *path = ftp_strdup(ep);
382f982db4aSGavin Atkinson }
383f982db4aSGavin Atkinson
384f982db4aSGavin Atkinson cp = strchr(thost, '@'); /* look for user[:pass]@ in URLs */
385f982db4aSGavin Atkinson if (cp != NULL) {
386cc361f65SGavin Atkinson if (*utype == FTP_URL_T)
387f982db4aSGavin Atkinson anonftp = 0; /* disable anonftp */
388cc361f65SGavin Atkinson *uuser = thost;
389f982db4aSGavin Atkinson *cp = '\0';
390cc361f65SGavin Atkinson thost = ftp_strdup(cp + 1);
391cc361f65SGavin Atkinson cp = strchr(*uuser, ':');
392f982db4aSGavin Atkinson if (cp != NULL) {
393f982db4aSGavin Atkinson *cp = '\0';
394cc361f65SGavin Atkinson *pass = ftp_strdup(cp + 1);
395f982db4aSGavin Atkinson }
396cc361f65SGavin Atkinson url_decode(*uuser);
397f982db4aSGavin Atkinson if (*pass)
398f982db4aSGavin Atkinson url_decode(*pass);
399f982db4aSGavin Atkinson }
400f982db4aSGavin Atkinson
401f982db4aSGavin Atkinson #ifdef INET6
402f982db4aSGavin Atkinson /*
403f982db4aSGavin Atkinson * Check if thost is an encoded IPv6 address, as per
404cc361f65SGavin Atkinson * RFC3986:
405f982db4aSGavin Atkinson * `[' ipv6-address ']'
406f982db4aSGavin Atkinson */
407f982db4aSGavin Atkinson if (*thost == '[') {
408f982db4aSGavin Atkinson cp = thost + 1;
409f982db4aSGavin Atkinson if ((ep = strchr(cp, ']')) == NULL ||
410f982db4aSGavin Atkinson (ep[1] != '\0' && ep[1] != ':')) {
411f982db4aSGavin Atkinson warnx("Invalid address `%s' in %s `%s'",
412f982db4aSGavin Atkinson thost, desc, origurl);
413f982db4aSGavin Atkinson goto cleanup_parse_url;
414f982db4aSGavin Atkinson }
415f982db4aSGavin Atkinson len = ep - cp; /* change `[xyz]' -> `xyz' */
416f982db4aSGavin Atkinson memmove(thost, thost + 1, len);
417f982db4aSGavin Atkinson thost[len] = '\0';
418f982db4aSGavin Atkinson if (! isipv6addr(thost)) {
419f982db4aSGavin Atkinson warnx("Invalid IPv6 address `%s' in %s `%s'",
420f982db4aSGavin Atkinson thost, desc, origurl);
421f982db4aSGavin Atkinson goto cleanup_parse_url;
422f982db4aSGavin Atkinson }
423f982db4aSGavin Atkinson cp = ep + 1;
424f982db4aSGavin Atkinson if (*cp == ':')
425f982db4aSGavin Atkinson cp++;
426f982db4aSGavin Atkinson else
427f982db4aSGavin Atkinson cp = NULL;
428f982db4aSGavin Atkinson } else
429f982db4aSGavin Atkinson #endif /* INET6 */
430f982db4aSGavin Atkinson if ((cp = strchr(thost, ':')) != NULL)
431f982db4aSGavin Atkinson *cp++ = '\0';
432f982db4aSGavin Atkinson *host = thost;
433f982db4aSGavin Atkinson
434f982db4aSGavin Atkinson /* look for [:port] */
435f982db4aSGavin Atkinson if (cp != NULL) {
436cc361f65SGavin Atkinson unsigned long nport;
437f982db4aSGavin Atkinson
438cc361f65SGavin Atkinson nport = strtoul(cp, &ep, 10);
439cc361f65SGavin Atkinson if (*cp == '\0' || *ep != '\0' ||
440cc361f65SGavin Atkinson nport < 1 || nport > MAX_IN_PORT_T) {
441f982db4aSGavin Atkinson warnx("Unknown port `%s' in %s `%s'",
442f982db4aSGavin Atkinson cp, desc, origurl);
443f982db4aSGavin Atkinson goto cleanup_parse_url;
444f982db4aSGavin Atkinson }
445f982db4aSGavin Atkinson *portnum = nport;
446f982db4aSGavin Atkinson tport = cp;
447f982db4aSGavin Atkinson }
448f982db4aSGavin Atkinson
449f982db4aSGavin Atkinson if (tport != NULL)
450cc361f65SGavin Atkinson *port = ftp_strdup(tport);
451cc361f65SGavin Atkinson if (*path == NULL) {
452cc361f65SGavin Atkinson const char *emptypath = "/";
453cc361f65SGavin Atkinson if (*utype == FTP_URL_T) /* skip first / for ftp URLs */
454cc361f65SGavin Atkinson emptypath++;
455cc361f65SGavin Atkinson *path = ftp_strdup(emptypath);
456cc361f65SGavin Atkinson }
457f982db4aSGavin Atkinson
458cc361f65SGavin Atkinson DPRINTF("parse_url: user `%s' pass `%s' host %s port %s(%d) "
459f982db4aSGavin Atkinson "path `%s'\n",
460cc361f65SGavin Atkinson STRorNULL(*uuser), STRorNULL(*pass),
461cc361f65SGavin Atkinson STRorNULL(*host), STRorNULL(*port),
462cc361f65SGavin Atkinson *portnum ? *portnum : -1, STRorNULL(*path));
463f982db4aSGavin Atkinson
464f982db4aSGavin Atkinson return (0);
465f982db4aSGavin Atkinson }
466f982db4aSGavin Atkinson
467f982db4aSGavin Atkinson sigjmp_buf httpabort;
468f982db4aSGavin Atkinson
469f982db4aSGavin Atkinson /*
470f982db4aSGavin Atkinson * Retrieve URL, via a proxy if necessary, using HTTP.
471f982db4aSGavin Atkinson * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or
472f982db4aSGavin Atkinson * http_proxy as appropriate.
473f982db4aSGavin Atkinson * Supports HTTP redirects.
474f982db4aSGavin Atkinson * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
475f982db4aSGavin Atkinson * is still open (e.g, ftp xfer with trailing /)
476f982db4aSGavin Atkinson */
477f982db4aSGavin Atkinson static int
fetch_url(const char * url,const char * proxyenv,char * proxyauth,char * wwwauth)478f982db4aSGavin Atkinson fetch_url(const char *url, const char *proxyenv, char *proxyauth, char *wwwauth)
479f982db4aSGavin Atkinson {
480f982db4aSGavin Atkinson struct addrinfo hints, *res, *res0 = NULL;
481f982db4aSGavin Atkinson int error;
482cc361f65SGavin Atkinson sigfunc volatile oldintr;
483cc361f65SGavin Atkinson sigfunc volatile oldintp;
484cc361f65SGavin Atkinson int volatile s;
485f982db4aSGavin Atkinson struct stat sb;
486cc361f65SGavin Atkinson int volatile ischunked;
487cc361f65SGavin Atkinson int volatile isproxy;
488cc361f65SGavin Atkinson int volatile rval;
489cc361f65SGavin Atkinson int volatile hcode;
490cc361f65SGavin Atkinson int len;
491cc361f65SGavin Atkinson size_t flen;
492f982db4aSGavin Atkinson static size_t bufsize;
493f982db4aSGavin Atkinson static char *xferbuf;
494f982db4aSGavin Atkinson const char *cp, *token;
495cc361f65SGavin Atkinson char *ep;
496cc361f65SGavin Atkinson char buf[FTPBUFLEN];
497cc361f65SGavin Atkinson const char *errormsg;
498cc361f65SGavin Atkinson char *volatile savefile;
499cc361f65SGavin Atkinson char *volatile auth;
500cc361f65SGavin Atkinson char *volatile location;
501cc361f65SGavin Atkinson char *volatile message;
502cc361f65SGavin Atkinson char *uuser, *pass, *host, *port, *path;
503cc361f65SGavin Atkinson char *volatile decodedpath;
504f982db4aSGavin Atkinson char *puser, *ppass, *useragent;
505f982db4aSGavin Atkinson off_t hashbytes, rangestart, rangeend, entitylen;
506cc361f65SGavin Atkinson int (*volatile closefunc)(FILE *);
507cc361f65SGavin Atkinson FILE *volatile fin;
508cc361f65SGavin Atkinson FILE *volatile fout;
509f982db4aSGavin Atkinson time_t mtime;
510f982db4aSGavin Atkinson url_t urltype;
511f982db4aSGavin Atkinson in_port_t portnum;
512f982db4aSGavin Atkinson
513cc361f65SGavin Atkinson DPRINTF("fetch_url: `%s' proxyenv `%s'\n", url, STRorNULL(proxyenv));
514cc361f65SGavin Atkinson
515f982db4aSGavin Atkinson oldintr = oldintp = NULL;
516f982db4aSGavin Atkinson closefunc = NULL;
517f982db4aSGavin Atkinson fin = fout = NULL;
518f982db4aSGavin Atkinson s = -1;
519cc361f65SGavin Atkinson savefile = NULL;
520f982db4aSGavin Atkinson auth = location = message = NULL;
521f982db4aSGavin Atkinson ischunked = isproxy = hcode = 0;
522f982db4aSGavin Atkinson rval = 1;
523cc361f65SGavin Atkinson uuser = pass = host = path = decodedpath = puser = ppass = NULL;
524f982db4aSGavin Atkinson
525cc361f65SGavin Atkinson if (parse_url(url, "URL", &urltype, &uuser, &pass, &host, &port,
526f982db4aSGavin Atkinson &portnum, &path) == -1)
527f982db4aSGavin Atkinson goto cleanup_fetch_url;
528f982db4aSGavin Atkinson
529f982db4aSGavin Atkinson if (urltype == FILE_URL_T && ! EMPTYSTRING(host)
530f982db4aSGavin Atkinson && strcasecmp(host, "localhost") != 0) {
531f982db4aSGavin Atkinson warnx("No support for non local file URL `%s'", url);
532f982db4aSGavin Atkinson goto cleanup_fetch_url;
533f982db4aSGavin Atkinson }
534f982db4aSGavin Atkinson
535f982db4aSGavin Atkinson if (EMPTYSTRING(path)) {
536f982db4aSGavin Atkinson if (urltype == FTP_URL_T) {
537f982db4aSGavin Atkinson rval = fetch_ftp(url);
538f982db4aSGavin Atkinson goto cleanup_fetch_url;
539f982db4aSGavin Atkinson }
540f982db4aSGavin Atkinson if (urltype != HTTP_URL_T || outfile == NULL) {
541f982db4aSGavin Atkinson warnx("Invalid URL (no file after host) `%s'", url);
542f982db4aSGavin Atkinson goto cleanup_fetch_url;
543f982db4aSGavin Atkinson }
544f982db4aSGavin Atkinson }
545f982db4aSGavin Atkinson
546cc361f65SGavin Atkinson decodedpath = ftp_strdup(path);
547f982db4aSGavin Atkinson url_decode(decodedpath);
548f982db4aSGavin Atkinson
549f982db4aSGavin Atkinson if (outfile)
550*bccb6d5aSDag-Erling Smørgrav savefile = outfile;
551f982db4aSGavin Atkinson else {
552f982db4aSGavin Atkinson cp = strrchr(decodedpath, '/'); /* find savefile */
553f982db4aSGavin Atkinson if (cp != NULL)
554cc361f65SGavin Atkinson savefile = ftp_strdup(cp + 1);
555f982db4aSGavin Atkinson else
556cc361f65SGavin Atkinson savefile = ftp_strdup(decodedpath);
557f982db4aSGavin Atkinson }
558cc361f65SGavin Atkinson DPRINTF("fetch_url: savefile `%s'\n", savefile);
559f982db4aSGavin Atkinson if (EMPTYSTRING(savefile)) {
560f982db4aSGavin Atkinson if (urltype == FTP_URL_T) {
561f982db4aSGavin Atkinson rval = fetch_ftp(url);
562f982db4aSGavin Atkinson goto cleanup_fetch_url;
563f982db4aSGavin Atkinson }
564cc361f65SGavin Atkinson warnx("No file after directory (you must specify an "
565f982db4aSGavin Atkinson "output file) `%s'", url);
566f982db4aSGavin Atkinson goto cleanup_fetch_url;
567f982db4aSGavin Atkinson }
568f982db4aSGavin Atkinson
569f982db4aSGavin Atkinson restart_point = 0;
570f982db4aSGavin Atkinson filesize = -1;
571f982db4aSGavin Atkinson rangestart = rangeend = entitylen = -1;
572f982db4aSGavin Atkinson mtime = -1;
573f982db4aSGavin Atkinson if (restartautofetch) {
574*bccb6d5aSDag-Erling Smørgrav if (stat(savefile, &sb) == 0)
575f982db4aSGavin Atkinson restart_point = sb.st_size;
576f982db4aSGavin Atkinson }
577f982db4aSGavin Atkinson if (urltype == FILE_URL_T) { /* file:// URLs */
578f982db4aSGavin Atkinson direction = "copied";
579f982db4aSGavin Atkinson fin = fopen(decodedpath, "r");
580f982db4aSGavin Atkinson if (fin == NULL) {
581cc361f65SGavin Atkinson warn("Can't open `%s'", decodedpath);
582f982db4aSGavin Atkinson goto cleanup_fetch_url;
583f982db4aSGavin Atkinson }
584f982db4aSGavin Atkinson if (fstat(fileno(fin), &sb) == 0) {
585f982db4aSGavin Atkinson mtime = sb.st_mtime;
586f982db4aSGavin Atkinson filesize = sb.st_size;
587f982db4aSGavin Atkinson }
588f982db4aSGavin Atkinson if (restart_point) {
589f982db4aSGavin Atkinson if (lseek(fileno(fin), restart_point, SEEK_SET) < 0) {
590cc361f65SGavin Atkinson warn("Can't seek to restart `%s'",
591f982db4aSGavin Atkinson decodedpath);
592f982db4aSGavin Atkinson goto cleanup_fetch_url;
593f982db4aSGavin Atkinson }
594f982db4aSGavin Atkinson }
595f982db4aSGavin Atkinson if (verbose) {
596f982db4aSGavin Atkinson fprintf(ttyout, "Copying %s", decodedpath);
597f982db4aSGavin Atkinson if (restart_point)
598f982db4aSGavin Atkinson fprintf(ttyout, " (restarting at " LLF ")",
599f982db4aSGavin Atkinson (LLT)restart_point);
600f982db4aSGavin Atkinson fputs("\n", ttyout);
601f982db4aSGavin Atkinson }
602f982db4aSGavin Atkinson } else { /* ftp:// or http:// URLs */
603cc361f65SGavin Atkinson const char *leading;
604f982db4aSGavin Atkinson int hasleading;
605f982db4aSGavin Atkinson
606f982db4aSGavin Atkinson if (proxyenv == NULL) {
607f982db4aSGavin Atkinson if (urltype == HTTP_URL_T)
608f982db4aSGavin Atkinson proxyenv = getoptionvalue("http_proxy");
609f982db4aSGavin Atkinson else if (urltype == FTP_URL_T)
610f982db4aSGavin Atkinson proxyenv = getoptionvalue("ftp_proxy");
611f982db4aSGavin Atkinson }
612f982db4aSGavin Atkinson direction = "retrieved";
613f982db4aSGavin Atkinson if (! EMPTYSTRING(proxyenv)) { /* use proxy */
614f982db4aSGavin Atkinson url_t purltype;
615f982db4aSGavin Atkinson char *phost, *ppath;
616f982db4aSGavin Atkinson char *pport, *no_proxy;
617cc361f65SGavin Atkinson in_port_t pportnum;
618f982db4aSGavin Atkinson
619f982db4aSGavin Atkinson isproxy = 1;
620f982db4aSGavin Atkinson
621f982db4aSGavin Atkinson /* check URL against list of no_proxied sites */
622f982db4aSGavin Atkinson no_proxy = getoptionvalue("no_proxy");
623f982db4aSGavin Atkinson if (! EMPTYSTRING(no_proxy)) {
624cc361f65SGavin Atkinson char *np, *np_copy, *np_iter;
625cc361f65SGavin Atkinson unsigned long np_port;
626f982db4aSGavin Atkinson size_t hlen, plen;
627f982db4aSGavin Atkinson
628cc361f65SGavin Atkinson np_iter = np_copy = ftp_strdup(no_proxy);
629f982db4aSGavin Atkinson hlen = strlen(host);
630cc361f65SGavin Atkinson while ((cp = strsep(&np_iter, " ,")) != NULL) {
631f982db4aSGavin Atkinson if (*cp == '\0')
632f982db4aSGavin Atkinson continue;
633f982db4aSGavin Atkinson if ((np = strrchr(cp, ':')) != NULL) {
634cc361f65SGavin Atkinson *np++ = '\0';
635cc361f65SGavin Atkinson np_port = strtoul(np, &ep, 10);
636cc361f65SGavin Atkinson if (*np == '\0' || *ep != '\0')
637f982db4aSGavin Atkinson continue;
638f982db4aSGavin Atkinson if (np_port != portnum)
639f982db4aSGavin Atkinson continue;
640f982db4aSGavin Atkinson }
641f982db4aSGavin Atkinson plen = strlen(cp);
642f982db4aSGavin Atkinson if (hlen < plen)
643f982db4aSGavin Atkinson continue;
644f982db4aSGavin Atkinson if (strncasecmp(host + hlen - plen,
645f982db4aSGavin Atkinson cp, plen) == 0) {
646f982db4aSGavin Atkinson isproxy = 0;
647f982db4aSGavin Atkinson break;
648f982db4aSGavin Atkinson }
649f982db4aSGavin Atkinson }
650f982db4aSGavin Atkinson FREEPTR(np_copy);
651f982db4aSGavin Atkinson if (isproxy == 0 && urltype == FTP_URL_T) {
652f982db4aSGavin Atkinson rval = fetch_ftp(url);
653f982db4aSGavin Atkinson goto cleanup_fetch_url;
654f982db4aSGavin Atkinson }
655f982db4aSGavin Atkinson }
656f982db4aSGavin Atkinson
657f982db4aSGavin Atkinson if (isproxy) {
658cc361f65SGavin Atkinson if (restart_point) {
659cc361f65SGavin Atkinson warnx("Can't restart via proxy URL `%s'",
660cc361f65SGavin Atkinson proxyenv);
661cc361f65SGavin Atkinson goto cleanup_fetch_url;
662cc361f65SGavin Atkinson }
663f982db4aSGavin Atkinson if (parse_url(proxyenv, "proxy URL", &purltype,
664cc361f65SGavin Atkinson &puser, &ppass, &phost, &pport, &pportnum,
665f982db4aSGavin Atkinson &ppath) == -1)
666f982db4aSGavin Atkinson goto cleanup_fetch_url;
667f982db4aSGavin Atkinson
668f982db4aSGavin Atkinson if ((purltype != HTTP_URL_T
669f982db4aSGavin Atkinson && purltype != FTP_URL_T) ||
670f982db4aSGavin Atkinson EMPTYSTRING(phost) ||
671f982db4aSGavin Atkinson (! EMPTYSTRING(ppath)
672f982db4aSGavin Atkinson && strcmp(ppath, "/") != 0)) {
673f982db4aSGavin Atkinson warnx("Malformed proxy URL `%s'",
674f982db4aSGavin Atkinson proxyenv);
675f982db4aSGavin Atkinson FREEPTR(phost);
676f982db4aSGavin Atkinson FREEPTR(pport);
677f982db4aSGavin Atkinson FREEPTR(ppath);
678f982db4aSGavin Atkinson goto cleanup_fetch_url;
679f982db4aSGavin Atkinson }
680f982db4aSGavin Atkinson if (isipv6addr(host) &&
681f982db4aSGavin Atkinson strchr(host, '%') != NULL) {
682f982db4aSGavin Atkinson warnx(
683f982db4aSGavin Atkinson "Scoped address notation `%s' disallowed via web proxy",
684f982db4aSGavin Atkinson host);
685f982db4aSGavin Atkinson FREEPTR(phost);
686f982db4aSGavin Atkinson FREEPTR(pport);
687f982db4aSGavin Atkinson FREEPTR(ppath);
688f982db4aSGavin Atkinson goto cleanup_fetch_url;
689f982db4aSGavin Atkinson }
690f982db4aSGavin Atkinson
691f982db4aSGavin Atkinson FREEPTR(host);
692f982db4aSGavin Atkinson host = phost;
693f982db4aSGavin Atkinson FREEPTR(port);
694f982db4aSGavin Atkinson port = pport;
695f982db4aSGavin Atkinson FREEPTR(path);
696cc361f65SGavin Atkinson path = ftp_strdup(url);
697f982db4aSGavin Atkinson FREEPTR(ppath);
698f982db4aSGavin Atkinson }
699f982db4aSGavin Atkinson } /* ! EMPTYSTRING(proxyenv) */
700f982db4aSGavin Atkinson
701f982db4aSGavin Atkinson memset(&hints, 0, sizeof(hints));
702f982db4aSGavin Atkinson hints.ai_flags = 0;
703f982db4aSGavin Atkinson hints.ai_family = family;
704f982db4aSGavin Atkinson hints.ai_socktype = SOCK_STREAM;
705f982db4aSGavin Atkinson hints.ai_protocol = 0;
706cc361f65SGavin Atkinson error = getaddrinfo(host, port, &hints, &res0);
707f982db4aSGavin Atkinson if (error) {
708cc361f65SGavin Atkinson warnx("Can't lookup `%s:%s': %s", host, port,
709cc361f65SGavin Atkinson (error == EAI_SYSTEM) ? strerror(errno)
710cc361f65SGavin Atkinson : gai_strerror(error));
711f982db4aSGavin Atkinson goto cleanup_fetch_url;
712f982db4aSGavin Atkinson }
713f982db4aSGavin Atkinson if (res0->ai_canonname)
714f982db4aSGavin Atkinson host = res0->ai_canonname;
715f982db4aSGavin Atkinson
716f982db4aSGavin Atkinson s = -1;
717f982db4aSGavin Atkinson for (res = res0; res; res = res->ai_next) {
718cc361f65SGavin Atkinson char hname[NI_MAXHOST], sname[NI_MAXSERV];
719cc361f65SGavin Atkinson
720f982db4aSGavin Atkinson ai_unmapped(res);
721f982db4aSGavin Atkinson if (getnameinfo(res->ai_addr, res->ai_addrlen,
722cc361f65SGavin Atkinson hname, sizeof(hname), sname, sizeof(sname),
723cc361f65SGavin Atkinson NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
724cc361f65SGavin Atkinson strlcpy(hname, "?", sizeof(hname));
725cc361f65SGavin Atkinson strlcpy(sname, "?", sizeof(sname));
726cc361f65SGavin Atkinson }
727f982db4aSGavin Atkinson
728cc361f65SGavin Atkinson if (verbose && res0->ai_next) {
729cc361f65SGavin Atkinson fprintf(ttyout, "Trying %s:%s ...\n",
730cc361f65SGavin Atkinson hname, sname);
731cc361f65SGavin Atkinson }
732f982db4aSGavin Atkinson
733f982db4aSGavin Atkinson s = socket(res->ai_family, SOCK_STREAM,
734f982db4aSGavin Atkinson res->ai_protocol);
735f982db4aSGavin Atkinson if (s < 0) {
736cc361f65SGavin Atkinson warn(
737cc361f65SGavin Atkinson "Can't create socket for connection to "
738cc361f65SGavin Atkinson "`%s:%s'", hname, sname);
739f982db4aSGavin Atkinson continue;
740f982db4aSGavin Atkinson }
741f982db4aSGavin Atkinson
742cc361f65SGavin Atkinson if (ftp_connect(s, res->ai_addr, res->ai_addrlen) < 0) {
743f982db4aSGavin Atkinson close(s);
744f982db4aSGavin Atkinson s = -1;
745f982db4aSGavin Atkinson continue;
746f982db4aSGavin Atkinson }
747f982db4aSGavin Atkinson
748f982db4aSGavin Atkinson /* success */
749f982db4aSGavin Atkinson break;
750f982db4aSGavin Atkinson }
751f982db4aSGavin Atkinson
752f982db4aSGavin Atkinson if (s < 0) {
753cc361f65SGavin Atkinson warnx("Can't connect to `%s:%s'", host, port);
754f982db4aSGavin Atkinson goto cleanup_fetch_url;
755f982db4aSGavin Atkinson }
756f982db4aSGavin Atkinson
757f982db4aSGavin Atkinson fin = fdopen(s, "r+");
758f982db4aSGavin Atkinson /*
759f982db4aSGavin Atkinson * Construct and send the request.
760f982db4aSGavin Atkinson */
761f982db4aSGavin Atkinson if (verbose)
762f982db4aSGavin Atkinson fprintf(ttyout, "Requesting %s\n", url);
763f982db4aSGavin Atkinson leading = " (";
764f982db4aSGavin Atkinson hasleading = 0;
765f982db4aSGavin Atkinson if (isproxy) {
766f982db4aSGavin Atkinson if (verbose) {
767f982db4aSGavin Atkinson fprintf(ttyout, "%svia %s:%s", leading,
768f982db4aSGavin Atkinson host, port);
769f982db4aSGavin Atkinson leading = ", ";
770f982db4aSGavin Atkinson hasleading++;
771f982db4aSGavin Atkinson }
772f982db4aSGavin Atkinson fprintf(fin, "GET %s HTTP/1.0\r\n", path);
773f982db4aSGavin Atkinson if (flushcache)
774f982db4aSGavin Atkinson fprintf(fin, "Pragma: no-cache\r\n");
775f982db4aSGavin Atkinson } else {
776f982db4aSGavin Atkinson fprintf(fin, "GET %s HTTP/1.1\r\n", path);
777f982db4aSGavin Atkinson if (strchr(host, ':')) {
778f982db4aSGavin Atkinson char *h, *p;
779f982db4aSGavin Atkinson
780f982db4aSGavin Atkinson /*
781f982db4aSGavin Atkinson * strip off IPv6 scope identifier, since it is
782f982db4aSGavin Atkinson * local to the node
783f982db4aSGavin Atkinson */
784cc361f65SGavin Atkinson h = ftp_strdup(host);
785f982db4aSGavin Atkinson if (isipv6addr(h) &&
786f982db4aSGavin Atkinson (p = strchr(h, '%')) != NULL) {
787f982db4aSGavin Atkinson *p = '\0';
788f982db4aSGavin Atkinson }
789f982db4aSGavin Atkinson fprintf(fin, "Host: [%s]", h);
790f982db4aSGavin Atkinson free(h);
791f982db4aSGavin Atkinson } else
792f982db4aSGavin Atkinson fprintf(fin, "Host: %s", host);
793f982db4aSGavin Atkinson if (portnum != HTTP_PORT)
794f982db4aSGavin Atkinson fprintf(fin, ":%u", portnum);
795f982db4aSGavin Atkinson fprintf(fin, "\r\n");
796f982db4aSGavin Atkinson fprintf(fin, "Accept: */*\r\n");
797f982db4aSGavin Atkinson fprintf(fin, "Connection: close\r\n");
798f982db4aSGavin Atkinson if (restart_point) {
799f982db4aSGavin Atkinson fputs(leading, ttyout);
800f982db4aSGavin Atkinson fprintf(fin, "Range: bytes=" LLF "-\r\n",
801f982db4aSGavin Atkinson (LLT)restart_point);
802f982db4aSGavin Atkinson fprintf(ttyout, "restarting at " LLF,
803f982db4aSGavin Atkinson (LLT)restart_point);
804f982db4aSGavin Atkinson leading = ", ";
805f982db4aSGavin Atkinson hasleading++;
806f982db4aSGavin Atkinson }
807f982db4aSGavin Atkinson if (flushcache)
808f982db4aSGavin Atkinson fprintf(fin, "Cache-Control: no-cache\r\n");
809f982db4aSGavin Atkinson }
810f982db4aSGavin Atkinson if ((useragent=getenv("FTPUSERAGENT")) != NULL) {
811f982db4aSGavin Atkinson fprintf(fin, "User-Agent: %s\r\n", useragent);
812f982db4aSGavin Atkinson } else {
813f982db4aSGavin Atkinson fprintf(fin, "User-Agent: %s/%s\r\n",
814f982db4aSGavin Atkinson FTP_PRODUCT, FTP_VERSION);
815f982db4aSGavin Atkinson }
816f982db4aSGavin Atkinson if (wwwauth) {
817f982db4aSGavin Atkinson if (verbose) {
818f982db4aSGavin Atkinson fprintf(ttyout, "%swith authorization",
819f982db4aSGavin Atkinson leading);
820f982db4aSGavin Atkinson leading = ", ";
821f982db4aSGavin Atkinson hasleading++;
822f982db4aSGavin Atkinson }
823f982db4aSGavin Atkinson fprintf(fin, "Authorization: %s\r\n", wwwauth);
824f982db4aSGavin Atkinson }
825f982db4aSGavin Atkinson if (proxyauth) {
826f982db4aSGavin Atkinson if (verbose) {
827f982db4aSGavin Atkinson fprintf(ttyout,
828f982db4aSGavin Atkinson "%swith proxy authorization", leading);
829f982db4aSGavin Atkinson leading = ", ";
830f982db4aSGavin Atkinson hasleading++;
831f982db4aSGavin Atkinson }
832f982db4aSGavin Atkinson fprintf(fin, "Proxy-Authorization: %s\r\n", proxyauth);
833f982db4aSGavin Atkinson }
834f982db4aSGavin Atkinson if (verbose && hasleading)
835f982db4aSGavin Atkinson fputs(")\n", ttyout);
836f982db4aSGavin Atkinson fprintf(fin, "\r\n");
837f982db4aSGavin Atkinson if (fflush(fin) == EOF) {
838f982db4aSGavin Atkinson warn("Writing HTTP request");
839f982db4aSGavin Atkinson goto cleanup_fetch_url;
840f982db4aSGavin Atkinson }
841f982db4aSGavin Atkinson
842f982db4aSGavin Atkinson /* Read the response */
843cc361f65SGavin Atkinson len = get_line(fin, buf, sizeof(buf), &errormsg);
844cc361f65SGavin Atkinson if (len < 0) {
845cc361f65SGavin Atkinson if (*errormsg == '\n')
846cc361f65SGavin Atkinson errormsg++;
847cc361f65SGavin Atkinson warnx("Receiving HTTP reply: %s", errormsg);
848f982db4aSGavin Atkinson goto cleanup_fetch_url;
849f982db4aSGavin Atkinson }
850f982db4aSGavin Atkinson while (len > 0 && (ISLWS(buf[len-1])))
851f982db4aSGavin Atkinson buf[--len] = '\0';
852cc361f65SGavin Atkinson DPRINTF("fetch_url: received `%s'\n", buf);
853f982db4aSGavin Atkinson
854f982db4aSGavin Atkinson /* Determine HTTP response code */
855f982db4aSGavin Atkinson cp = strchr(buf, ' ');
856f982db4aSGavin Atkinson if (cp == NULL)
857f982db4aSGavin Atkinson goto improper;
858f982db4aSGavin Atkinson else
859f982db4aSGavin Atkinson cp++;
860f982db4aSGavin Atkinson hcode = strtol(cp, &ep, 10);
861f982db4aSGavin Atkinson if (*ep != '\0' && !isspace((unsigned char)*ep))
862f982db4aSGavin Atkinson goto improper;
863cc361f65SGavin Atkinson message = ftp_strdup(cp);
864f982db4aSGavin Atkinson
865f982db4aSGavin Atkinson /* Read the rest of the header. */
866f982db4aSGavin Atkinson while (1) {
867cc361f65SGavin Atkinson len = get_line(fin, buf, sizeof(buf), &errormsg);
868cc361f65SGavin Atkinson if (len < 0) {
869cc361f65SGavin Atkinson if (*errormsg == '\n')
870cc361f65SGavin Atkinson errormsg++;
871cc361f65SGavin Atkinson warnx("Receiving HTTP reply: %s", errormsg);
872f982db4aSGavin Atkinson goto cleanup_fetch_url;
873f982db4aSGavin Atkinson }
874f982db4aSGavin Atkinson while (len > 0 && (ISLWS(buf[len-1])))
875f982db4aSGavin Atkinson buf[--len] = '\0';
876f982db4aSGavin Atkinson if (len == 0)
877f982db4aSGavin Atkinson break;
878cc361f65SGavin Atkinson DPRINTF("fetch_url: received `%s'\n", buf);
879f982db4aSGavin Atkinson
880f982db4aSGavin Atkinson /*
881f982db4aSGavin Atkinson * Look for some headers
882f982db4aSGavin Atkinson */
883f982db4aSGavin Atkinson
884f982db4aSGavin Atkinson cp = buf;
885f982db4aSGavin Atkinson
886f982db4aSGavin Atkinson if (match_token(&cp, "Content-Length:")) {
887f982db4aSGavin Atkinson filesize = STRTOLL(cp, &ep, 10);
888f982db4aSGavin Atkinson if (filesize < 0 || *ep != '\0')
889f982db4aSGavin Atkinson goto improper;
890cc361f65SGavin Atkinson DPRINTF("fetch_url: parsed len as: " LLF "\n",
891f982db4aSGavin Atkinson (LLT)filesize);
892f982db4aSGavin Atkinson
893f982db4aSGavin Atkinson } else if (match_token(&cp, "Content-Range:")) {
894f982db4aSGavin Atkinson if (! match_token(&cp, "bytes"))
895f982db4aSGavin Atkinson goto improper;
896f982db4aSGavin Atkinson
897f982db4aSGavin Atkinson if (*cp == '*')
898f982db4aSGavin Atkinson cp++;
899f982db4aSGavin Atkinson else {
900f982db4aSGavin Atkinson rangestart = STRTOLL(cp, &ep, 10);
901f982db4aSGavin Atkinson if (rangestart < 0 || *ep != '-')
902f982db4aSGavin Atkinson goto improper;
903f982db4aSGavin Atkinson cp = ep + 1;
904f982db4aSGavin Atkinson rangeend = STRTOLL(cp, &ep, 10);
905f982db4aSGavin Atkinson if (rangeend < 0 || rangeend < rangestart)
906f982db4aSGavin Atkinson goto improper;
907f982db4aSGavin Atkinson cp = ep;
908f982db4aSGavin Atkinson }
909f982db4aSGavin Atkinson if (*cp != '/')
910f982db4aSGavin Atkinson goto improper;
911f982db4aSGavin Atkinson cp++;
912f982db4aSGavin Atkinson if (*cp == '*')
913f982db4aSGavin Atkinson cp++;
914f982db4aSGavin Atkinson else {
915f982db4aSGavin Atkinson entitylen = STRTOLL(cp, &ep, 10);
916f982db4aSGavin Atkinson if (entitylen < 0)
917f982db4aSGavin Atkinson goto improper;
918f982db4aSGavin Atkinson cp = ep;
919f982db4aSGavin Atkinson }
920f982db4aSGavin Atkinson if (*cp != '\0')
921f982db4aSGavin Atkinson goto improper;
922f982db4aSGavin Atkinson
923cc361f65SGavin Atkinson #ifndef NO_DEBUG
924cc361f65SGavin Atkinson if (ftp_debug) {
925f982db4aSGavin Atkinson fprintf(ttyout, "parsed range as: ");
926f982db4aSGavin Atkinson if (rangestart == -1)
927f982db4aSGavin Atkinson fprintf(ttyout, "*");
928f982db4aSGavin Atkinson else
929f982db4aSGavin Atkinson fprintf(ttyout, LLF "-" LLF,
930f982db4aSGavin Atkinson (LLT)rangestart,
931f982db4aSGavin Atkinson (LLT)rangeend);
932f982db4aSGavin Atkinson fprintf(ttyout, "/" LLF "\n", (LLT)entitylen);
933f982db4aSGavin Atkinson }
934cc361f65SGavin Atkinson #endif
935f982db4aSGavin Atkinson if (! restart_point) {
936f982db4aSGavin Atkinson warnx(
937f982db4aSGavin Atkinson "Received unexpected Content-Range header");
938f982db4aSGavin Atkinson goto cleanup_fetch_url;
939f982db4aSGavin Atkinson }
940f982db4aSGavin Atkinson
941f982db4aSGavin Atkinson } else if (match_token(&cp, "Last-Modified:")) {
942f982db4aSGavin Atkinson struct tm parsed;
943f982db4aSGavin Atkinson char *t;
944f982db4aSGavin Atkinson
945cc361f65SGavin Atkinson memset(&parsed, 0, sizeof(parsed));
946f982db4aSGavin Atkinson /* RFC1123 */
947f982db4aSGavin Atkinson if ((t = strptime(cp,
948f982db4aSGavin Atkinson "%a, %d %b %Y %H:%M:%S GMT",
949f982db4aSGavin Atkinson &parsed))
950cc361f65SGavin Atkinson /* RFC0850 */
951f982db4aSGavin Atkinson || (t = strptime(cp,
952f982db4aSGavin Atkinson "%a, %d-%b-%y %H:%M:%S GMT",
953f982db4aSGavin Atkinson &parsed))
954f982db4aSGavin Atkinson /* asctime */
955f982db4aSGavin Atkinson || (t = strptime(cp,
956f982db4aSGavin Atkinson "%a, %b %d %H:%M:%S %Y",
957f982db4aSGavin Atkinson &parsed))) {
958f982db4aSGavin Atkinson parsed.tm_isdst = -1;
959f982db4aSGavin Atkinson if (*t == '\0')
960f982db4aSGavin Atkinson mtime = timegm(&parsed);
961cc361f65SGavin Atkinson #ifndef NO_DEBUG
962cc361f65SGavin Atkinson if (ftp_debug && mtime != -1) {
963f982db4aSGavin Atkinson fprintf(ttyout,
964f982db4aSGavin Atkinson "parsed date as: %s",
965cc361f65SGavin Atkinson rfc2822time(localtime(&mtime)));
966f982db4aSGavin Atkinson }
967cc361f65SGavin Atkinson #endif
968f982db4aSGavin Atkinson }
969f982db4aSGavin Atkinson
970f982db4aSGavin Atkinson } else if (match_token(&cp, "Location:")) {
971cc361f65SGavin Atkinson location = ftp_strdup(cp);
972cc361f65SGavin Atkinson DPRINTF("fetch_url: parsed location as `%s'\n",
973cc361f65SGavin Atkinson cp);
974f982db4aSGavin Atkinson
975f982db4aSGavin Atkinson } else if (match_token(&cp, "Transfer-Encoding:")) {
976f982db4aSGavin Atkinson if (match_token(&cp, "binary")) {
977f982db4aSGavin Atkinson warnx(
978cc361f65SGavin Atkinson "Bogus transfer encoding `binary' (fetching anyway)");
979f982db4aSGavin Atkinson continue;
980f982db4aSGavin Atkinson }
981f982db4aSGavin Atkinson if (! (token = match_token(&cp, "chunked"))) {
982f982db4aSGavin Atkinson warnx(
983cc361f65SGavin Atkinson "Unsupported transfer encoding `%s'",
984f982db4aSGavin Atkinson token);
985f982db4aSGavin Atkinson goto cleanup_fetch_url;
986f982db4aSGavin Atkinson }
987f982db4aSGavin Atkinson ischunked++;
988cc361f65SGavin Atkinson DPRINTF("fetch_url: using chunked encoding\n");
989f982db4aSGavin Atkinson
990f982db4aSGavin Atkinson } else if (match_token(&cp, "Proxy-Authenticate:")
991f982db4aSGavin Atkinson || match_token(&cp, "WWW-Authenticate:")) {
992f982db4aSGavin Atkinson if (! (token = match_token(&cp, "Basic"))) {
993cc361f65SGavin Atkinson DPRINTF(
994cc361f65SGavin Atkinson "fetch_url: skipping unknown auth scheme `%s'\n",
995f982db4aSGavin Atkinson token);
996f982db4aSGavin Atkinson continue;
997f982db4aSGavin Atkinson }
998f982db4aSGavin Atkinson FREEPTR(auth);
999cc361f65SGavin Atkinson auth = ftp_strdup(token);
1000cc361f65SGavin Atkinson DPRINTF("fetch_url: parsed auth as `%s'\n", cp);
1001f982db4aSGavin Atkinson }
1002f982db4aSGavin Atkinson
1003f982db4aSGavin Atkinson }
1004f982db4aSGavin Atkinson /* finished parsing header */
1005f982db4aSGavin Atkinson
1006f982db4aSGavin Atkinson switch (hcode) {
1007f982db4aSGavin Atkinson case 200:
1008f982db4aSGavin Atkinson break;
1009f982db4aSGavin Atkinson case 206:
1010f982db4aSGavin Atkinson if (! restart_point) {
1011f982db4aSGavin Atkinson warnx("Not expecting partial content header");
1012f982db4aSGavin Atkinson goto cleanup_fetch_url;
1013f982db4aSGavin Atkinson }
1014f982db4aSGavin Atkinson break;
1015f982db4aSGavin Atkinson case 300:
1016f982db4aSGavin Atkinson case 301:
1017f982db4aSGavin Atkinson case 302:
1018f982db4aSGavin Atkinson case 303:
1019f982db4aSGavin Atkinson case 305:
1020cc361f65SGavin Atkinson case 307:
1021f982db4aSGavin Atkinson if (EMPTYSTRING(location)) {
1022f982db4aSGavin Atkinson warnx(
1023f982db4aSGavin Atkinson "No redirection Location provided by server");
1024f982db4aSGavin Atkinson goto cleanup_fetch_url;
1025f982db4aSGavin Atkinson }
1026f982db4aSGavin Atkinson if (redirect_loop++ > 5) {
1027f982db4aSGavin Atkinson warnx("Too many redirections requested");
1028f982db4aSGavin Atkinson goto cleanup_fetch_url;
1029f982db4aSGavin Atkinson }
1030f982db4aSGavin Atkinson if (hcode == 305) {
1031f982db4aSGavin Atkinson if (verbose)
1032f982db4aSGavin Atkinson fprintf(ttyout, "Redirected via %s\n",
1033f982db4aSGavin Atkinson location);
1034f982db4aSGavin Atkinson rval = fetch_url(url, location,
1035f982db4aSGavin Atkinson proxyauth, wwwauth);
1036f982db4aSGavin Atkinson } else {
1037f982db4aSGavin Atkinson if (verbose)
1038f982db4aSGavin Atkinson fprintf(ttyout, "Redirected to %s\n",
1039f982db4aSGavin Atkinson location);
1040f982db4aSGavin Atkinson rval = go_fetch(location);
1041f982db4aSGavin Atkinson }
1042f982db4aSGavin Atkinson goto cleanup_fetch_url;
1043f982db4aSGavin Atkinson #ifndef NO_AUTH
1044f982db4aSGavin Atkinson case 401:
1045f982db4aSGavin Atkinson case 407:
1046f982db4aSGavin Atkinson {
1047f982db4aSGavin Atkinson char **authp;
1048f982db4aSGavin Atkinson char *auser, *apass;
1049f982db4aSGavin Atkinson
1050f982db4aSGavin Atkinson if (hcode == 401) {
1051f982db4aSGavin Atkinson authp = &wwwauth;
1052cc361f65SGavin Atkinson auser = uuser;
1053f982db4aSGavin Atkinson apass = pass;
1054f982db4aSGavin Atkinson } else {
1055f982db4aSGavin Atkinson authp = &proxyauth;
1056f982db4aSGavin Atkinson auser = puser;
1057f982db4aSGavin Atkinson apass = ppass;
1058f982db4aSGavin Atkinson }
1059f982db4aSGavin Atkinson if (verbose || *authp == NULL ||
1060f982db4aSGavin Atkinson auser == NULL || apass == NULL)
1061f982db4aSGavin Atkinson fprintf(ttyout, "%s\n", message);
1062f982db4aSGavin Atkinson if (EMPTYSTRING(auth)) {
1063f982db4aSGavin Atkinson warnx(
1064f982db4aSGavin Atkinson "No authentication challenge provided by server");
1065f982db4aSGavin Atkinson goto cleanup_fetch_url;
1066f982db4aSGavin Atkinson }
1067f982db4aSGavin Atkinson if (*authp != NULL) {
1068f982db4aSGavin Atkinson char reply[10];
1069f982db4aSGavin Atkinson
1070f982db4aSGavin Atkinson fprintf(ttyout,
1071f982db4aSGavin Atkinson "Authorization failed. Retry (y/n)? ");
1072cc361f65SGavin Atkinson if (get_line(stdin, reply, sizeof(reply), NULL)
1073cc361f65SGavin Atkinson < 0) {
1074f982db4aSGavin Atkinson goto cleanup_fetch_url;
1075f982db4aSGavin Atkinson }
1076f982db4aSGavin Atkinson if (tolower((unsigned char)reply[0]) != 'y')
1077f982db4aSGavin Atkinson goto cleanup_fetch_url;
1078f982db4aSGavin Atkinson auser = NULL;
1079f982db4aSGavin Atkinson apass = NULL;
1080f982db4aSGavin Atkinson }
1081f982db4aSGavin Atkinson if (auth_url(auth, authp, auser, apass) == 0) {
1082f982db4aSGavin Atkinson rval = fetch_url(url, proxyenv,
1083f982db4aSGavin Atkinson proxyauth, wwwauth);
1084f982db4aSGavin Atkinson memset(*authp, 0, strlen(*authp));
1085f982db4aSGavin Atkinson FREEPTR(*authp);
1086f982db4aSGavin Atkinson }
1087f982db4aSGavin Atkinson goto cleanup_fetch_url;
1088f982db4aSGavin Atkinson }
1089f982db4aSGavin Atkinson #endif
1090f982db4aSGavin Atkinson default:
1091f982db4aSGavin Atkinson if (message)
1092cc361f65SGavin Atkinson warnx("Error retrieving file `%s'", message);
1093f982db4aSGavin Atkinson else
1094f982db4aSGavin Atkinson warnx("Unknown error retrieving file");
1095f982db4aSGavin Atkinson goto cleanup_fetch_url;
1096f982db4aSGavin Atkinson }
1097f982db4aSGavin Atkinson } /* end of ftp:// or http:// specific setup */
1098f982db4aSGavin Atkinson
1099f982db4aSGavin Atkinson /* Open the output file. */
1100*bccb6d5aSDag-Erling Smørgrav
1101*bccb6d5aSDag-Erling Smørgrav /*
1102*bccb6d5aSDag-Erling Smørgrav * Only trust filenames with special meaning if they came from
1103*bccb6d5aSDag-Erling Smørgrav * the command line
1104*bccb6d5aSDag-Erling Smørgrav */
1105*bccb6d5aSDag-Erling Smørgrav if (outfile == savefile) {
1106f982db4aSGavin Atkinson if (strcmp(savefile, "-") == 0) {
1107f982db4aSGavin Atkinson fout = stdout;
1108f982db4aSGavin Atkinson } else if (*savefile == '|') {
1109f982db4aSGavin Atkinson oldintp = xsignal(SIGPIPE, SIG_IGN);
1110f982db4aSGavin Atkinson fout = popen(savefile + 1, "w");
1111f982db4aSGavin Atkinson if (fout == NULL) {
1112cc361f65SGavin Atkinson warn("Can't execute `%s'", savefile + 1);
1113f982db4aSGavin Atkinson goto cleanup_fetch_url;
1114f982db4aSGavin Atkinson }
1115f982db4aSGavin Atkinson closefunc = pclose;
1116*bccb6d5aSDag-Erling Smørgrav }
1117*bccb6d5aSDag-Erling Smørgrav }
1118*bccb6d5aSDag-Erling Smørgrav if (fout == NULL) {
1119f982db4aSGavin Atkinson if ((rangeend != -1 && rangeend <= restart_point) ||
1120f982db4aSGavin Atkinson (rangestart == -1 && filesize != -1 && filesize <= restart_point)) {
1121f982db4aSGavin Atkinson /* already done */
1122f982db4aSGavin Atkinson if (verbose)
1123f982db4aSGavin Atkinson fprintf(ttyout, "already done\n");
1124f982db4aSGavin Atkinson rval = 0;
1125f982db4aSGavin Atkinson goto cleanup_fetch_url;
1126f982db4aSGavin Atkinson }
1127f982db4aSGavin Atkinson if (restart_point && rangestart != -1) {
1128f982db4aSGavin Atkinson if (entitylen != -1)
1129f982db4aSGavin Atkinson filesize = entitylen;
1130f982db4aSGavin Atkinson if (rangestart != restart_point) {
1131f982db4aSGavin Atkinson warnx(
1132f982db4aSGavin Atkinson "Size of `%s' differs from save file `%s'",
1133f982db4aSGavin Atkinson url, savefile);
1134f982db4aSGavin Atkinson goto cleanup_fetch_url;
1135f982db4aSGavin Atkinson }
1136f982db4aSGavin Atkinson fout = fopen(savefile, "a");
1137f982db4aSGavin Atkinson } else
1138f982db4aSGavin Atkinson fout = fopen(savefile, "w");
1139f982db4aSGavin Atkinson if (fout == NULL) {
1140f982db4aSGavin Atkinson warn("Can't open `%s'", savefile);
1141f982db4aSGavin Atkinson goto cleanup_fetch_url;
1142f982db4aSGavin Atkinson }
1143f982db4aSGavin Atkinson closefunc = fclose;
1144f982db4aSGavin Atkinson }
1145f982db4aSGavin Atkinson
1146f982db4aSGavin Atkinson /* Trap signals */
1147f982db4aSGavin Atkinson if (sigsetjmp(httpabort, 1))
1148f982db4aSGavin Atkinson goto cleanup_fetch_url;
1149f982db4aSGavin Atkinson (void)xsignal(SIGQUIT, psummary);
1150f982db4aSGavin Atkinson oldintr = xsignal(SIGINT, aborthttp);
1151f982db4aSGavin Atkinson
1152cc361f65SGavin Atkinson if ((size_t)rcvbuf_size > bufsize) {
1153f982db4aSGavin Atkinson if (xferbuf)
1154f982db4aSGavin Atkinson (void)free(xferbuf);
1155f982db4aSGavin Atkinson bufsize = rcvbuf_size;
1156cc361f65SGavin Atkinson xferbuf = ftp_malloc(bufsize);
1157f982db4aSGavin Atkinson }
1158f982db4aSGavin Atkinson
1159f982db4aSGavin Atkinson bytes = 0;
1160f982db4aSGavin Atkinson hashbytes = mark;
1161f982db4aSGavin Atkinson progressmeter(-1);
1162f982db4aSGavin Atkinson
1163f982db4aSGavin Atkinson /* Finally, suck down the file. */
1164f982db4aSGavin Atkinson do {
1165f982db4aSGavin Atkinson long chunksize;
1166cc361f65SGavin Atkinson short lastchunk;
1167f982db4aSGavin Atkinson
1168f982db4aSGavin Atkinson chunksize = 0;
1169cc361f65SGavin Atkinson lastchunk = 0;
1170cc361f65SGavin Atkinson /* read chunk-size */
1171f982db4aSGavin Atkinson if (ischunked) {
1172f982db4aSGavin Atkinson if (fgets(xferbuf, bufsize, fin) == NULL) {
1173cc361f65SGavin Atkinson warnx("Unexpected EOF reading chunk-size");
1174f982db4aSGavin Atkinson goto cleanup_fetch_url;
1175f982db4aSGavin Atkinson }
1176cc361f65SGavin Atkinson errno = 0;
1177f982db4aSGavin Atkinson chunksize = strtol(xferbuf, &ep, 16);
1178cc361f65SGavin Atkinson if (ep == xferbuf) {
1179cc361f65SGavin Atkinson warnx("Invalid chunk-size");
1180cc361f65SGavin Atkinson goto cleanup_fetch_url;
1181cc361f65SGavin Atkinson }
1182cc361f65SGavin Atkinson if (errno == ERANGE || chunksize < 0) {
1183cc361f65SGavin Atkinson errno = ERANGE;
1184cc361f65SGavin Atkinson warn("Chunk-size `%.*s'",
1185cc361f65SGavin Atkinson (int)(ep-xferbuf), xferbuf);
1186cc361f65SGavin Atkinson goto cleanup_fetch_url;
1187cc361f65SGavin Atkinson }
1188f982db4aSGavin Atkinson
1189f982db4aSGavin Atkinson /*
1190f982db4aSGavin Atkinson * XXX: Work around bug in Apache 1.3.9 and
1191f982db4aSGavin Atkinson * 1.3.11, which incorrectly put trailing
1192cc361f65SGavin Atkinson * space after the chunk-size.
1193f982db4aSGavin Atkinson */
1194f982db4aSGavin Atkinson while (*ep == ' ')
1195f982db4aSGavin Atkinson ep++;
1196f982db4aSGavin Atkinson
1197cc361f65SGavin Atkinson /* skip [ chunk-ext ] */
1198cc361f65SGavin Atkinson if (*ep == ';') {
1199cc361f65SGavin Atkinson while (*ep && *ep != '\r')
1200cc361f65SGavin Atkinson ep++;
1201cc361f65SGavin Atkinson }
1202cc361f65SGavin Atkinson
1203f982db4aSGavin Atkinson if (strcmp(ep, "\r\n") != 0) {
1204cc361f65SGavin Atkinson warnx("Unexpected data following chunk-size");
1205f982db4aSGavin Atkinson goto cleanup_fetch_url;
1206f982db4aSGavin Atkinson }
1207cc361f65SGavin Atkinson DPRINTF("fetch_url: got chunk-size of " LLF "\n",
1208f982db4aSGavin Atkinson (LLT)chunksize);
1209cc361f65SGavin Atkinson if (chunksize == 0) {
1210cc361f65SGavin Atkinson lastchunk = 1;
1211cc361f65SGavin Atkinson goto chunkdone;
1212cc361f65SGavin Atkinson }
1213f982db4aSGavin Atkinson }
1214f982db4aSGavin Atkinson /* transfer file or chunk */
1215f982db4aSGavin Atkinson while (1) {
1216f982db4aSGavin Atkinson struct timeval then, now, td;
1217f982db4aSGavin Atkinson off_t bufrem;
1218f982db4aSGavin Atkinson
1219f982db4aSGavin Atkinson if (rate_get)
1220f982db4aSGavin Atkinson (void)gettimeofday(&then, NULL);
1221cc361f65SGavin Atkinson bufrem = rate_get ? rate_get : (off_t)bufsize;
1222f982db4aSGavin Atkinson if (ischunked)
1223f982db4aSGavin Atkinson bufrem = MIN(chunksize, bufrem);
1224f982db4aSGavin Atkinson while (bufrem > 0) {
1225cc361f65SGavin Atkinson flen = fread(xferbuf, sizeof(char),
1226cc361f65SGavin Atkinson MIN((off_t)bufsize, bufrem), fin);
1227cc361f65SGavin Atkinson if (flen <= 0)
1228f982db4aSGavin Atkinson goto chunkdone;
1229cc361f65SGavin Atkinson bytes += flen;
1230cc361f65SGavin Atkinson bufrem -= flen;
1231cc361f65SGavin Atkinson if (fwrite(xferbuf, sizeof(char), flen, fout)
1232cc361f65SGavin Atkinson != flen) {
1233f982db4aSGavin Atkinson warn("Writing `%s'", savefile);
1234f982db4aSGavin Atkinson goto cleanup_fetch_url;
1235f982db4aSGavin Atkinson }
1236f982db4aSGavin Atkinson if (hash && !progress) {
1237f982db4aSGavin Atkinson while (bytes >= hashbytes) {
1238f982db4aSGavin Atkinson (void)putc('#', ttyout);
1239f982db4aSGavin Atkinson hashbytes += mark;
1240f982db4aSGavin Atkinson }
1241f982db4aSGavin Atkinson (void)fflush(ttyout);
1242f982db4aSGavin Atkinson }
1243f982db4aSGavin Atkinson if (ischunked) {
1244cc361f65SGavin Atkinson chunksize -= flen;
1245f982db4aSGavin Atkinson if (chunksize <= 0)
1246f982db4aSGavin Atkinson break;
1247f982db4aSGavin Atkinson }
1248f982db4aSGavin Atkinson }
1249f982db4aSGavin Atkinson if (rate_get) {
1250f982db4aSGavin Atkinson while (1) {
1251f982db4aSGavin Atkinson (void)gettimeofday(&now, NULL);
1252f982db4aSGavin Atkinson timersub(&now, &then, &td);
1253f982db4aSGavin Atkinson if (td.tv_sec > 0)
1254f982db4aSGavin Atkinson break;
1255f982db4aSGavin Atkinson usleep(1000000 - td.tv_usec);
1256f982db4aSGavin Atkinson }
1257f982db4aSGavin Atkinson }
1258f982db4aSGavin Atkinson if (ischunked && chunksize <= 0)
1259f982db4aSGavin Atkinson break;
1260f982db4aSGavin Atkinson }
1261f982db4aSGavin Atkinson /* read CRLF after chunk*/
1262f982db4aSGavin Atkinson chunkdone:
1263f982db4aSGavin Atkinson if (ischunked) {
1264cc361f65SGavin Atkinson if (fgets(xferbuf, bufsize, fin) == NULL) {
1265cc361f65SGavin Atkinson warnx("Unexpected EOF reading chunk CRLF");
1266cc361f65SGavin Atkinson goto cleanup_fetch_url;
1267cc361f65SGavin Atkinson }
1268f982db4aSGavin Atkinson if (strcmp(xferbuf, "\r\n") != 0) {
1269f982db4aSGavin Atkinson warnx("Unexpected data following chunk");
1270f982db4aSGavin Atkinson goto cleanup_fetch_url;
1271f982db4aSGavin Atkinson }
1272cc361f65SGavin Atkinson if (lastchunk)
1273cc361f65SGavin Atkinson break;
1274f982db4aSGavin Atkinson }
1275f982db4aSGavin Atkinson } while (ischunked);
1276cc361f65SGavin Atkinson
1277cc361f65SGavin Atkinson /* XXX: deal with optional trailer & CRLF here? */
1278cc361f65SGavin Atkinson
1279f982db4aSGavin Atkinson if (hash && !progress && bytes > 0) {
1280f982db4aSGavin Atkinson if (bytes < mark)
1281f982db4aSGavin Atkinson (void)putc('#', ttyout);
1282f982db4aSGavin Atkinson (void)putc('\n', ttyout);
1283f982db4aSGavin Atkinson }
1284f982db4aSGavin Atkinson if (ferror(fin)) {
1285f982db4aSGavin Atkinson warn("Reading file");
1286f982db4aSGavin Atkinson goto cleanup_fetch_url;
1287f982db4aSGavin Atkinson }
1288f982db4aSGavin Atkinson progressmeter(1);
1289f982db4aSGavin Atkinson (void)fflush(fout);
1290f982db4aSGavin Atkinson if (closefunc == fclose && mtime != -1) {
1291f982db4aSGavin Atkinson struct timeval tval[2];
1292f982db4aSGavin Atkinson
1293f982db4aSGavin Atkinson (void)gettimeofday(&tval[0], NULL);
1294f982db4aSGavin Atkinson tval[1].tv_sec = mtime;
1295f982db4aSGavin Atkinson tval[1].tv_usec = 0;
1296f982db4aSGavin Atkinson (*closefunc)(fout);
1297f982db4aSGavin Atkinson fout = NULL;
1298f982db4aSGavin Atkinson
1299f982db4aSGavin Atkinson if (utimes(savefile, tval) == -1) {
1300f982db4aSGavin Atkinson fprintf(ttyout,
1301f982db4aSGavin Atkinson "Can't change modification time to %s",
1302cc361f65SGavin Atkinson rfc2822time(localtime(&mtime)));
1303f982db4aSGavin Atkinson }
1304f982db4aSGavin Atkinson }
1305f982db4aSGavin Atkinson if (bytes > 0)
1306f982db4aSGavin Atkinson ptransfer(0);
1307f982db4aSGavin Atkinson bytes = 0;
1308f982db4aSGavin Atkinson
1309f982db4aSGavin Atkinson rval = 0;
1310f982db4aSGavin Atkinson goto cleanup_fetch_url;
1311f982db4aSGavin Atkinson
1312f982db4aSGavin Atkinson improper:
1313cc361f65SGavin Atkinson warnx("Improper response from `%s:%s'", host, port);
1314f982db4aSGavin Atkinson
1315f982db4aSGavin Atkinson cleanup_fetch_url:
1316f982db4aSGavin Atkinson if (oldintr)
1317f982db4aSGavin Atkinson (void)xsignal(SIGINT, oldintr);
1318f982db4aSGavin Atkinson if (oldintp)
1319f982db4aSGavin Atkinson (void)xsignal(SIGPIPE, oldintp);
1320f982db4aSGavin Atkinson if (fin != NULL)
1321f982db4aSGavin Atkinson fclose(fin);
1322f982db4aSGavin Atkinson else if (s != -1)
1323f982db4aSGavin Atkinson close(s);
1324f982db4aSGavin Atkinson if (closefunc != NULL && fout != NULL)
1325f982db4aSGavin Atkinson (*closefunc)(fout);
1326f982db4aSGavin Atkinson if (res0)
1327f982db4aSGavin Atkinson freeaddrinfo(res0);
1328*bccb6d5aSDag-Erling Smørgrav if (savefile != outfile)
1329f982db4aSGavin Atkinson FREEPTR(savefile);
1330cc361f65SGavin Atkinson FREEPTR(uuser);
1331cc361f65SGavin Atkinson if (pass != NULL)
1332cc361f65SGavin Atkinson memset(pass, 0, strlen(pass));
1333f982db4aSGavin Atkinson FREEPTR(pass);
1334f982db4aSGavin Atkinson FREEPTR(host);
1335f982db4aSGavin Atkinson FREEPTR(port);
1336f982db4aSGavin Atkinson FREEPTR(path);
1337f982db4aSGavin Atkinson FREEPTR(decodedpath);
1338f982db4aSGavin Atkinson FREEPTR(puser);
1339cc361f65SGavin Atkinson if (ppass != NULL)
1340cc361f65SGavin Atkinson memset(ppass, 0, strlen(ppass));
1341f982db4aSGavin Atkinson FREEPTR(ppass);
1342f982db4aSGavin Atkinson FREEPTR(auth);
1343f982db4aSGavin Atkinson FREEPTR(location);
1344f982db4aSGavin Atkinson FREEPTR(message);
1345f982db4aSGavin Atkinson return (rval);
1346f982db4aSGavin Atkinson }
1347f982db4aSGavin Atkinson
1348f982db4aSGavin Atkinson /*
1349f982db4aSGavin Atkinson * Abort a HTTP retrieval
1350f982db4aSGavin Atkinson */
1351f982db4aSGavin Atkinson void
aborthttp(int notused)1352f982db4aSGavin Atkinson aborthttp(int notused)
1353f982db4aSGavin Atkinson {
1354f982db4aSGavin Atkinson char msgbuf[100];
1355cc361f65SGavin Atkinson size_t len;
1356f982db4aSGavin Atkinson
1357f982db4aSGavin Atkinson sigint_raised = 1;
1358f982db4aSGavin Atkinson alarmtimer(0);
1359f982db4aSGavin Atkinson len = strlcpy(msgbuf, "\nHTTP fetch aborted.\n", sizeof(msgbuf));
1360f982db4aSGavin Atkinson write(fileno(ttyout), msgbuf, len);
1361f982db4aSGavin Atkinson siglongjmp(httpabort, 1);
1362f982db4aSGavin Atkinson }
1363f982db4aSGavin Atkinson
1364f982db4aSGavin Atkinson /*
1365f982db4aSGavin Atkinson * Retrieve ftp URL or classic ftp argument using FTP.
1366f982db4aSGavin Atkinson * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
1367f982db4aSGavin Atkinson * is still open (e.g, ftp xfer with trailing /)
1368f982db4aSGavin Atkinson */
1369f982db4aSGavin Atkinson static int
fetch_ftp(const char * url)1370f982db4aSGavin Atkinson fetch_ftp(const char *url)
1371f982db4aSGavin Atkinson {
1372f982db4aSGavin Atkinson char *cp, *xargv[5], rempath[MAXPATHLEN];
1373cc361f65SGavin Atkinson char *host, *path, *dir, *file, *uuser, *pass;
1374f982db4aSGavin Atkinson char *port;
1375cc361f65SGavin Atkinson char cmdbuf[MAXPATHLEN];
1376cc361f65SGavin Atkinson char dirbuf[4];
1377cc361f65SGavin Atkinson int dirhasglob, filehasglob, rval, transtype, xargc;
1378cc361f65SGavin Atkinson int oanonftp, oautologin;
1379f982db4aSGavin Atkinson in_port_t portnum;
1380f982db4aSGavin Atkinson url_t urltype;
1381f982db4aSGavin Atkinson
1382cc361f65SGavin Atkinson DPRINTF("fetch_ftp: `%s'\n", url);
1383cc361f65SGavin Atkinson host = path = dir = file = uuser = pass = NULL;
1384f982db4aSGavin Atkinson port = NULL;
1385f982db4aSGavin Atkinson rval = 1;
1386cc361f65SGavin Atkinson transtype = TYPE_I;
1387f982db4aSGavin Atkinson
1388f982db4aSGavin Atkinson if (STRNEQUAL(url, FTP_URL)) {
1389cc361f65SGavin Atkinson if ((parse_url(url, "URL", &urltype, &uuser, &pass,
1390f982db4aSGavin Atkinson &host, &port, &portnum, &path) == -1) ||
1391cc361f65SGavin Atkinson (uuser != NULL && *uuser == '\0') ||
1392f982db4aSGavin Atkinson EMPTYSTRING(host)) {
1393f982db4aSGavin Atkinson warnx("Invalid URL `%s'", url);
1394f982db4aSGavin Atkinson goto cleanup_fetch_ftp;
1395f982db4aSGavin Atkinson }
1396f982db4aSGavin Atkinson /*
1397f982db4aSGavin Atkinson * Note: Don't url_decode(path) here. We need to keep the
1398f982db4aSGavin Atkinson * distinction between "/" and "%2F" until later.
1399f982db4aSGavin Atkinson */
1400f982db4aSGavin Atkinson
1401f982db4aSGavin Atkinson /* check for trailing ';type=[aid]' */
1402f982db4aSGavin Atkinson if (! EMPTYSTRING(path) && (cp = strrchr(path, ';')) != NULL) {
1403f982db4aSGavin Atkinson if (strcasecmp(cp, ";type=a") == 0)
1404cc361f65SGavin Atkinson transtype = TYPE_A;
1405f982db4aSGavin Atkinson else if (strcasecmp(cp, ";type=i") == 0)
1406cc361f65SGavin Atkinson transtype = TYPE_I;
1407f982db4aSGavin Atkinson else if (strcasecmp(cp, ";type=d") == 0) {
1408f982db4aSGavin Atkinson warnx(
1409f982db4aSGavin Atkinson "Directory listing via a URL is not supported");
1410f982db4aSGavin Atkinson goto cleanup_fetch_ftp;
1411f982db4aSGavin Atkinson } else {
1412f982db4aSGavin Atkinson warnx("Invalid suffix `%s' in URL `%s'", cp,
1413f982db4aSGavin Atkinson url);
1414f982db4aSGavin Atkinson goto cleanup_fetch_ftp;
1415f982db4aSGavin Atkinson }
1416f982db4aSGavin Atkinson *cp = 0;
1417f982db4aSGavin Atkinson }
1418f982db4aSGavin Atkinson } else { /* classic style `[user@]host:[file]' */
1419f982db4aSGavin Atkinson urltype = CLASSIC_URL_T;
1420cc361f65SGavin Atkinson host = ftp_strdup(url);
1421f982db4aSGavin Atkinson cp = strchr(host, '@');
1422f982db4aSGavin Atkinson if (cp != NULL) {
1423f982db4aSGavin Atkinson *cp = '\0';
1424cc361f65SGavin Atkinson uuser = host;
1425f982db4aSGavin Atkinson anonftp = 0; /* disable anonftp */
1426cc361f65SGavin Atkinson host = ftp_strdup(cp + 1);
1427f982db4aSGavin Atkinson }
1428f982db4aSGavin Atkinson cp = strchr(host, ':');
1429f982db4aSGavin Atkinson if (cp != NULL) {
1430f982db4aSGavin Atkinson *cp = '\0';
1431cc361f65SGavin Atkinson path = ftp_strdup(cp + 1);
1432f982db4aSGavin Atkinson }
1433f982db4aSGavin Atkinson }
1434f982db4aSGavin Atkinson if (EMPTYSTRING(host))
1435f982db4aSGavin Atkinson goto cleanup_fetch_ftp;
1436f982db4aSGavin Atkinson
1437f982db4aSGavin Atkinson /* Extract the file and (if present) directory name. */
1438f982db4aSGavin Atkinson dir = path;
1439f982db4aSGavin Atkinson if (! EMPTYSTRING(dir)) {
1440f982db4aSGavin Atkinson /*
1441f982db4aSGavin Atkinson * If we are dealing with classic `[user@]host:[path]' syntax,
1442f982db4aSGavin Atkinson * then a path of the form `/file' (resulting from input of the
1443f982db4aSGavin Atkinson * form `host:/file') means that we should do "CWD /" before
1444f982db4aSGavin Atkinson * retrieving the file. So we set dir="/" and file="file".
1445f982db4aSGavin Atkinson *
1446f982db4aSGavin Atkinson * But if we are dealing with URLs like `ftp://host/path' then
1447f982db4aSGavin Atkinson * a path of the form `/file' (resulting from a URL of the form
1448f982db4aSGavin Atkinson * `ftp://host//file') means that we should do `CWD ' (with an
1449f982db4aSGavin Atkinson * empty argument) before retrieving the file. So we set
1450f982db4aSGavin Atkinson * dir="" and file="file".
1451f982db4aSGavin Atkinson *
1452f982db4aSGavin Atkinson * If the path does not contain / at all, we set dir=NULL.
1453f982db4aSGavin Atkinson * (We get a path without any slashes if we are dealing with
1454f982db4aSGavin Atkinson * classic `[user@]host:[file]' or URL `ftp://host/file'.)
1455f982db4aSGavin Atkinson *
1456f982db4aSGavin Atkinson * In all other cases, we set dir to a string that does not
1457f982db4aSGavin Atkinson * include the final '/' that separates the dir part from the
1458f982db4aSGavin Atkinson * file part of the path. (This will be the empty string if
1459f982db4aSGavin Atkinson * and only if we are dealing with a path of the form `/file'
1460f982db4aSGavin Atkinson * resulting from an URL of the form `ftp://host//file'.)
1461f982db4aSGavin Atkinson */
1462f982db4aSGavin Atkinson cp = strrchr(dir, '/');
1463f982db4aSGavin Atkinson if (cp == dir && urltype == CLASSIC_URL_T) {
1464f982db4aSGavin Atkinson file = cp + 1;
1465cc361f65SGavin Atkinson (void)strlcpy(dirbuf, "/", sizeof(dirbuf));
1466cc361f65SGavin Atkinson dir = dirbuf;
1467f982db4aSGavin Atkinson } else if (cp != NULL) {
1468f982db4aSGavin Atkinson *cp++ = '\0';
1469f982db4aSGavin Atkinson file = cp;
1470f982db4aSGavin Atkinson } else {
1471f982db4aSGavin Atkinson file = dir;
1472f982db4aSGavin Atkinson dir = NULL;
1473f982db4aSGavin Atkinson }
1474f982db4aSGavin Atkinson } else
1475f982db4aSGavin Atkinson dir = NULL;
1476f982db4aSGavin Atkinson if (urltype == FTP_URL_T && file != NULL) {
1477f982db4aSGavin Atkinson url_decode(file);
1478f982db4aSGavin Atkinson /* but still don't url_decode(dir) */
1479f982db4aSGavin Atkinson }
1480cc361f65SGavin Atkinson DPRINTF("fetch_ftp: user `%s' pass `%s' host %s port %s "
1481f982db4aSGavin Atkinson "path `%s' dir `%s' file `%s'\n",
1482cc361f65SGavin Atkinson STRorNULL(uuser), STRorNULL(pass),
1483cc361f65SGavin Atkinson STRorNULL(host), STRorNULL(port),
1484cc361f65SGavin Atkinson STRorNULL(path), STRorNULL(dir), STRorNULL(file));
1485f982db4aSGavin Atkinson
1486f982db4aSGavin Atkinson dirhasglob = filehasglob = 0;
1487f982db4aSGavin Atkinson if (doglob && urltype == CLASSIC_URL_T) {
1488f982db4aSGavin Atkinson if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL)
1489f982db4aSGavin Atkinson dirhasglob = 1;
1490f982db4aSGavin Atkinson if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL)
1491f982db4aSGavin Atkinson filehasglob = 1;
1492f982db4aSGavin Atkinson }
1493f982db4aSGavin Atkinson
1494f982db4aSGavin Atkinson /* Set up the connection */
1495cc361f65SGavin Atkinson oanonftp = anonftp;
1496f982db4aSGavin Atkinson if (connected)
1497f982db4aSGavin Atkinson disconnect(0, NULL);
1498cc361f65SGavin Atkinson anonftp = oanonftp;
1499cc361f65SGavin Atkinson (void)strlcpy(cmdbuf, getprogname(), sizeof(cmdbuf));
1500cc361f65SGavin Atkinson xargv[0] = cmdbuf;
1501f982db4aSGavin Atkinson xargv[1] = host;
1502f982db4aSGavin Atkinson xargv[2] = NULL;
1503f982db4aSGavin Atkinson xargc = 2;
1504f982db4aSGavin Atkinson if (port) {
1505f982db4aSGavin Atkinson xargv[2] = port;
1506f982db4aSGavin Atkinson xargv[3] = NULL;
1507f982db4aSGavin Atkinson xargc = 3;
1508f982db4aSGavin Atkinson }
1509f982db4aSGavin Atkinson oautologin = autologin;
1510f982db4aSGavin Atkinson /* don't autologin in setpeer(), use ftp_login() below */
1511f982db4aSGavin Atkinson autologin = 0;
1512f982db4aSGavin Atkinson setpeer(xargc, xargv);
1513f982db4aSGavin Atkinson autologin = oautologin;
1514f982db4aSGavin Atkinson if ((connected == 0) ||
1515cc361f65SGavin Atkinson (connected == 1 && !ftp_login(host, uuser, pass))) {
1516cc361f65SGavin Atkinson warnx("Can't connect or login to host `%s:%s'",
1517cc361f65SGavin Atkinson host, port ? port : "?");
1518f982db4aSGavin Atkinson goto cleanup_fetch_ftp;
1519f982db4aSGavin Atkinson }
1520f982db4aSGavin Atkinson
1521cc361f65SGavin Atkinson switch (transtype) {
1522f982db4aSGavin Atkinson case TYPE_A:
1523f982db4aSGavin Atkinson setascii(1, xargv);
1524f982db4aSGavin Atkinson break;
1525f982db4aSGavin Atkinson case TYPE_I:
1526f982db4aSGavin Atkinson setbinary(1, xargv);
1527f982db4aSGavin Atkinson break;
1528f982db4aSGavin Atkinson default:
1529cc361f65SGavin Atkinson errx(1, "fetch_ftp: unknown transfer type %d", transtype);
1530f982db4aSGavin Atkinson }
1531f982db4aSGavin Atkinson
1532f982db4aSGavin Atkinson /*
1533f982db4aSGavin Atkinson * Change directories, if necessary.
1534f982db4aSGavin Atkinson *
1535f982db4aSGavin Atkinson * Note: don't use EMPTYSTRING(dir) below, because
1536f982db4aSGavin Atkinson * dir=="" means something different from dir==NULL.
1537f982db4aSGavin Atkinson */
1538f982db4aSGavin Atkinson if (dir != NULL && !dirhasglob) {
1539f982db4aSGavin Atkinson char *nextpart;
1540f982db4aSGavin Atkinson
1541f982db4aSGavin Atkinson /*
1542f982db4aSGavin Atkinson * If we are dealing with a classic `[user@]host:[path]'
1543f982db4aSGavin Atkinson * (urltype is CLASSIC_URL_T) then we have a raw directory
1544f982db4aSGavin Atkinson * name (not encoded in any way) and we can change
1545f982db4aSGavin Atkinson * directories in one step.
1546f982db4aSGavin Atkinson *
1547f982db4aSGavin Atkinson * If we are dealing with an `ftp://host/path' URL
1548cc361f65SGavin Atkinson * (urltype is FTP_URL_T), then RFC3986 says we need to
1549f982db4aSGavin Atkinson * send a separate CWD command for each unescaped "/"
1550f982db4aSGavin Atkinson * in the path, and we have to interpret %hex escaping
1551f982db4aSGavin Atkinson * *after* we find the slashes. It's possible to get
1552f982db4aSGavin Atkinson * empty components here, (from multiple adjacent
1553cc361f65SGavin Atkinson * slashes in the path) and RFC3986 says that we should
1554f982db4aSGavin Atkinson * still do `CWD ' (with a null argument) in such cases.
1555f982db4aSGavin Atkinson *
1556f982db4aSGavin Atkinson * Many ftp servers don't support `CWD ', so if there's an
1557f982db4aSGavin Atkinson * error performing that command, bail out with a descriptive
1558f982db4aSGavin Atkinson * message.
1559f982db4aSGavin Atkinson *
1560f982db4aSGavin Atkinson * Examples:
1561f982db4aSGavin Atkinson *
1562f982db4aSGavin Atkinson * host: dir="", urltype=CLASSIC_URL_T
1563f982db4aSGavin Atkinson * logged in (to default directory)
1564f982db4aSGavin Atkinson * host:file dir=NULL, urltype=CLASSIC_URL_T
1565f982db4aSGavin Atkinson * "RETR file"
1566f982db4aSGavin Atkinson * host:dir/ dir="dir", urltype=CLASSIC_URL_T
1567f982db4aSGavin Atkinson * "CWD dir", logged in
1568f982db4aSGavin Atkinson * ftp://host/ dir="", urltype=FTP_URL_T
1569f982db4aSGavin Atkinson * logged in (to default directory)
1570f982db4aSGavin Atkinson * ftp://host/dir/ dir="dir", urltype=FTP_URL_T
1571f982db4aSGavin Atkinson * "CWD dir", logged in
1572f982db4aSGavin Atkinson * ftp://host/file dir=NULL, urltype=FTP_URL_T
1573f982db4aSGavin Atkinson * "RETR file"
1574f982db4aSGavin Atkinson * ftp://host//file dir="", urltype=FTP_URL_T
1575f982db4aSGavin Atkinson * "CWD ", "RETR file"
1576f982db4aSGavin Atkinson * host:/file dir="/", urltype=CLASSIC_URL_T
1577f982db4aSGavin Atkinson * "CWD /", "RETR file"
1578f982db4aSGavin Atkinson * ftp://host///file dir="/", urltype=FTP_URL_T
1579f982db4aSGavin Atkinson * "CWD ", "CWD ", "RETR file"
1580f982db4aSGavin Atkinson * ftp://host/%2F/file dir="%2F", urltype=FTP_URL_T
1581f982db4aSGavin Atkinson * "CWD /", "RETR file"
1582f982db4aSGavin Atkinson * ftp://host/foo/file dir="foo", urltype=FTP_URL_T
1583f982db4aSGavin Atkinson * "CWD foo", "RETR file"
1584f982db4aSGavin Atkinson * ftp://host/foo/bar/file dir="foo/bar"
1585f982db4aSGavin Atkinson * "CWD foo", "CWD bar", "RETR file"
1586f982db4aSGavin Atkinson * ftp://host//foo/bar/file dir="/foo/bar"
1587f982db4aSGavin Atkinson * "CWD ", "CWD foo", "CWD bar", "RETR file"
1588f982db4aSGavin Atkinson * ftp://host/foo//bar/file dir="foo//bar"
1589f982db4aSGavin Atkinson * "CWD foo", "CWD ", "CWD bar", "RETR file"
1590f982db4aSGavin Atkinson * ftp://host/%2F/foo/bar/file dir="%2F/foo/bar"
1591f982db4aSGavin Atkinson * "CWD /", "CWD foo", "CWD bar", "RETR file"
1592f982db4aSGavin Atkinson * ftp://host/%2Ffoo/bar/file dir="%2Ffoo/bar"
1593f982db4aSGavin Atkinson * "CWD /foo", "CWD bar", "RETR file"
1594f982db4aSGavin Atkinson * ftp://host/%2Ffoo%2Fbar/file dir="%2Ffoo%2Fbar"
1595f982db4aSGavin Atkinson * "CWD /foo/bar", "RETR file"
1596f982db4aSGavin Atkinson * ftp://host/%2Ffoo%2Fbar%2Ffile dir=NULL
1597f982db4aSGavin Atkinson * "RETR /foo/bar/file"
1598f982db4aSGavin Atkinson *
1599f982db4aSGavin Atkinson * Note that we don't need `dir' after this point.
1600f982db4aSGavin Atkinson */
1601f982db4aSGavin Atkinson do {
1602f982db4aSGavin Atkinson if (urltype == FTP_URL_T) {
1603f982db4aSGavin Atkinson nextpart = strchr(dir, '/');
1604f982db4aSGavin Atkinson if (nextpart) {
1605f982db4aSGavin Atkinson *nextpart = '\0';
1606f982db4aSGavin Atkinson nextpart++;
1607f982db4aSGavin Atkinson }
1608f982db4aSGavin Atkinson url_decode(dir);
1609f982db4aSGavin Atkinson } else
1610f982db4aSGavin Atkinson nextpart = NULL;
1611cc361f65SGavin Atkinson DPRINTF("fetch_ftp: dir `%s', nextpart `%s'\n",
1612cc361f65SGavin Atkinson STRorNULL(dir), STRorNULL(nextpart));
1613f982db4aSGavin Atkinson if (urltype == FTP_URL_T || *dir != '\0') {
1614cc361f65SGavin Atkinson (void)strlcpy(cmdbuf, "cd", sizeof(cmdbuf));
1615cc361f65SGavin Atkinson xargv[0] = cmdbuf;
1616f982db4aSGavin Atkinson xargv[1] = dir;
1617f982db4aSGavin Atkinson xargv[2] = NULL;
1618f982db4aSGavin Atkinson dirchange = 0;
1619f982db4aSGavin Atkinson cd(2, xargv);
1620f982db4aSGavin Atkinson if (! dirchange) {
1621f982db4aSGavin Atkinson if (*dir == '\0' && code == 500)
1622f982db4aSGavin Atkinson fprintf(stderr,
1623f982db4aSGavin Atkinson "\n"
1624f982db4aSGavin Atkinson "ftp: The `CWD ' command (without a directory), which is required by\n"
1625cc361f65SGavin Atkinson " RFC3986 to support the empty directory in the URL pathname (`//'),\n"
1626cc361f65SGavin Atkinson " conflicts with the server's conformance to RFC0959.\n"
1627f982db4aSGavin Atkinson " Try the same URL without the `//' in the URL pathname.\n"
1628f982db4aSGavin Atkinson "\n");
1629f982db4aSGavin Atkinson goto cleanup_fetch_ftp;
1630f982db4aSGavin Atkinson }
1631f982db4aSGavin Atkinson }
1632f982db4aSGavin Atkinson dir = nextpart;
1633f982db4aSGavin Atkinson } while (dir != NULL);
1634f982db4aSGavin Atkinson }
1635f982db4aSGavin Atkinson
1636f982db4aSGavin Atkinson if (EMPTYSTRING(file)) {
1637f982db4aSGavin Atkinson rval = -1;
1638f982db4aSGavin Atkinson goto cleanup_fetch_ftp;
1639f982db4aSGavin Atkinson }
1640f982db4aSGavin Atkinson
1641f982db4aSGavin Atkinson if (dirhasglob) {
1642f982db4aSGavin Atkinson (void)strlcpy(rempath, dir, sizeof(rempath));
1643f982db4aSGavin Atkinson (void)strlcat(rempath, "/", sizeof(rempath));
1644f982db4aSGavin Atkinson (void)strlcat(rempath, file, sizeof(rempath));
1645f982db4aSGavin Atkinson file = rempath;
1646f982db4aSGavin Atkinson }
1647f982db4aSGavin Atkinson
1648f982db4aSGavin Atkinson /* Fetch the file(s). */
1649f982db4aSGavin Atkinson xargc = 2;
1650cc361f65SGavin Atkinson (void)strlcpy(cmdbuf, "get", sizeof(cmdbuf));
1651cc361f65SGavin Atkinson xargv[0] = cmdbuf;
1652f982db4aSGavin Atkinson xargv[1] = file;
1653f982db4aSGavin Atkinson xargv[2] = NULL;
1654f982db4aSGavin Atkinson if (dirhasglob || filehasglob) {
1655f982db4aSGavin Atkinson int ointeractive;
1656f982db4aSGavin Atkinson
1657f982db4aSGavin Atkinson ointeractive = interactive;
1658f982db4aSGavin Atkinson interactive = 0;
1659f982db4aSGavin Atkinson if (restartautofetch)
1660cc361f65SGavin Atkinson (void)strlcpy(cmdbuf, "mreget", sizeof(cmdbuf));
1661f982db4aSGavin Atkinson else
1662cc361f65SGavin Atkinson (void)strlcpy(cmdbuf, "mget", sizeof(cmdbuf));
1663cc361f65SGavin Atkinson xargv[0] = cmdbuf;
1664f982db4aSGavin Atkinson mget(xargc, xargv);
1665f982db4aSGavin Atkinson interactive = ointeractive;
1666f982db4aSGavin Atkinson } else {
1667f982db4aSGavin Atkinson if (outfile == NULL) {
1668f982db4aSGavin Atkinson cp = strrchr(file, '/'); /* find savefile */
1669f982db4aSGavin Atkinson if (cp != NULL)
1670f982db4aSGavin Atkinson outfile = cp + 1;
1671f982db4aSGavin Atkinson else
1672f982db4aSGavin Atkinson outfile = file;
1673f982db4aSGavin Atkinson }
1674f982db4aSGavin Atkinson xargv[2] = (char *)outfile;
1675f982db4aSGavin Atkinson xargv[3] = NULL;
1676f982db4aSGavin Atkinson xargc++;
1677f982db4aSGavin Atkinson if (restartautofetch)
1678f982db4aSGavin Atkinson reget(xargc, xargv);
1679f982db4aSGavin Atkinson else
1680f982db4aSGavin Atkinson get(xargc, xargv);
1681f982db4aSGavin Atkinson }
1682f982db4aSGavin Atkinson
1683f982db4aSGavin Atkinson if ((code / 100) == COMPLETE)
1684f982db4aSGavin Atkinson rval = 0;
1685f982db4aSGavin Atkinson
1686f982db4aSGavin Atkinson cleanup_fetch_ftp:
1687cc361f65SGavin Atkinson FREEPTR(port);
1688f982db4aSGavin Atkinson FREEPTR(host);
1689f982db4aSGavin Atkinson FREEPTR(path);
1690cc361f65SGavin Atkinson FREEPTR(uuser);
1691cc361f65SGavin Atkinson if (pass)
1692cc361f65SGavin Atkinson memset(pass, 0, strlen(pass));
1693f982db4aSGavin Atkinson FREEPTR(pass);
1694f982db4aSGavin Atkinson return (rval);
1695f982db4aSGavin Atkinson }
1696f982db4aSGavin Atkinson
1697f982db4aSGavin Atkinson /*
1698f982db4aSGavin Atkinson * Retrieve the given file to outfile.
1699f982db4aSGavin Atkinson * Supports arguments of the form:
1700f982db4aSGavin Atkinson * "host:path", "ftp://host/path" if $ftpproxy, call fetch_url() else
1701f982db4aSGavin Atkinson * call fetch_ftp()
1702f982db4aSGavin Atkinson * "http://host/path" call fetch_url() to use HTTP
1703f982db4aSGavin Atkinson * "file:///path" call fetch_url() to copy
1704f982db4aSGavin Atkinson * "about:..." print a message
1705f982db4aSGavin Atkinson *
1706f982db4aSGavin Atkinson * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection
1707f982db4aSGavin Atkinson * is still open (e.g, ftp xfer with trailing /)
1708f982db4aSGavin Atkinson */
1709f982db4aSGavin Atkinson static int
go_fetch(const char * url)1710f982db4aSGavin Atkinson go_fetch(const char *url)
1711f982db4aSGavin Atkinson {
1712cc361f65SGavin Atkinson char *proxyenv;
1713f982db4aSGavin Atkinson
1714f982db4aSGavin Atkinson #ifndef NO_ABOUT
1715f982db4aSGavin Atkinson /*
1716f982db4aSGavin Atkinson * Check for about:*
1717f982db4aSGavin Atkinson */
1718f982db4aSGavin Atkinson if (STRNEQUAL(url, ABOUT_URL)) {
1719f982db4aSGavin Atkinson url += sizeof(ABOUT_URL) -1;
1720f982db4aSGavin Atkinson if (strcasecmp(url, "ftp") == 0 ||
1721f982db4aSGavin Atkinson strcasecmp(url, "tnftp") == 0) {
1722f982db4aSGavin Atkinson fputs(
1723f982db4aSGavin Atkinson "This version of ftp has been enhanced by Luke Mewburn <lukem@NetBSD.org>\n"
1724f982db4aSGavin Atkinson "for the NetBSD project. Execute `man ftp' for more details.\n", ttyout);
1725f982db4aSGavin Atkinson } else if (strcasecmp(url, "lukem") == 0) {
1726f982db4aSGavin Atkinson fputs(
1727f982db4aSGavin Atkinson "Luke Mewburn is the author of most of the enhancements in this ftp client.\n"
1728f982db4aSGavin Atkinson "Please email feedback to <lukem@NetBSD.org>.\n", ttyout);
1729f982db4aSGavin Atkinson } else if (strcasecmp(url, "netbsd") == 0) {
1730f982db4aSGavin Atkinson fputs(
1731f982db4aSGavin Atkinson "NetBSD is a freely available and redistributable UNIX-like operating system.\n"
1732f982db4aSGavin Atkinson "For more information, see http://www.NetBSD.org/\n", ttyout);
1733f982db4aSGavin Atkinson } else if (strcasecmp(url, "version") == 0) {
1734f982db4aSGavin Atkinson fprintf(ttyout, "Version: %s %s%s\n",
1735f982db4aSGavin Atkinson FTP_PRODUCT, FTP_VERSION,
1736f982db4aSGavin Atkinson #ifdef INET6
1737f982db4aSGavin Atkinson ""
1738f982db4aSGavin Atkinson #else
1739f982db4aSGavin Atkinson " (-IPv6)"
1740f982db4aSGavin Atkinson #endif
1741f982db4aSGavin Atkinson );
1742f982db4aSGavin Atkinson } else {
1743f982db4aSGavin Atkinson fprintf(ttyout, "`%s' is an interesting topic.\n", url);
1744f982db4aSGavin Atkinson }
1745f982db4aSGavin Atkinson fputs("\n", ttyout);
1746f982db4aSGavin Atkinson return (0);
1747f982db4aSGavin Atkinson }
1748f982db4aSGavin Atkinson #endif
1749f982db4aSGavin Atkinson
1750f982db4aSGavin Atkinson /*
1751f982db4aSGavin Atkinson * Check for file:// and http:// URLs.
1752f982db4aSGavin Atkinson */
1753f982db4aSGavin Atkinson if (STRNEQUAL(url, HTTP_URL) || STRNEQUAL(url, FILE_URL))
1754f982db4aSGavin Atkinson return (fetch_url(url, NULL, NULL, NULL));
1755f982db4aSGavin Atkinson
1756f982db4aSGavin Atkinson /*
1757f982db4aSGavin Atkinson * Try FTP URL-style and host:file arguments next.
1758f982db4aSGavin Atkinson * If ftpproxy is set with an FTP URL, use fetch_url()
1759f982db4aSGavin Atkinson * Othewise, use fetch_ftp().
1760f982db4aSGavin Atkinson */
1761cc361f65SGavin Atkinson proxyenv = getoptionvalue("ftp_proxy");
1762cc361f65SGavin Atkinson if (!EMPTYSTRING(proxyenv) && STRNEQUAL(url, FTP_URL))
1763f982db4aSGavin Atkinson return (fetch_url(url, NULL, NULL, NULL));
1764f982db4aSGavin Atkinson
1765f982db4aSGavin Atkinson return (fetch_ftp(url));
1766f982db4aSGavin Atkinson }
1767f982db4aSGavin Atkinson
1768f982db4aSGavin Atkinson /*
1769f982db4aSGavin Atkinson * Retrieve multiple files from the command line,
1770f982db4aSGavin Atkinson * calling go_fetch() for each file.
1771f982db4aSGavin Atkinson *
1772f982db4aSGavin Atkinson * If an ftp path has a trailing "/", the path will be cd-ed into and
1773f982db4aSGavin Atkinson * the connection remains open, and the function will return -1
1774f982db4aSGavin Atkinson * (to indicate the connection is alive).
1775f982db4aSGavin Atkinson * If an error occurs the return value will be the offset+1 in
1776f982db4aSGavin Atkinson * argv[] of the file that caused a problem (i.e, argv[x]
1777f982db4aSGavin Atkinson * returns x+1)
1778f982db4aSGavin Atkinson * Otherwise, 0 is returned if all files retrieved successfully.
1779f982db4aSGavin Atkinson */
1780f982db4aSGavin Atkinson int
auto_fetch(int argc,char * argv[])1781f982db4aSGavin Atkinson auto_fetch(int argc, char *argv[])
1782f982db4aSGavin Atkinson {
1783cc361f65SGavin Atkinson volatile int argpos, rval;
1784f982db4aSGavin Atkinson
1785cc361f65SGavin Atkinson argpos = rval = 0;
1786f982db4aSGavin Atkinson
1787f982db4aSGavin Atkinson if (sigsetjmp(toplevel, 1)) {
1788f982db4aSGavin Atkinson if (connected)
1789f982db4aSGavin Atkinson disconnect(0, NULL);
1790f982db4aSGavin Atkinson if (rval > 0)
1791f982db4aSGavin Atkinson rval = argpos + 1;
1792f982db4aSGavin Atkinson return (rval);
1793f982db4aSGavin Atkinson }
1794f982db4aSGavin Atkinson (void)xsignal(SIGINT, intr);
1795f982db4aSGavin Atkinson (void)xsignal(SIGPIPE, lostpeer);
1796f982db4aSGavin Atkinson
1797f982db4aSGavin Atkinson /*
1798f982db4aSGavin Atkinson * Loop through as long as there's files to fetch.
1799f982db4aSGavin Atkinson */
1800cc361f65SGavin Atkinson for (; (rval == 0) && (argpos < argc); argpos++) {
1801f982db4aSGavin Atkinson if (strchr(argv[argpos], ':') == NULL)
1802f982db4aSGavin Atkinson break;
1803f982db4aSGavin Atkinson redirect_loop = 0;
1804f982db4aSGavin Atkinson if (!anonftp)
1805f982db4aSGavin Atkinson anonftp = 2; /* Handle "automatic" transfers. */
1806f982db4aSGavin Atkinson rval = go_fetch(argv[argpos]);
1807f982db4aSGavin Atkinson if (outfile != NULL && strcmp(outfile, "-") != 0
1808f982db4aSGavin Atkinson && outfile[0] != '|')
1809f982db4aSGavin Atkinson outfile = NULL;
1810f982db4aSGavin Atkinson if (rval > 0)
1811f982db4aSGavin Atkinson rval = argpos + 1;
1812f982db4aSGavin Atkinson }
1813f982db4aSGavin Atkinson
1814f982db4aSGavin Atkinson if (connected && rval != -1)
1815f982db4aSGavin Atkinson disconnect(0, NULL);
1816f982db4aSGavin Atkinson return (rval);
1817f982db4aSGavin Atkinson }
1818f982db4aSGavin Atkinson
1819f982db4aSGavin Atkinson
1820cc361f65SGavin Atkinson /*
1821cc361f65SGavin Atkinson * Upload multiple files from the command line.
1822cc361f65SGavin Atkinson *
1823cc361f65SGavin Atkinson * If an error occurs the return value will be the offset+1 in
1824cc361f65SGavin Atkinson * argv[] of the file that caused a problem (i.e, argv[x]
1825cc361f65SGavin Atkinson * returns x+1)
1826cc361f65SGavin Atkinson * Otherwise, 0 is returned if all files uploaded successfully.
1827cc361f65SGavin Atkinson */
1828f982db4aSGavin Atkinson int
auto_put(int argc,char ** argv,const char * uploadserver)1829f982db4aSGavin Atkinson auto_put(int argc, char **argv, const char *uploadserver)
1830f982db4aSGavin Atkinson {
1831f982db4aSGavin Atkinson char *uargv[4], *path, *pathsep;
1832cc361f65SGavin Atkinson int uargc, rval, argpos;
1833cc361f65SGavin Atkinson size_t len;
1834cc361f65SGavin Atkinson char cmdbuf[MAX_C_NAME];
1835f982db4aSGavin Atkinson
1836cc361f65SGavin Atkinson (void)strlcpy(cmdbuf, "mput", sizeof(cmdbuf));
1837cc361f65SGavin Atkinson uargv[0] = cmdbuf;
1838cc361f65SGavin Atkinson uargv[1] = argv[0];
1839cc361f65SGavin Atkinson uargc = 2;
1840f982db4aSGavin Atkinson uargv[2] = uargv[3] = NULL;
1841f982db4aSGavin Atkinson pathsep = NULL;
1842f982db4aSGavin Atkinson rval = 1;
1843f982db4aSGavin Atkinson
1844cc361f65SGavin Atkinson DPRINTF("auto_put: target `%s'\n", uploadserver);
1845f982db4aSGavin Atkinson
1846cc361f65SGavin Atkinson path = ftp_strdup(uploadserver);
1847f982db4aSGavin Atkinson len = strlen(path);
1848f982db4aSGavin Atkinson if (path[len - 1] != '/' && path[len - 1] != ':') {
1849f982db4aSGavin Atkinson /*
1850f982db4aSGavin Atkinson * make sure we always pass a directory to auto_fetch
1851f982db4aSGavin Atkinson */
1852f982db4aSGavin Atkinson if (argc > 1) { /* more than one file to upload */
1853f982db4aSGavin Atkinson len = strlen(uploadserver) + 2; /* path + "/" + "\0" */
1854f982db4aSGavin Atkinson free(path);
1855cc361f65SGavin Atkinson path = (char *)ftp_malloc(len);
1856f982db4aSGavin Atkinson (void)strlcpy(path, uploadserver, len);
1857f982db4aSGavin Atkinson (void)strlcat(path, "/", len);
1858f982db4aSGavin Atkinson } else { /* single file to upload */
1859cc361f65SGavin Atkinson (void)strlcpy(cmdbuf, "put", sizeof(cmdbuf));
1860cc361f65SGavin Atkinson uargv[0] = cmdbuf;
1861f982db4aSGavin Atkinson pathsep = strrchr(path, '/');
1862f982db4aSGavin Atkinson if (pathsep == NULL) {
1863f982db4aSGavin Atkinson pathsep = strrchr(path, ':');
1864f982db4aSGavin Atkinson if (pathsep == NULL) {
1865f982db4aSGavin Atkinson warnx("Invalid URL `%s'", path);
1866f982db4aSGavin Atkinson goto cleanup_auto_put;
1867f982db4aSGavin Atkinson }
1868f982db4aSGavin Atkinson pathsep++;
1869cc361f65SGavin Atkinson uargv[2] = ftp_strdup(pathsep);
1870f982db4aSGavin Atkinson pathsep[0] = '/';
1871f982db4aSGavin Atkinson } else
1872cc361f65SGavin Atkinson uargv[2] = ftp_strdup(pathsep + 1);
1873f982db4aSGavin Atkinson pathsep[1] = '\0';
1874f982db4aSGavin Atkinson uargc++;
1875f982db4aSGavin Atkinson }
1876f982db4aSGavin Atkinson }
1877cc361f65SGavin Atkinson DPRINTF("auto_put: URL `%s' argv[2] `%s'\n",
1878cc361f65SGavin Atkinson path, STRorNULL(uargv[2]));
1879f982db4aSGavin Atkinson
1880f982db4aSGavin Atkinson /* connect and cwd */
1881f982db4aSGavin Atkinson rval = auto_fetch(1, &path);
1882f982db4aSGavin Atkinson if(rval >= 0)
1883f982db4aSGavin Atkinson goto cleanup_auto_put;
1884f982db4aSGavin Atkinson
1885cc361f65SGavin Atkinson rval = 0;
1886cc361f65SGavin Atkinson
1887cc361f65SGavin Atkinson /* target filename provided; upload 1 file */
1888f982db4aSGavin Atkinson /* XXX : is this the best way? */
1889f982db4aSGavin Atkinson if (uargc == 3) {
1890f982db4aSGavin Atkinson uargv[1] = argv[0];
1891f982db4aSGavin Atkinson put(uargc, uargv);
1892cc361f65SGavin Atkinson if ((code / 100) != COMPLETE)
1893cc361f65SGavin Atkinson rval = 1;
1894cc361f65SGavin Atkinson } else { /* otherwise a target dir: upload all files to it */
1895cc361f65SGavin Atkinson for(argpos = 0; argv[argpos] != NULL; argpos++) {
1896cc361f65SGavin Atkinson uargv[1] = argv[argpos];
1897f982db4aSGavin Atkinson mput(uargc, uargv);
1898cc361f65SGavin Atkinson if ((code / 100) != COMPLETE) {
1899cc361f65SGavin Atkinson rval = argpos + 1;
1900cc361f65SGavin Atkinson break;
1901f982db4aSGavin Atkinson }
1902cc361f65SGavin Atkinson }
1903cc361f65SGavin Atkinson }
1904f982db4aSGavin Atkinson
1905f982db4aSGavin Atkinson cleanup_auto_put:
1906cc361f65SGavin Atkinson free(path);
1907f982db4aSGavin Atkinson FREEPTR(uargv[2]);
1908f982db4aSGavin Atkinson return (rval);
1909f982db4aSGavin Atkinson }
1910