1*8cc1b8faSsthen /* $OpenBSD: fetch.c,v 1.218 2024/04/23 08:50:38 sthen Exp $ */
2bfd817adSflorian /* $NetBSD: fetch.c,v 1.14 1997/08/18 10:20:20 lukem Exp $ */
3bfd817adSflorian
4bfd817adSflorian /*-
5bfd817adSflorian * Copyright (c) 1997 The NetBSD Foundation, Inc.
6bfd817adSflorian * All rights reserved.
7bfd817adSflorian *
8bfd817adSflorian * This code is derived from software contributed to The NetBSD Foundation
9bfd817adSflorian * by Jason Thorpe and Luke Mewburn.
10bfd817adSflorian *
11bfd817adSflorian * Redistribution and use in source and binary forms, with or without
12bfd817adSflorian * modification, are permitted provided that the following conditions
13bfd817adSflorian * are met:
14bfd817adSflorian * 1. Redistributions of source code must retain the above copyright
15bfd817adSflorian * notice, this list of conditions and the following disclaimer.
16bfd817adSflorian * 2. Redistributions in binary form must reproduce the above copyright
17bfd817adSflorian * notice, this list of conditions and the following disclaimer in the
18bfd817adSflorian * documentation and/or other materials provided with the distribution.
19bfd817adSflorian *
20bfd817adSflorian * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21bfd817adSflorian * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22bfd817adSflorian * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23bfd817adSflorian * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24bfd817adSflorian * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25bfd817adSflorian * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26bfd817adSflorian * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27bfd817adSflorian * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28bfd817adSflorian * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29bfd817adSflorian * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30bfd817adSflorian * POSSIBILITY OF SUCH DAMAGE.
31bfd817adSflorian */
32bfd817adSflorian
33bfd817adSflorian /*
34bfd817adSflorian * FTP User Program -- Command line file retrieval
35bfd817adSflorian */
36bfd817adSflorian
37bfd817adSflorian #include <sys/types.h>
38bfd817adSflorian #include <sys/socket.h>
39bfd817adSflorian #include <sys/stat.h>
40bfd817adSflorian
41bfd817adSflorian #include <netinet/in.h>
42bfd817adSflorian
43bfd817adSflorian #include <arpa/ftp.h>
44bfd817adSflorian #include <arpa/inet.h>
45bfd817adSflorian
46bfd817adSflorian #include <ctype.h>
47bfd817adSflorian #include <err.h>
48bfd817adSflorian #include <libgen.h>
49bfd817adSflorian #include <netdb.h>
50bfd817adSflorian #include <fcntl.h>
51bfd817adSflorian #include <signal.h>
52740dbc81Sderaadt #include <vis.h>
53bfd817adSflorian #include <stdio.h>
54bfd817adSflorian #include <stdarg.h>
55bfd817adSflorian #include <errno.h>
56bfd817adSflorian #include <stdlib.h>
57bfd817adSflorian #include <string.h>
58bfd817adSflorian #include <unistd.h>
59bfd817adSflorian #include <resolv.h>
6049dd5d56Srobert #include <utime.h>
61bfd817adSflorian
62bfd817adSflorian #ifndef NOSSL
63bfd817adSflorian #include <tls.h>
64bfd817adSflorian #else /* !NOSSL */
65bfd817adSflorian struct tls;
66bfd817adSflorian #endif /* !NOSSL */
67bfd817adSflorian
68bfd817adSflorian #include "ftp_var.h"
69bfd817adSflorian #include "cmds.h"
70bfd817adSflorian
71ba6894b2Sjca static int file_get(const char *, const char *);
72bfd817adSflorian static int url_get(const char *, const char *, const char *, int);
737102f6c2Sjca static int save_chunked(FILE *, struct tls *, int , char *, size_t);
74194d6d9dSjca static void aborthttp(int);
75194d6d9dSjca static char hextochar(const char *);
76194d6d9dSjca static char *urldecode(const char *);
77194d6d9dSjca static char *recode_credentials(const char *_userinfo);
78583394bbSjca static void ftp_close(FILE **, struct tls **, int *);
79194d6d9dSjca static const char *sockerror(struct tls *);
80b237d516Sjca #ifdef SMALL
81b237d516Sjca #define ftp_printf(fp, ...) fprintf(fp, __VA_ARGS__)
82b237d516Sjca #else
83b237d516Sjca static int ftp_printf(FILE *, const char *, ...);
84b237d516Sjca #endif /* SMALL */
85bfd817adSflorian #ifndef NOSSL
86194d6d9dSjca static int proxy_connect(int, char *, char *);
87194d6d9dSjca static int stdio_tls_write_wrapper(void *, const char *, int);
88194d6d9dSjca static int stdio_tls_read_wrapper(void *, char *, int);
89bfd817adSflorian #endif /* !NOSSL */
90bfd817adSflorian
91bfd817adSflorian #define FTP_URL "ftp://" /* ftp URL prefix */
92bfd817adSflorian #define HTTP_URL "http://" /* http URL prefix */
93bfd817adSflorian #define HTTPS_URL "https://" /* https URL prefix */
94bfd817adSflorian #define FILE_URL "file:" /* file URL prefix */
95bfd817adSflorian #define FTP_PROXY "ftp_proxy" /* env var with ftp proxy location */
96bfd817adSflorian #define HTTP_PROXY "http_proxy" /* env var with http proxy location */
97bfd817adSflorian
98bfd817adSflorian #define EMPTYSTRING(x) ((x) == NULL || (*(x) == '\0'))
99bfd817adSflorian
100bfd817adSflorian static const char at_encoding_warning[] =
101bfd817adSflorian "Extra `@' characters in usernames and passwords should be encoded as %%40";
102bfd817adSflorian
103194d6d9dSjca static jmp_buf httpabort;
104bfd817adSflorian
105bfd817adSflorian static int redirect_loop;
106f78cba61Sjca static int retried;
107bfd817adSflorian
108bfd817adSflorian /*
1097138ccd3Skn * Determine whether the character needs encoding, per RFC2396.
110bfd817adSflorian */
111bfd817adSflorian static int
to_encode(const char * c0)1127138ccd3Skn to_encode(const char *c0)
113bfd817adSflorian {
1147138ccd3Skn /* 2.4.3. Excluded US-ASCII Characters */
1157138ccd3Skn const char *excluded_chars =
1167138ccd3Skn " " /* space */
1177138ccd3Skn "<>#\"" /* delims (modulo "%", see below) */
1187138ccd3Skn "{}|\\^[]`" /* unwise */
1197138ccd3Skn ;
120bfd817adSflorian const unsigned char *c = (const unsigned char *)c0;
121bfd817adSflorian
122bfd817adSflorian /*
123bfd817adSflorian * No corresponding graphic US-ASCII.
124bfd817adSflorian * Control characters and octets not used in US-ASCII.
125bfd817adSflorian */
126bfd817adSflorian return (iscntrl(*c) || !isascii(*c) ||
127bfd817adSflorian
128bfd817adSflorian /*
1297138ccd3Skn * '%' is also reserved, if is not followed by two
130bfd817adSflorian * hexadecimal digits.
131bfd817adSflorian */
1327138ccd3Skn strchr(excluded_chars, *c) != NULL ||
1337810f2e0Sderaadt (*c == '%' && (!isxdigit(c[1]) || !isxdigit(c[2]))));
134bfd817adSflorian }
135bfd817adSflorian
136bfd817adSflorian /*
1377138ccd3Skn * Encode given URL, per RFC2396.
138bfd817adSflorian * Allocate and return string to the caller.
139bfd817adSflorian */
140bfd817adSflorian static char *
url_encode(const char * path)141bfd817adSflorian url_encode(const char *path)
142bfd817adSflorian {
143bfd817adSflorian size_t i, length, new_length;
144bfd817adSflorian char *epath, *epathp;
145bfd817adSflorian
146bfd817adSflorian length = new_length = strlen(path);
147bfd817adSflorian
148bfd817adSflorian /*
149bfd817adSflorian * First pass:
1507138ccd3Skn * Count characters to encode and determine length of the final URL.
151bfd817adSflorian */
152bfd817adSflorian for (i = 0; i < length; i++)
1537138ccd3Skn if (to_encode(path + i))
154bfd817adSflorian new_length += 2;
155bfd817adSflorian
156bfd817adSflorian epath = epathp = malloc(new_length + 1); /* One more for '\0'. */
157bfd817adSflorian if (epath == NULL)
158bfd817adSflorian err(1, "Can't allocate memory for URL encoding");
159bfd817adSflorian
160bfd817adSflorian /*
161bfd817adSflorian * Second pass:
162bfd817adSflorian * Encode, and copy final URL.
163bfd817adSflorian */
164bfd817adSflorian for (i = 0; i < length; i++)
1657138ccd3Skn if (to_encode(path + i)) {
166bfd817adSflorian snprintf(epathp, 4, "%%" "%02x",
167bfd817adSflorian (unsigned char)path[i]);
168bfd817adSflorian epathp += 3;
169bfd817adSflorian } else
170bfd817adSflorian *(epathp++) = path[i];
171bfd817adSflorian
172bfd817adSflorian *epathp = '\0';
173bfd817adSflorian return (epath);
174bfd817adSflorian }
175bfd817adSflorian
176bfd817adSflorian /*
177ba6894b2Sjca * Copy a local file (used by the OpenBSD installer).
178ba6894b2Sjca * Returns -1 on failure, 0 on success
179ba6894b2Sjca */
180ba6894b2Sjca static int
file_get(const char * path,const char * outfile)181ba6894b2Sjca file_get(const char *path, const char *outfile)
182ba6894b2Sjca {
183ba6894b2Sjca struct stat st;
1843f8a9645Sjca int fd, out = -1, rval = -1, save_errno;
185ba6894b2Sjca volatile sig_t oldintr, oldinti;
186ba6894b2Sjca const char *savefile;
1873411e43fSnaddy char *buf = NULL, *cp, *pathbuf = NULL;
188ba6894b2Sjca const size_t buflen = 128 * 1024;
189ba6894b2Sjca off_t hashbytes;
190ba6894b2Sjca ssize_t len, wlen;
191ba6894b2Sjca
192ba6894b2Sjca direction = "received";
193ba6894b2Sjca
194ba6894b2Sjca fd = open(path, O_RDONLY);
195ba6894b2Sjca if (fd == -1) {
196ba6894b2Sjca warn("Can't open file %s", path);
197ba6894b2Sjca return -1;
198ba6894b2Sjca }
199ba6894b2Sjca
200ba6894b2Sjca if (fstat(fd, &st) == -1)
201ba6894b2Sjca filesize = -1;
202ba6894b2Sjca else
203ba6894b2Sjca filesize = st.st_size;
204ba6894b2Sjca
205ba6894b2Sjca if (outfile != NULL)
206ba6894b2Sjca savefile = outfile;
207ba6894b2Sjca else {
208ba6894b2Sjca if (path[strlen(path) - 1] == '/') /* Consider no file */
209ba6894b2Sjca savefile = NULL; /* after dir invalid. */
2103411e43fSnaddy else {
2113411e43fSnaddy pathbuf = strdup(path);
2123411e43fSnaddy if (pathbuf == NULL)
2133411e43fSnaddy errx(1, "Can't allocate memory for filename");
2143411e43fSnaddy savefile = basename(pathbuf);
2153411e43fSnaddy }
216ba6894b2Sjca }
217ba6894b2Sjca
218ba6894b2Sjca if (EMPTYSTRING(savefile)) {
219ba6894b2Sjca warnx("No filename after directory (use -o): %s", path);
220ba6894b2Sjca goto cleanup_copy;
221ba6894b2Sjca }
222ba6894b2Sjca
223ba6894b2Sjca /* Open the output file. */
224ba6894b2Sjca if (!pipeout) {
225ba6894b2Sjca out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC, 0666);
226ba6894b2Sjca if (out == -1) {
227ba6894b2Sjca warn("Can't open %s", savefile);
228ba6894b2Sjca goto cleanup_copy;
229ba6894b2Sjca }
230ba6894b2Sjca } else
231ba6894b2Sjca out = fileno(stdout);
232ba6894b2Sjca
233ba6894b2Sjca if ((buf = malloc(buflen)) == NULL)
234ba6894b2Sjca errx(1, "Can't allocate memory for transfer buffer");
235ba6894b2Sjca
236ba6894b2Sjca /* Trap signals */
237ba6894b2Sjca oldintr = NULL;
238ba6894b2Sjca oldinti = NULL;
239ba6894b2Sjca if (setjmp(httpabort)) {
240ba6894b2Sjca if (oldintr)
241ba6894b2Sjca (void)signal(SIGINT, oldintr);
242ba6894b2Sjca if (oldinti)
243ba6894b2Sjca (void)signal(SIGINFO, oldinti);
244ba6894b2Sjca goto cleanup_copy;
245ba6894b2Sjca }
2465fbde59fSkn oldintr = signal(SIGINT, aborthttp);
247ba6894b2Sjca
248ba6894b2Sjca bytes = 0;
249ba6894b2Sjca hashbytes = mark;
250ba6894b2Sjca progressmeter(-1, path);
251ba6894b2Sjca
252ba6894b2Sjca /* Finally, suck down the file. */
253ba6894b2Sjca oldinti = signal(SIGINFO, psummary);
254ba6894b2Sjca while ((len = read(fd, buf, buflen)) > 0) {
255ba6894b2Sjca bytes += len;
256ba6894b2Sjca for (cp = buf; len > 0; len -= wlen, cp += wlen) {
257ba6894b2Sjca if ((wlen = write(out, cp, len)) == -1) {
258ba6894b2Sjca warn("Writing %s", savefile);
2597413ba97Sjca signal(SIGINT, oldintr);
260ba6894b2Sjca signal(SIGINFO, oldinti);
261ba6894b2Sjca goto cleanup_copy;
262ba6894b2Sjca }
263ba6894b2Sjca }
264ba6894b2Sjca if (hash && !progress) {
265ba6894b2Sjca while (bytes >= hashbytes) {
266ba6894b2Sjca (void)putc('#', ttyout);
267ba6894b2Sjca hashbytes += mark;
268ba6894b2Sjca }
269ba6894b2Sjca (void)fflush(ttyout);
270ba6894b2Sjca }
271ba6894b2Sjca }
272ba6894b2Sjca save_errno = errno;
2737413ba97Sjca signal(SIGINT, oldintr);
274ba6894b2Sjca signal(SIGINFO, oldinti);
275ba6894b2Sjca if (hash && !progress && bytes > 0) {
276ba6894b2Sjca if (bytes < mark)
277ba6894b2Sjca (void)putc('#', ttyout);
278ba6894b2Sjca (void)putc('\n', ttyout);
279ba6894b2Sjca (void)fflush(ttyout);
280ba6894b2Sjca }
281ba6894b2Sjca if (len == -1) {
282ba6894b2Sjca warnc(save_errno, "Reading from file");
283ba6894b2Sjca goto cleanup_copy;
284ba6894b2Sjca }
285ba6894b2Sjca progressmeter(1, NULL);
286ba6894b2Sjca if (verbose)
287ba6894b2Sjca ptransfer(0);
288ba6894b2Sjca
289ba6894b2Sjca rval = 0;
290ba6894b2Sjca
291ba6894b2Sjca cleanup_copy:
292ba6894b2Sjca free(buf);
2933411e43fSnaddy free(pathbuf);
294ba6894b2Sjca if (out >= 0 && out != fileno(stdout))
295ba6894b2Sjca close(out);
296ba6894b2Sjca close(fd);
297ba6894b2Sjca
298ba6894b2Sjca return rval;
299ba6894b2Sjca }
300ba6894b2Sjca
301ba6894b2Sjca /*
302bfd817adSflorian * Retrieve URL, via the proxy in $proxyvar if necessary.
303bfd817adSflorian * Returns -1 on failure, 0 on success
304bfd817adSflorian */
305bfd817adSflorian static int
url_get(const char * origline,const char * proxyenv,const char * outfile,int lastfile)306bfd817adSflorian url_get(const char *origline, const char *proxyenv, const char *outfile, int lastfile)
307bfd817adSflorian {
308bfd817adSflorian char pbuf[NI_MAXSERV], hbuf[NI_MAXHOST], *cp, *portnum, *path, ststr[4];
309bfd817adSflorian char *hosttail, *cause = "unknown", *newline, *host, *port, *buf = NULL;
310740dbc81Sderaadt char *epath, *redirurl, *loctail, *h, *p, gerror[200];
311583394bbSjca int error, isftpurl = 0, isredirect = 0, rval = -1;
3123733520cSjca int isunavail = 0, retryafter = -1;
313bfd817adSflorian struct addrinfo hints, *res0, *res;
314583394bbSjca const char *savefile;
3153411e43fSnaddy char *pathbuf = NULL;
316583394bbSjca char *proxyurl = NULL;
3177e8f63e0Syasuoka char *credentials = NULL, *proxy_credentials = NULL;
318583394bbSjca int fd = -1, out = -1;
319bfd817adSflorian volatile sig_t oldintr, oldinti;
320bfd817adSflorian FILE *fin = NULL;
321bfd817adSflorian off_t hashbytes;
322bfd817adSflorian const char *errstr;
323bfd817adSflorian ssize_t len, wlen;
324bb1c3b3fSnaddy size_t bufsize;
325bfd817adSflorian char *proxyhost = NULL;
326bfd817adSflorian #ifndef NOSSL
327bfd817adSflorian char *sslpath = NULL, *sslhost = NULL;
3280f23e651Sjca int ishttpsurl = 0;
329bfd817adSflorian #endif /* !NOSSL */
330bfd817adSflorian #ifndef SMALL
331e4b576d7Sjca char *full_host = NULL;
332e4b576d7Sjca const char *scheme;
333bfd817adSflorian char *locbase;
334bfd817adSflorian struct addrinfo *ares = NULL;
33549dd5d56Srobert char tmbuf[32];
33649dd5d56Srobert time_t mtime = 0;
33749dd5d56Srobert struct stat stbuf;
33849dd5d56Srobert struct tm lmt = { 0 };
33949dd5d56Srobert struct timespec ts[2];
340e4b576d7Sjca #endif /* !SMALL */
341bfd817adSflorian struct tls *tls = NULL;
342bfd817adSflorian int status;
343bfd817adSflorian int save_errno;
344bfd817adSflorian const size_t buflen = 128 * 1024;
3457102f6c2Sjca int chunked = 0;
346bfd817adSflorian
347bfd817adSflorian direction = "received";
348bfd817adSflorian
349bfd817adSflorian newline = strdup(origline);
350bfd817adSflorian if (newline == NULL)
351bfd817adSflorian errx(1, "Can't allocate memory to parse URL");
352bfd817adSflorian if (strncasecmp(newline, HTTP_URL, sizeof(HTTP_URL) - 1) == 0) {
353bfd817adSflorian host = newline + sizeof(HTTP_URL) - 1;
354e4b576d7Sjca #ifndef SMALL
355bfd817adSflorian scheme = HTTP_URL;
356bfd817adSflorian #endif /* !SMALL */
357bfd817adSflorian } else if (strncasecmp(newline, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
358bfd817adSflorian host = newline + sizeof(FTP_URL) - 1;
359bfd817adSflorian isftpurl = 1;
360bfd817adSflorian #ifndef SMALL
361bfd817adSflorian scheme = FTP_URL;
362bfd817adSflorian #endif /* !SMALL */
363bfd817adSflorian } else if (strncasecmp(newline, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0) {
364d5ffb3f3Sjca #ifndef NOSSL
365bfd817adSflorian host = newline + sizeof(HTTPS_URL) - 1;
366bfd817adSflorian ishttpsurl = 1;
367d5ffb3f3Sjca #else
368d5ffb3f3Sjca errx(1, "%s: No HTTPS support", newline);
369d5ffb3f3Sjca #endif /* !NOSSL */
370e4b576d7Sjca #ifndef SMALL
371bfd817adSflorian scheme = HTTPS_URL;
372e4b576d7Sjca #endif /* !SMALL */
373bfd817adSflorian } else
374ba6894b2Sjca errx(1, "%s: URL not permitted", newline);
375bfd817adSflorian
376bfd817adSflorian path = strchr(host, '/'); /* Find path */
37734ed7cb2Sjca
37834ed7cb2Sjca /*
37934ed7cb2Sjca * Look for auth header in host.
38034ed7cb2Sjca * Basic auth from RFC 2617, valid characters for path are in
38134ed7cb2Sjca * RFC 3986 section 3.3.
38234ed7cb2Sjca */
3830f23e651Sjca if (!isftpurl) {
38434ed7cb2Sjca p = strchr(host, '@');
38534ed7cb2Sjca if (p != NULL && (path == NULL || p < path)) {
38634ed7cb2Sjca *p++ = '\0';
38734ed7cb2Sjca credentials = recode_credentials(host);
38834ed7cb2Sjca
38934ed7cb2Sjca /* Overwrite userinfo */
39034ed7cb2Sjca memmove(host, p, strlen(p) + 1);
39134ed7cb2Sjca path = strchr(host, '/');
39234ed7cb2Sjca }
39334ed7cb2Sjca }
39434ed7cb2Sjca
395bfd817adSflorian if (EMPTYSTRING(path)) {
396bfd817adSflorian if (outfile) { /* No slash, but */
397bfd817adSflorian path = strchr(host,'\0'); /* we have outfile. */
398bfd817adSflorian goto noslash;
399bfd817adSflorian }
400bfd817adSflorian if (isftpurl)
401bfd817adSflorian goto noftpautologin;
402bfd817adSflorian warnx("No `/' after host (use -o): %s", origline);
403bfd817adSflorian goto cleanup_url_get;
404bfd817adSflorian }
405bfd817adSflorian *path++ = '\0';
406bfd817adSflorian if (EMPTYSTRING(path) && !outfile) {
407bfd817adSflorian if (isftpurl)
408bfd817adSflorian goto noftpautologin;
409bfd817adSflorian warnx("No filename after host (use -o): %s", origline);
410bfd817adSflorian goto cleanup_url_get;
411bfd817adSflorian }
412bfd817adSflorian
413bfd817adSflorian noslash:
414bfd817adSflorian if (outfile)
415bfd817adSflorian savefile = outfile;
416bfd817adSflorian else {
417bfd817adSflorian if (path[strlen(path) - 1] == '/') /* Consider no file */
418bfd817adSflorian savefile = NULL; /* after dir invalid. */
4193411e43fSnaddy else {
4203411e43fSnaddy pathbuf = strdup(path);
4213411e43fSnaddy if (pathbuf == NULL)
4223411e43fSnaddy errx(1, "Can't allocate memory for filename");
4233411e43fSnaddy savefile = basename(pathbuf);
4243411e43fSnaddy }
425bfd817adSflorian }
426bfd817adSflorian
427bfd817adSflorian if (EMPTYSTRING(savefile)) {
428bfd817adSflorian if (isftpurl)
429bfd817adSflorian goto noftpautologin;
430bfd817adSflorian warnx("No filename after directory (use -o): %s", origline);
431bfd817adSflorian goto cleanup_url_get;
432bfd817adSflorian }
433bfd817adSflorian
434bfd817adSflorian #ifndef SMALL
435bfd817adSflorian if (resume && pipeout) {
436bfd817adSflorian warnx("can't append to stdout");
437bfd817adSflorian goto cleanup_url_get;
438bfd817adSflorian }
439bfd817adSflorian #endif /* !SMALL */
440bfd817adSflorian
441ba6894b2Sjca if (proxyenv != NULL) { /* use proxy */
442bfd817adSflorian #ifndef NOSSL
443bfd817adSflorian if (ishttpsurl) {
444bfd817adSflorian sslpath = strdup(path);
445bfd817adSflorian sslhost = strdup(host);
446bfd817adSflorian if (! sslpath || ! sslhost)
447bfd817adSflorian errx(1, "Can't allocate memory for https path/host.");
448bfd817adSflorian }
449bfd817adSflorian #endif /* !NOSSL */
450bfd817adSflorian proxyhost = strdup(host);
451bfd817adSflorian if (proxyhost == NULL)
452bfd817adSflorian errx(1, "Can't allocate memory for proxy host.");
453bfd817adSflorian proxyurl = strdup(proxyenv);
454bfd817adSflorian if (proxyurl == NULL)
455bfd817adSflorian errx(1, "Can't allocate memory for proxy URL.");
456bfd817adSflorian if (strncasecmp(proxyurl, HTTP_URL, sizeof(HTTP_URL) - 1) == 0)
457bfd817adSflorian host = proxyurl + sizeof(HTTP_URL) - 1;
458bfd817adSflorian else if (strncasecmp(proxyurl, FTP_URL, sizeof(FTP_URL) - 1) == 0)
459bfd817adSflorian host = proxyurl + sizeof(FTP_URL) - 1;
460bfd817adSflorian else {
461bfd817adSflorian warnx("Malformed proxy URL: %s", proxyenv);
462bfd817adSflorian goto cleanup_url_get;
463bfd817adSflorian }
464bfd817adSflorian if (EMPTYSTRING(host)) {
465bfd817adSflorian warnx("Malformed proxy URL: %s", proxyenv);
466bfd817adSflorian goto cleanup_url_get;
467bfd817adSflorian }
468bfd817adSflorian if (*--path == '\0')
469bfd817adSflorian *path = '/'; /* add / back to real path */
470bfd817adSflorian path = strchr(host, '/'); /* remove trailing / on host */
471bfd817adSflorian if (!EMPTYSTRING(path))
472bfd817adSflorian *path++ = '\0'; /* i guess this ++ is useless */
473bfd817adSflorian
474bfd817adSflorian path = strchr(host, '@'); /* look for credentials in proxy */
475bfd817adSflorian if (!EMPTYSTRING(path)) {
476bfd817adSflorian *path = '\0';
477bfd817adSflorian if (strchr(host, ':') == NULL) {
478bfd817adSflorian warnx("Malformed proxy URL: %s", proxyenv);
479bfd817adSflorian goto cleanup_url_get;
480bfd817adSflorian }
4817e8f63e0Syasuoka proxy_credentials = recode_credentials(host);
482bfd817adSflorian *path = '@'; /* restore @ in proxyurl */
483bfd817adSflorian
484bfd817adSflorian /*
485bfd817adSflorian * This removes the password from proxyurl,
486bfd817adSflorian * filling with stars
487bfd817adSflorian */
488bfd817adSflorian for (host = 1 + strchr(proxyurl + 5, ':'); *host != '@';
489bfd817adSflorian host++)
490bfd817adSflorian *host = '*';
491bfd817adSflorian
492bfd817adSflorian host = path + 1;
493bfd817adSflorian }
494bfd817adSflorian
495bfd817adSflorian path = newline;
496bfd817adSflorian }
497bfd817adSflorian
498bfd817adSflorian if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL &&
499bfd817adSflorian (hosttail[1] == '\0' || hosttail[1] == ':')) {
500bfd817adSflorian host++;
501bfd817adSflorian *hosttail++ = '\0';
502bfd817adSflorian #ifndef SMALL
503bfd817adSflorian if (asprintf(&full_host, "[%s]", host) == -1)
504bfd817adSflorian errx(1, "Cannot allocate memory for hostname");
505bfd817adSflorian #endif /* !SMALL */
506bfd817adSflorian } else
507bfd817adSflorian hosttail = host;
508bfd817adSflorian
509bfd817adSflorian portnum = strrchr(hosttail, ':'); /* find portnum */
510bfd817adSflorian if (portnum != NULL)
511bfd817adSflorian *portnum++ = '\0';
512bfd817adSflorian #ifndef NOSSL
513bfd817adSflorian port = portnum ? portnum : (ishttpsurl ? httpsport : httpport);
514bfd817adSflorian #else /* !NOSSL */
515bfd817adSflorian port = portnum ? portnum : httpport;
516bfd817adSflorian #endif /* !NOSSL */
517bfd817adSflorian
518bfd817adSflorian #ifndef SMALL
519bfd817adSflorian if (full_host == NULL)
520bfd817adSflorian if ((full_host = strdup(host)) == NULL)
521bfd817adSflorian errx(1, "Cannot allocate memory for hostname");
522bfd817adSflorian if (debug)
523bfd817adSflorian fprintf(ttyout, "host %s, port %s, path %s, "
524bfd817adSflorian "save as %s, auth %s.\n", host, port, path,
525bfd817adSflorian savefile, credentials ? credentials : "none");
526bfd817adSflorian #endif /* !SMALL */
527bfd817adSflorian
528bfd817adSflorian memset(&hints, 0, sizeof(hints));
529bfd817adSflorian hints.ai_family = family;
530bfd817adSflorian hints.ai_socktype = SOCK_STREAM;
531bfd817adSflorian error = getaddrinfo(host, port, &hints, &res0);
532bfd817adSflorian /*
533bfd817adSflorian * If the services file is corrupt/missing, fall back
534bfd817adSflorian * on our hard-coded defines.
535bfd817adSflorian */
536bfd817adSflorian if (error == EAI_SERVICE && port == httpport) {
537bfd817adSflorian snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT);
538bfd817adSflorian error = getaddrinfo(host, pbuf, &hints, &res0);
539bfd817adSflorian #ifndef NOSSL
540bfd817adSflorian } else if (error == EAI_SERVICE && port == httpsport) {
541bfd817adSflorian snprintf(pbuf, sizeof(pbuf), "%d", HTTPS_PORT);
542bfd817adSflorian error = getaddrinfo(host, pbuf, &hints, &res0);
543bfd817adSflorian #endif /* !NOSSL */
544bfd817adSflorian }
545bfd817adSflorian if (error) {
546bfd817adSflorian warnx("%s: %s", host, gai_strerror(error));
547bfd817adSflorian goto cleanup_url_get;
548bfd817adSflorian }
549bfd817adSflorian
550bfd817adSflorian #ifndef SMALL
551bfd817adSflorian if (srcaddr) {
552bfd817adSflorian hints.ai_flags |= AI_NUMERICHOST;
553bfd817adSflorian error = getaddrinfo(srcaddr, NULL, &hints, &ares);
554bfd817adSflorian if (error) {
555bfd817adSflorian warnx("%s: %s", srcaddr, gai_strerror(error));
556bfd817adSflorian goto cleanup_url_get;
557bfd817adSflorian }
558bfd817adSflorian }
559bfd817adSflorian #endif /* !SMALL */
560bfd817adSflorian
561bfd817adSflorian /* ensure consistent order of the output */
562bfd817adSflorian if (verbose)
563bfd817adSflorian setvbuf(ttyout, NULL, _IOLBF, 0);
564bfd817adSflorian
565bfd817adSflorian fd = -1;
566bfd817adSflorian for (res = res0; res; res = res->ai_next) {
567bfd817adSflorian if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf,
568bfd817adSflorian sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
569bfd817adSflorian strlcpy(hbuf, "(unknown)", sizeof(hbuf));
570bfd817adSflorian if (verbose)
571bfd817adSflorian fprintf(ttyout, "Trying %s...\n", hbuf);
572bfd817adSflorian
573bfd817adSflorian fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
574bfd817adSflorian if (fd == -1) {
575bfd817adSflorian cause = "socket";
576bfd817adSflorian continue;
577bfd817adSflorian }
578bfd817adSflorian
579bfd817adSflorian #ifndef SMALL
580bfd817adSflorian if (srcaddr) {
581bfd817adSflorian if (ares->ai_family != res->ai_family) {
582bfd817adSflorian close(fd);
583bfd817adSflorian fd = -1;
584bfd817adSflorian errno = EINVAL;
585bfd817adSflorian cause = "bind";
586bfd817adSflorian continue;
587bfd817adSflorian }
5883aaa63ebSderaadt if (bind(fd, ares->ai_addr, ares->ai_addrlen) == -1) {
589bfd817adSflorian save_errno = errno;
590bfd817adSflorian close(fd);
591bfd817adSflorian errno = save_errno;
592bfd817adSflorian fd = -1;
593bfd817adSflorian cause = "bind";
594bfd817adSflorian continue;
595bfd817adSflorian }
596bfd817adSflorian }
597bfd817adSflorian #endif /* !SMALL */
598bfd817adSflorian
599f37faed6Smillert error = timed_connect(fd, res->ai_addr, res->ai_addrlen,
600f37faed6Smillert connect_timeout);
601bfd817adSflorian if (error != 0) {
602bfd817adSflorian save_errno = errno;
603bfd817adSflorian close(fd);
604bfd817adSflorian errno = save_errno;
605bfd817adSflorian fd = -1;
606bfd817adSflorian cause = "connect";
607bfd817adSflorian continue;
608bfd817adSflorian }
609bfd817adSflorian
610bfd817adSflorian /* get port in numeric */
611bfd817adSflorian if (getnameinfo(res->ai_addr, res->ai_addrlen, NULL, 0,
612bfd817adSflorian pbuf, sizeof(pbuf), NI_NUMERICSERV) == 0)
613bfd817adSflorian port = pbuf;
614bfd817adSflorian else
615bfd817adSflorian port = NULL;
616bfd817adSflorian
617bfd817adSflorian #ifndef NOSSL
618bfd817adSflorian if (proxyenv && sslhost)
6197e8f63e0Syasuoka proxy_connect(fd, sslhost, proxy_credentials);
620bfd817adSflorian #endif /* !NOSSL */
621bfd817adSflorian break;
622bfd817adSflorian }
623bfd817adSflorian freeaddrinfo(res0);
624bfd817adSflorian #ifndef SMALL
625bfd817adSflorian if (srcaddr)
626bfd817adSflorian freeaddrinfo(ares);
627bfd817adSflorian #endif /* !SMALL */
628bfd817adSflorian if (fd < 0) {
629bfd817adSflorian warn("%s", cause);
630bfd817adSflorian goto cleanup_url_get;
631bfd817adSflorian }
632bfd817adSflorian
633bfd817adSflorian #ifndef NOSSL
634bfd817adSflorian if (ishttpsurl) {
63580ddac7aSbeck ssize_t ret;
636bfd817adSflorian if (proxyenv && sslpath) {
637bfd817adSflorian ishttpsurl = 0;
638bfd817adSflorian proxyurl = NULL;
639bfd817adSflorian path = sslpath;
640bfd817adSflorian }
641bfd817adSflorian if (sslhost == NULL) {
642bfd817adSflorian sslhost = strdup(host);
643bfd817adSflorian if (sslhost == NULL)
644bfd817adSflorian errx(1, "Can't allocate memory for https host.");
645bfd817adSflorian }
646bfd817adSflorian if ((tls = tls_client()) == NULL) {
647bfd817adSflorian fprintf(ttyout, "failed to create SSL client\n");
648bfd817adSflorian goto cleanup_url_get;
649bfd817adSflorian }
650bfd817adSflorian if (tls_configure(tls, tls_config) != 0) {
65180ddac7aSbeck fprintf(ttyout, "TLS configuration failure: %s\n",
652bfd817adSflorian tls_error(tls));
653bfd817adSflorian goto cleanup_url_get;
654bfd817adSflorian }
655bfd817adSflorian if (tls_connect_socket(tls, fd, sslhost) != 0) {
65680ddac7aSbeck fprintf(ttyout, "TLS connect failure: %s\n", tls_error(tls));
657bfd817adSflorian goto cleanup_url_get;
658bfd817adSflorian }
65980ddac7aSbeck do {
66080ddac7aSbeck ret = tls_handshake(tls);
66180ddac7aSbeck } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
66280ddac7aSbeck if (ret != 0) {
66380ddac7aSbeck fprintf(ttyout, "TLS handshake failure: %s\n", tls_error(tls));
66494d4d1aaSjca goto cleanup_url_get;
66594d4d1aaSjca }
66694d4d1aaSjca fin = funopen(tls, stdio_tls_read_wrapper,
66794d4d1aaSjca stdio_tls_write_wrapper, NULL, NULL);
668bfd817adSflorian } else {
669bfd817adSflorian fin = fdopen(fd, "r+");
670bfd817adSflorian fd = -1;
671bfd817adSflorian }
672bfd817adSflorian #else /* !NOSSL */
673bfd817adSflorian fin = fdopen(fd, "r+");
674bfd817adSflorian fd = -1;
675bfd817adSflorian #endif /* !NOSSL */
676bfd817adSflorian
677bfd817adSflorian #ifdef SMALL
678bfd817adSflorian if (lastfile) {
679bfd817adSflorian if (pipeout) {
680bfd817adSflorian if (pledge("stdio rpath inet dns tty", NULL) == -1)
681bfd817adSflorian err(1, "pledge");
682bfd817adSflorian } else {
683bfd817adSflorian if (pledge("stdio rpath wpath cpath inet dns tty", NULL) == -1)
684bfd817adSflorian err(1, "pledge");
685bfd817adSflorian }
686bfd817adSflorian }
687bfd817adSflorian #endif
688bfd817adSflorian
689bfd817adSflorian /*
690bfd817adSflorian * Construct and send the request. Proxy requests don't want leading /.
691bfd817adSflorian */
692bfd817adSflorian #ifndef NOSSL
693bfd817adSflorian cookie_get(host, path, ishttpsurl, &buf);
694bfd817adSflorian #endif /* !NOSSL */
695bfd817adSflorian
696bfd817adSflorian epath = url_encode(path);
697bfd817adSflorian if (proxyurl) {
698bfd817adSflorian if (verbose) {
699bfd817adSflorian fprintf(ttyout, "Requesting %s (via %s)\n",
70033488478Skn origline, proxyurl);
701bfd817adSflorian }
702bfd817adSflorian /*
703bfd817adSflorian * Host: directive must use the destination host address for
704bfd817adSflorian * the original URI (path).
705bfd817adSflorian */
706b237d516Sjca ftp_printf(fin, "GET %s HTTP/1.1\r\n"
7077102f6c2Sjca "Connection: close\r\n"
708456e8b6aStb "Host: %s\r\n%s%s\r\n"
709456e8b6aStb "Accept: */*\r\n",
710bfd817adSflorian epath, proxyhost, buf ? buf : "", httpuseragent);
7117e8f63e0Syasuoka if (credentials)
7127e8f63e0Syasuoka ftp_printf(fin, "Authorization: Basic %s\r\n",
7137e8f63e0Syasuoka credentials);
7147e8f63e0Syasuoka if (proxy_credentials)
7157e8f63e0Syasuoka ftp_printf(fin, "Proxy-Authorization: Basic %s\r\n",
7167e8f63e0Syasuoka proxy_credentials);
7177e8f63e0Syasuoka ftp_printf(fin, "\r\n");
718bfd817adSflorian } else {
7195818f8d1Skn if (verbose)
72033488478Skn fprintf(ttyout, "Requesting %s\n", origline);
72133488478Skn #ifndef SMALL
72249dd5d56Srobert if (resume || timestamp) {
72349dd5d56Srobert if (stat(savefile, &stbuf) == 0) {
72449dd5d56Srobert if (resume)
725bfd817adSflorian restart_point = stbuf.st_size;
72649dd5d56Srobert if (timestamp)
72749dd5d56Srobert mtime = stbuf.st_mtime;
72849dd5d56Srobert } else {
729bfd817adSflorian restart_point = 0;
73049dd5d56Srobert mtime = 0;
73149dd5d56Srobert }
732bfd817adSflorian }
733bfd817adSflorian #endif /* SMALL */
734b237d516Sjca ftp_printf(fin,
7357102f6c2Sjca "GET /%s HTTP/1.1\r\n"
7367102f6c2Sjca "Connection: close\r\n"
7377102f6c2Sjca "Host: ", epath);
738bfd817adSflorian if (proxyhost) {
739b237d516Sjca ftp_printf(fin, "%s", proxyhost);
740bfd817adSflorian port = NULL;
741bfd817adSflorian } else if (strchr(host, ':')) {
742bfd817adSflorian /*
743bfd817adSflorian * strip off scoped address portion, since it's
744bfd817adSflorian * local to node
745bfd817adSflorian */
746bfd817adSflorian h = strdup(host);
747bfd817adSflorian if (h == NULL)
748bfd817adSflorian errx(1, "Can't allocate memory.");
749bfd817adSflorian if ((p = strchr(h, '%')) != NULL)
750bfd817adSflorian *p = '\0';
751b237d516Sjca ftp_printf(fin, "[%s]", h);
752bfd817adSflorian free(h);
753bfd817adSflorian } else
754b237d516Sjca ftp_printf(fin, "%s", host);
755bfd817adSflorian
756bfd817adSflorian /*
757bfd817adSflorian * Send port number only if it's specified and does not equal
758bfd817adSflorian * 80. Some broken HTTP servers get confused if you explicitly
759bfd817adSflorian * send them the port number.
760bfd817adSflorian */
761bfd817adSflorian #ifndef NOSSL
762bfd817adSflorian if (port && strcmp(port, (ishttpsurl ? "443" : "80")) != 0)
763b237d516Sjca ftp_printf(fin, ":%s", port);
764bfd817adSflorian if (restart_point)
765b237d516Sjca ftp_printf(fin, "\r\nRange: bytes=%lld-",
766bfd817adSflorian (long long)restart_point);
767bfd817adSflorian #else /* !NOSSL */
768bfd817adSflorian if (port && strcmp(port, "80") != 0)
769b237d516Sjca ftp_printf(fin, ":%s", port);
770bfd817adSflorian #endif /* !NOSSL */
77149dd5d56Srobert
77249dd5d56Srobert #ifndef SMALL
77349dd5d56Srobert if (mtime && (http_time(mtime, tmbuf, sizeof(tmbuf)) != 0))
77449dd5d56Srobert ftp_printf(fin, "\r\nIf-Modified-Since: %s", tmbuf);
77549dd5d56Srobert #endif /* SMALL */
77649dd5d56Srobert
777456e8b6aStb ftp_printf(fin, "\r\n%s%s\r\n", buf ? buf : "", httpuseragent);
778456e8b6aStb ftp_printf(fin, "Accept: */*\r\n");
77967a189a2Sjca if (credentials)
78067a189a2Sjca ftp_printf(fin, "Authorization: Basic %s\r\n",
78167a189a2Sjca credentials);
78267a189a2Sjca ftp_printf(fin, "\r\n");
783bfd817adSflorian }
784bfd817adSflorian free(epath);
785bfd817adSflorian
786bfd817adSflorian #ifndef NOSSL
787bfd817adSflorian free(buf);
788bfd817adSflorian #endif /* !NOSSL */
789bfd817adSflorian buf = NULL;
790bb1c3b3fSnaddy bufsize = 0;
791bfd817adSflorian
79294d4d1aaSjca if (fflush(fin) == EOF) {
79394d4d1aaSjca warnx("Writing HTTP request: %s", sockerror(tls));
794bfd817adSflorian goto cleanup_url_get;
795bfd817adSflorian }
796bb1c3b3fSnaddy if ((len = getline(&buf, &bufsize, fin)) == -1) {
79794d4d1aaSjca warnx("Receiving HTTP reply: %s", sockerror(tls));
798bfd817adSflorian goto cleanup_url_get;
799bfd817adSflorian }
800bfd817adSflorian
801bfd817adSflorian while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
802bfd817adSflorian buf[--len] = '\0';
803bfd817adSflorian #ifndef SMALL
804bfd817adSflorian if (debug)
805bfd817adSflorian fprintf(ttyout, "received '%s'\n", buf);
806bfd817adSflorian #endif /* !SMALL */
807bfd817adSflorian
808bfd817adSflorian cp = strchr(buf, ' ');
809bfd817adSflorian if (cp == NULL)
810bfd817adSflorian goto improper;
811bfd817adSflorian else
812bfd817adSflorian cp++;
813bfd817adSflorian
814bfd817adSflorian strlcpy(ststr, cp, sizeof(ststr));
815f78cba61Sjca status = strtonum(ststr, 200, 503, &errstr);
816bfd817adSflorian if (errstr) {
817740dbc81Sderaadt strnvis(gerror, cp, sizeof gerror, VIS_SAFE);
818740dbc81Sderaadt warnx("Error retrieving %s: %s", origline, gerror);
819bfd817adSflorian goto cleanup_url_get;
820bfd817adSflorian }
821bfd817adSflorian
822bfd817adSflorian switch (status) {
823bfd817adSflorian case 200: /* OK */
824bfd817adSflorian #ifndef SMALL
825bfd817adSflorian /*
826bfd817adSflorian * When we request a partial file, and we receive an HTTP 200
827bfd817adSflorian * it is a good indication that the server doesn't support
828bfd817adSflorian * range requests, and is about to send us the entire file.
829bfd817adSflorian * If the restart_point == 0, then we are not actually
830bfd817adSflorian * requesting a partial file, and an HTTP 200 is appropriate.
831bfd817adSflorian */
832bfd817adSflorian if (resume && restart_point != 0) {
833bfd817adSflorian warnx("Server does not support resume.");
834bfd817adSflorian restart_point = resume = 0;
835bfd817adSflorian }
836bfd817adSflorian /* FALLTHROUGH */
837bfd817adSflorian case 206: /* Partial Content */
838bfd817adSflorian #endif /* !SMALL */
839bfd817adSflorian break;
840bfd817adSflorian case 301: /* Moved Permanently */
841bfd817adSflorian case 302: /* Found */
842bfd817adSflorian case 303: /* See Other */
843bfd817adSflorian case 307: /* Temporary Redirect */
844e0549513Schrisz case 308: /* Permanent Redirect (RFC 7538) */
845bfd817adSflorian isredirect++;
846bfd817adSflorian if (redirect_loop++ > 10) {
847bfd817adSflorian warnx("Too many redirections requested");
848bfd817adSflorian goto cleanup_url_get;
849bfd817adSflorian }
850bfd817adSflorian break;
851bfd817adSflorian #ifndef SMALL
85249dd5d56Srobert case 304: /* Not Modified */
85349dd5d56Srobert warnx("File is not modified on the server");
85449dd5d56Srobert goto cleanup_url_get;
855bfd817adSflorian case 416: /* Requested Range Not Satisfiable */
856bfd817adSflorian warnx("File is already fully retrieved.");
857bfd817adSflorian goto cleanup_url_get;
858bfd817adSflorian #endif /* !SMALL */
859f78cba61Sjca case 503:
8603733520cSjca isunavail = 1;
8613733520cSjca break;
862bfd817adSflorian default:
863740dbc81Sderaadt strnvis(gerror, cp, sizeof gerror, VIS_SAFE);
864740dbc81Sderaadt warnx("Error retrieving %s: %s", origline, gerror);
865bfd817adSflorian goto cleanup_url_get;
866bfd817adSflorian }
867bfd817adSflorian
868bfd817adSflorian /*
869bfd817adSflorian * Read the rest of the header.
870bfd817adSflorian */
871bfd817adSflorian filesize = -1;
872bfd817adSflorian
873bfd817adSflorian for (;;) {
874bb1c3b3fSnaddy if ((len = getline(&buf, &bufsize, fin)) == -1) {
87594d4d1aaSjca warnx("Receiving HTTP reply: %s", sockerror(tls));
876bfd817adSflorian goto cleanup_url_get;
877bfd817adSflorian }
878bfd817adSflorian
87939263320Sclaudio while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n' ||
88039263320Sclaudio buf[len-1] == ' ' || buf[len-1] == '\t'))
881bfd817adSflorian buf[--len] = '\0';
882bfd817adSflorian if (len == 0)
883bfd817adSflorian break;
884bfd817adSflorian #ifndef SMALL
885bfd817adSflorian if (debug)
886bfd817adSflorian fprintf(ttyout, "received '%s'\n", buf);
887bfd817adSflorian #endif /* !SMALL */
888bfd817adSflorian
889bfd817adSflorian /* Look for some headers */
890bfd817adSflorian cp = buf;
891bfd817adSflorian #define CONTENTLEN "Content-Length:"
892bfd817adSflorian if (strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0) {
893bfd817adSflorian cp += sizeof(CONTENTLEN) - 1;
8944520669eSclaudio cp += strspn(cp, " \t");
895bfd817adSflorian filesize = strtonum(cp, 0, LLONG_MAX, &errstr);
896bfd817adSflorian if (errstr != NULL)
897bfd817adSflorian goto improper;
898bfd817adSflorian #ifndef SMALL
899bfd817adSflorian if (restart_point)
900bfd817adSflorian filesize += restart_point;
901bfd817adSflorian #endif /* !SMALL */
902bfd817adSflorian #define LOCATION "Location:"
903bfd817adSflorian } else if (isredirect &&
904bfd817adSflorian strncasecmp(cp, LOCATION, sizeof(LOCATION) - 1) == 0) {
905bfd817adSflorian cp += sizeof(LOCATION) - 1;
9064520669eSclaudio cp += strspn(cp, " \t");
907bfd817adSflorian /*
908bfd817adSflorian * If there is a colon before the first slash, this URI
909bfd817adSflorian * is not relative. RFC 3986 4.2
910bfd817adSflorian */
911bfd817adSflorian if (cp[strcspn(cp, ":/")] != ':') {
912bfd817adSflorian #ifdef SMALL
913bfd817adSflorian errx(1, "Relative redirect not supported");
914bfd817adSflorian #else /* SMALL */
915bfd817adSflorian /* XXX doesn't handle protocol-relative URIs */
916bfd817adSflorian if (*cp == '/') {
917bfd817adSflorian locbase = NULL;
918bfd817adSflorian cp++;
919bfd817adSflorian } else {
920bfd817adSflorian locbase = strdup(path);
921bfd817adSflorian if (locbase == NULL)
922bfd817adSflorian errx(1, "Can't allocate memory"
923bfd817adSflorian " for location base");
924bfd817adSflorian loctail = strchr(locbase, '#');
925bfd817adSflorian if (loctail != NULL)
926bfd817adSflorian *loctail = '\0';
927bfd817adSflorian loctail = strchr(locbase, '?');
928bfd817adSflorian if (loctail != NULL)
929bfd817adSflorian *loctail = '\0';
930bfd817adSflorian loctail = strrchr(locbase, '/');
931bfd817adSflorian if (loctail == NULL) {
932bfd817adSflorian free(locbase);
933bfd817adSflorian locbase = NULL;
934bfd817adSflorian } else
935bfd817adSflorian loctail[1] = '\0';
936bfd817adSflorian }
937d9a51c35Sjmc /* Construct URL from relative redirect */
938bfd817adSflorian if (asprintf(&redirurl, "%s%s%s%s/%s%s",
939bfd817adSflorian scheme, full_host,
940bfd817adSflorian portnum ? ":" : "",
941bfd817adSflorian portnum ? portnum : "",
942bfd817adSflorian locbase ? locbase : "",
943bfd817adSflorian cp) == -1)
944bfd817adSflorian errx(1, "Cannot build "
945bfd817adSflorian "redirect URL");
946bfd817adSflorian free(locbase);
947bfd817adSflorian #endif /* SMALL */
948bfd817adSflorian } else if ((redirurl = strdup(cp)) == NULL)
949bfd817adSflorian errx(1, "Cannot allocate memory for URL");
950bfd817adSflorian loctail = strchr(redirurl, '#');
951bfd817adSflorian if (loctail != NULL)
952bfd817adSflorian *loctail = '\0';
953e8e14267Sclaudio if (verbose) {
954e8e14267Sclaudio char *visbuf;
955e8e14267Sclaudio if (stravis(&visbuf, redirurl, VIS_SAFE) == -1)
956e8e14267Sclaudio err(1, "Cannot vis redirect URL");
957e8e14267Sclaudio fprintf(ttyout, "Redirected to %s\n", visbuf);
958e8e14267Sclaudio free(visbuf);
959e8e14267Sclaudio }
960535fae4eSjca ftp_close(&fin, &tls, &fd);
961bfd817adSflorian rval = url_get(redirurl, proxyenv, savefile, lastfile);
962bfd817adSflorian free(redirurl);
963bfd817adSflorian goto cleanup_url_get;
9643733520cSjca #define RETRYAFTER "Retry-After:"
9653733520cSjca } else if (isunavail &&
9663733520cSjca strncasecmp(cp, RETRYAFTER, sizeof(RETRYAFTER) - 1) == 0) {
9673733520cSjca cp += sizeof(RETRYAFTER) - 1;
9684520669eSclaudio cp += strspn(cp, " \t");
9693733520cSjca retryafter = strtonum(cp, 0, 0, &errstr);
9703733520cSjca if (errstr != NULL)
9713733520cSjca retryafter = -1;
9727102f6c2Sjca #define TRANSFER_ENCODING "Transfer-Encoding:"
9737102f6c2Sjca } else if (strncasecmp(cp, TRANSFER_ENCODING,
9747102f6c2Sjca sizeof(TRANSFER_ENCODING) - 1) == 0) {
9757102f6c2Sjca cp += sizeof(TRANSFER_ENCODING) - 1;
9764520669eSclaudio cp += strspn(cp, " \t");
9777102f6c2Sjca if (strcasecmp(cp, "chunked") == 0)
9787102f6c2Sjca chunked = 1;
97949dd5d56Srobert #ifndef SMALL
98049dd5d56Srobert #define LAST_MODIFIED "Last-Modified:"
98149dd5d56Srobert } else if (strncasecmp(cp, LAST_MODIFIED,
98249dd5d56Srobert sizeof(LAST_MODIFIED) - 1) == 0) {
98349dd5d56Srobert cp += sizeof(LAST_MODIFIED) - 1;
984e846c64dSop cp += strspn(cp, " \t");
98549dd5d56Srobert if (strptime(cp, "%a, %d %h %Y %T %Z", &lmt) == NULL)
98649dd5d56Srobert server_timestamps = 0;
98749dd5d56Srobert #endif /* !SMALL */
988bfd817adSflorian }
989bfd817adSflorian }
990bb1c3b3fSnaddy free(buf);
991a0c22864Snaddy buf = NULL;
992bfd817adSflorian
9937102f6c2Sjca /* Content-Length should be ignored for Transfer-Encoding: chunked */
9947102f6c2Sjca if (chunked)
9957102f6c2Sjca filesize = -1;
9967102f6c2Sjca
9973733520cSjca if (isunavail) {
9983733520cSjca if (retried || retryafter != 0)
999740dbc81Sderaadt warnx("Error retrieving %s: 503 Service Unavailable",
1000740dbc81Sderaadt origline);
10013733520cSjca else {
10023733520cSjca if (verbose)
10033733520cSjca fprintf(ttyout, "Retrying %s\n", origline);
10043733520cSjca retried = 1;
10051e6907dcSjca ftp_close(&fin, &tls, &fd);
10063733520cSjca rval = url_get(origline, proxyenv, savefile, lastfile);
10073733520cSjca }
10083733520cSjca goto cleanup_url_get;
10093733520cSjca }
10103733520cSjca
1011bfd817adSflorian /* Open the output file. */
1012bfd817adSflorian if (!pipeout) {
1013bfd817adSflorian #ifndef SMALL
1014bfd817adSflorian if (resume)
1015bfd817adSflorian out = open(savefile, O_CREAT | O_WRONLY | O_APPEND,
1016bfd817adSflorian 0666);
1017bfd817adSflorian else
1018bfd817adSflorian #endif /* !SMALL */
1019bfd817adSflorian out = open(savefile, O_CREAT | O_WRONLY | O_TRUNC,
1020bfd817adSflorian 0666);
10213aaa63ebSderaadt if (out == -1) {
1022bfd817adSflorian warn("Can't open %s", savefile);
1023bfd817adSflorian goto cleanup_url_get;
1024bfd817adSflorian }
1025bfd817adSflorian } else {
1026bfd817adSflorian out = fileno(stdout);
1027bfd817adSflorian #ifdef SMALL
1028bfd817adSflorian if (lastfile) {
1029bfd817adSflorian if (pledge("stdio tty", NULL) == -1)
1030bfd817adSflorian err(1, "pledge");
1031bfd817adSflorian }
1032bfd817adSflorian #endif
1033bfd817adSflorian }
1034bfd817adSflorian
1035583394bbSjca if ((buf = malloc(buflen)) == NULL)
1036583394bbSjca errx(1, "Can't allocate memory for transfer buffer");
1037583394bbSjca
1038bfd817adSflorian /* Trap signals */
1039bfd817adSflorian oldintr = NULL;
1040bfd817adSflorian oldinti = NULL;
1041bfd817adSflorian if (setjmp(httpabort)) {
1042bfd817adSflorian if (oldintr)
1043bfd817adSflorian (void)signal(SIGINT, oldintr);
1044bfd817adSflorian if (oldinti)
1045bfd817adSflorian (void)signal(SIGINFO, oldinti);
1046bfd817adSflorian goto cleanup_url_get;
1047bfd817adSflorian }
1048bfd817adSflorian oldintr = signal(SIGINT, aborthttp);
1049bfd817adSflorian
1050bfd817adSflorian bytes = 0;
1051bfd817adSflorian hashbytes = mark;
1052bfd817adSflorian progressmeter(-1, path);
1053bfd817adSflorian
1054bfd817adSflorian /* Finally, suck down the file. */
10557102f6c2Sjca oldinti = signal(SIGINFO, psummary);
10567102f6c2Sjca if (chunked) {
10572b371687Sjca error = save_chunked(fin, tls, out, buf, buflen);
10587413ba97Sjca signal(SIGINT, oldintr);
10597102f6c2Sjca signal(SIGINFO, oldinti);
10602b371687Sjca if (error == -1)
10617102f6c2Sjca goto cleanup_url_get;
10627102f6c2Sjca } else {
1063d6fcb866Sjca while ((len = fread(buf, 1, buflen, fin)) > 0) {
1064bfd817adSflorian bytes += len;
1065d6fcb866Sjca for (cp = buf; len > 0; len -= wlen, cp += wlen) {
1066d6fcb866Sjca if ((wlen = write(out, cp, len)) == -1) {
1067bfd817adSflorian warn("Writing %s", savefile);
10687413ba97Sjca signal(SIGINT, oldintr);
1069bfd817adSflorian signal(SIGINFO, oldinti);
1070bfd817adSflorian goto cleanup_url_get;
1071d6fcb866Sjca }
1072bfd817adSflorian }
1073bfd817adSflorian if (hash && !progress) {
1074bfd817adSflorian while (bytes >= hashbytes) {
1075bfd817adSflorian (void)putc('#', ttyout);
1076bfd817adSflorian hashbytes += mark;
1077bfd817adSflorian }
1078bfd817adSflorian (void)fflush(ttyout);
1079bfd817adSflorian }
1080bfd817adSflorian }
1081d6fcb866Sjca save_errno = errno;
10827413ba97Sjca signal(SIGINT, oldintr);
1083bfd817adSflorian signal(SIGINFO, oldinti);
1084bfd817adSflorian if (hash && !progress && bytes > 0) {
1085bfd817adSflorian if (bytes < mark)
1086bfd817adSflorian (void)putc('#', ttyout);
1087bfd817adSflorian (void)putc('\n', ttyout);
1088bfd817adSflorian (void)fflush(ttyout);
1089bfd817adSflorian }
1090d6fcb866Sjca if (len == 0 && ferror(fin)) {
1091d6fcb866Sjca errno = save_errno;
109294d4d1aaSjca warnx("Reading from socket: %s", sockerror(tls));
1093bfd817adSflorian goto cleanup_url_get;
1094bfd817adSflorian }
10957102f6c2Sjca }
1096bfd817adSflorian progressmeter(1, NULL);
1097bfd817adSflorian if (
1098bfd817adSflorian #ifndef SMALL
1099bfd817adSflorian !resume &&
1100bfd817adSflorian #endif /* !SMALL */
1101bfd817adSflorian filesize != -1 && len == 0 && bytes != filesize) {
1102bfd817adSflorian if (verbose)
1103bfd817adSflorian fputs("Read short file.\n", ttyout);
1104bfd817adSflorian goto cleanup_url_get;
1105bfd817adSflorian }
1106bfd817adSflorian
1107bfd817adSflorian if (verbose)
1108bfd817adSflorian ptransfer(0);
1109bfd817adSflorian
1110bfd817adSflorian rval = 0;
1111bfd817adSflorian goto cleanup_url_get;
1112bfd817adSflorian
1113bfd817adSflorian noftpautologin:
1114bfd817adSflorian warnx(
1115bfd817adSflorian "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
1116bfd817adSflorian goto cleanup_url_get;
1117bfd817adSflorian
1118bfd817adSflorian improper:
1119bfd817adSflorian warnx("Improper response from %s", host);
1120bfd817adSflorian
1121bfd817adSflorian cleanup_url_get:
1122e4b576d7Sjca #ifndef SMALL
1123bfd817adSflorian free(full_host);
1124e4b576d7Sjca #endif /* !SMALL */
1125e4b576d7Sjca #ifndef NOSSL
1126bfd817adSflorian free(sslhost);
1127bfd817adSflorian #endif /* !NOSSL */
1128535fae4eSjca ftp_close(&fin, &tls, &fd);
112949dd5d56Srobert if (out >= 0 && out != fileno(stdout)) {
113049dd5d56Srobert #ifndef SMALL
11311e951bc7Ssthen if (server_timestamps && lmt.tm_zone != 0 &&
11321e951bc7Ssthen fstat(out, &stbuf) == 0 && S_ISREG(stbuf.st_mode) != 0) {
113349dd5d56Srobert ts[0].tv_nsec = UTIME_NOW;
113449dd5d56Srobert ts[1].tv_nsec = 0;
113549dd5d56Srobert setenv("TZ", lmt.tm_zone, 1);
113649dd5d56Srobert if (((ts[1].tv_sec = mktime(&lmt)) != -1) &&
113749dd5d56Srobert (futimens(out, ts) == -1))
113849dd5d56Srobert warnx("Unable to set file modification time");
113949dd5d56Srobert }
114049dd5d56Srobert #endif /* !SMALL */
1141bfd817adSflorian close(out);
114249dd5d56Srobert }
1143bfd817adSflorian free(buf);
11443411e43fSnaddy free(pathbuf);
1145bfd817adSflorian free(proxyhost);
1146bfd817adSflorian free(proxyurl);
1147bfd817adSflorian free(newline);
1148bfd817adSflorian free(credentials);
11497e8f63e0Syasuoka free(proxy_credentials);
1150bfd817adSflorian return (rval);
1151bfd817adSflorian }
1152bfd817adSflorian
11537102f6c2Sjca static int
save_chunked(FILE * fin,struct tls * tls,int out,char * buf,size_t buflen)11547102f6c2Sjca save_chunked(FILE *fin, struct tls *tls, int out, char *buf, size_t buflen)
11557102f6c2Sjca {
11567102f6c2Sjca
1157bb1c3b3fSnaddy char *header = NULL, *end, *cp;
11587102f6c2Sjca unsigned long chunksize;
1159bb1c3b3fSnaddy size_t hsize = 0, rlen, wlen;
11607102f6c2Sjca ssize_t written;
11617102f6c2Sjca char cr, lf;
11627102f6c2Sjca
11637102f6c2Sjca for (;;) {
1164bb1c3b3fSnaddy if (getline(&header, &hsize, fin) == -1)
11657102f6c2Sjca break;
11667102f6c2Sjca /* strip CRLF and any optional chunk extension */
116739263320Sclaudio header[strcspn(header, "; \t\r\n")] = '\0';
11687102f6c2Sjca errno = 0;
11697102f6c2Sjca chunksize = strtoul(header, &end, 16);
11707102f6c2Sjca if (errno || header[0] == '\0' || *end != '\0' ||
11717102f6c2Sjca chunksize > INT_MAX) {
11727102f6c2Sjca warnx("Invalid chunk size '%s'", header);
11737102f6c2Sjca free(header);
11747102f6c2Sjca return -1;
11757102f6c2Sjca }
11767102f6c2Sjca
11777102f6c2Sjca if (chunksize == 0) {
11787102f6c2Sjca /* We're done. Ignore optional trailer. */
1179bb1c3b3fSnaddy free(header);
11807102f6c2Sjca return 0;
11817102f6c2Sjca }
11827102f6c2Sjca
11837102f6c2Sjca for (written = 0; chunksize != 0; chunksize -= rlen) {
11847102f6c2Sjca rlen = (chunksize < buflen) ? chunksize : buflen;
11857102f6c2Sjca rlen = fread(buf, 1, rlen, fin);
11867102f6c2Sjca if (rlen == 0)
11877102f6c2Sjca break;
11887102f6c2Sjca bytes += rlen;
11897102f6c2Sjca for (cp = buf, wlen = rlen; wlen > 0;
11907102f6c2Sjca wlen -= written, cp += written) {
11917102f6c2Sjca if ((written = write(out, cp, wlen)) == -1) {
11927102f6c2Sjca warn("Writing output file");
1193bb1c3b3fSnaddy free(header);
11947102f6c2Sjca return -1;
11957102f6c2Sjca }
11967102f6c2Sjca }
11977102f6c2Sjca }
11987102f6c2Sjca
11997102f6c2Sjca if (rlen == 0 ||
12007102f6c2Sjca fread(&cr, 1, 1, fin) != 1 ||
12017102f6c2Sjca fread(&lf, 1, 1, fin) != 1)
12027102f6c2Sjca break;
12037102f6c2Sjca
12047102f6c2Sjca if (cr != '\r' || lf != '\n') {
12057102f6c2Sjca warnx("Invalid chunked encoding");
1206bb1c3b3fSnaddy free(header);
12077102f6c2Sjca return -1;
12087102f6c2Sjca }
12097102f6c2Sjca }
1210bb1c3b3fSnaddy free(header);
12117102f6c2Sjca
12127102f6c2Sjca if (ferror(fin))
12137102f6c2Sjca warnx("Error while reading from socket: %s", sockerror(tls));
12147102f6c2Sjca else
12157102f6c2Sjca warnx("Invalid chunked encoding: short read");
12167102f6c2Sjca
12177102f6c2Sjca return -1;
12187102f6c2Sjca }
12197102f6c2Sjca
1220bfd817adSflorian /*
1221bfd817adSflorian * Abort a http retrieval
1222bfd817adSflorian */
1223194d6d9dSjca static void
aborthttp(int signo)1224bfd817adSflorian aborthttp(int signo)
1225bfd817adSflorian {
12265fbde59fSkn const char errmsg[] = "\nfetch aborted.\n";
1227bfd817adSflorian
12285fbde59fSkn write(fileno(ttyout), errmsg, sizeof(errmsg) - 1);
1229bfd817adSflorian longjmp(httpabort, 1);
1230bfd817adSflorian }
1231bfd817adSflorian
1232bfd817adSflorian /*
1233bfd817adSflorian * Retrieve multiple files from the command line, transferring
1234bfd817adSflorian * files of the form "host:path", "ftp://host/path" using the
1235bfd817adSflorian * ftp protocol, and files of the form "http://host/path" using
1236bfd817adSflorian * the http protocol.
1237bfd817adSflorian * If path has a trailing "/", then return (-1);
1238bfd817adSflorian * the path will be cd-ed into and the connection remains open,
1239bfd817adSflorian * and the function will return -1 (to indicate the connection
1240bfd817adSflorian * is alive).
1241bfd817adSflorian * If an error occurs the return value will be the offset+1 in
1242bfd817adSflorian * argv[] of the file that caused a problem (i.e, argv[x]
1243bfd817adSflorian * returns x+1)
1244bfd817adSflorian * Otherwise, 0 is returned if all files retrieved successfully.
1245bfd817adSflorian */
1246bfd817adSflorian int
auto_fetch(int argc,char * argv[],char * outfile)1247bfd817adSflorian auto_fetch(int argc, char *argv[], char *outfile)
1248bfd817adSflorian {
1249bfd817adSflorian char *xargv[5];
1250bfd817adSflorian char *cp, *url, *host, *dir, *file, *portnum;
1251bfd817adSflorian char *username, *pass, *pathstart;
1252bfd817adSflorian char *ftpproxy, *httpproxy;
1253bfd817adSflorian int rval, xargc, lastfile;
1254bfd817adSflorian volatile int argpos;
1255bfd817adSflorian int dirhasglob, filehasglob, oautologin;
1256bfd817adSflorian char rempath[PATH_MAX];
1257bfd817adSflorian
1258bfd817adSflorian argpos = 0;
1259bfd817adSflorian
1260bfd817adSflorian if (setjmp(toplevel)) {
1261bfd817adSflorian if (connected)
1262bfd817adSflorian disconnect(0, NULL);
1263bfd817adSflorian return (argpos + 1);
1264bfd817adSflorian }
1265bfd817adSflorian (void)signal(SIGINT, (sig_t)intr);
1266bfd817adSflorian (void)signal(SIGPIPE, (sig_t)lostpeer);
1267bfd817adSflorian
1268bfd817adSflorian if ((ftpproxy = getenv(FTP_PROXY)) != NULL && *ftpproxy == '\0')
1269bfd817adSflorian ftpproxy = NULL;
1270bfd817adSflorian if ((httpproxy = getenv(HTTP_PROXY)) != NULL && *httpproxy == '\0')
1271bfd817adSflorian httpproxy = NULL;
1272bfd817adSflorian
1273bfd817adSflorian /*
1274bfd817adSflorian * Loop through as long as there's files to fetch.
1275bfd817adSflorian */
1276bfd817adSflorian username = pass = NULL;
1277bfd817adSflorian for (rval = 0; (rval == 0) && (argpos < argc); free(url), argpos++) {
1278bfd817adSflorian if (strchr(argv[argpos], ':') == NULL)
1279bfd817adSflorian break;
1280bfd817adSflorian
1281bfd817adSflorian free(username);
1282bfd817adSflorian free(pass);
1283bfd817adSflorian host = dir = file = portnum = username = pass = NULL;
1284bfd817adSflorian
1285bfd817adSflorian lastfile = (argv[argpos+1] == NULL);
1286bfd817adSflorian
1287bfd817adSflorian /*
1288bfd817adSflorian * We muck with the string, so we make a copy.
1289bfd817adSflorian */
1290bfd817adSflorian url = strdup(argv[argpos]);
1291bfd817adSflorian if (url == NULL)
1292bfd817adSflorian errx(1, "Can't allocate memory for auto-fetch.");
1293bfd817adSflorian
1294ba6894b2Sjca if (strncasecmp(url, FILE_URL, sizeof(FILE_URL) - 1) == 0) {
1295ba6894b2Sjca if (file_get(url + sizeof(FILE_URL) - 1, outfile) == -1)
1296ba6894b2Sjca rval = argpos + 1;
1297ba6894b2Sjca continue;
1298ba6894b2Sjca }
1299ba6894b2Sjca
1300bfd817adSflorian /*
1301ba6894b2Sjca * Try HTTP URL-style arguments next.
1302bfd817adSflorian */
1303bfd817adSflorian if (strncasecmp(url, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
1304ba6894b2Sjca strncasecmp(url, HTTPS_URL, sizeof(HTTPS_URL) -1) == 0) {
1305bfd817adSflorian redirect_loop = 0;
1306f78cba61Sjca retried = 0;
1307bfd817adSflorian if (url_get(url, httpproxy, outfile, lastfile) == -1)
1308bfd817adSflorian rval = argpos + 1;
1309bfd817adSflorian continue;
1310bfd817adSflorian }
1311bfd817adSflorian
1312bfd817adSflorian /*
1313bfd817adSflorian * Try FTP URL-style arguments next. If ftpproxy is
1314bfd817adSflorian * set, use url_get() instead of standard ftp.
1315bfd817adSflorian * Finally, try host:file.
1316bfd817adSflorian */
1317bfd817adSflorian host = url;
1318bfd817adSflorian if (strncasecmp(url, FTP_URL, sizeof(FTP_URL) - 1) == 0) {
1319bfd817adSflorian char *passend, *passagain, *userend;
1320bfd817adSflorian
1321bfd817adSflorian if (ftpproxy) {
1322bfd817adSflorian if (url_get(url, ftpproxy, outfile, lastfile) == -1)
1323bfd817adSflorian rval = argpos + 1;
1324bfd817adSflorian continue;
1325bfd817adSflorian }
1326bfd817adSflorian host += sizeof(FTP_URL) - 1;
1327bfd817adSflorian dir = strchr(host, '/');
1328bfd817adSflorian
1329bfd817adSflorian /* Look for [user:pass@]host[:port] */
1330bfd817adSflorian
1331bfd817adSflorian /* check if we have "user:pass@" */
1332bfd817adSflorian userend = strchr(host, ':');
1333bfd817adSflorian passend = strchr(host, '@');
1334bfd817adSflorian if (passend && userend && userend < passend &&
1335bfd817adSflorian (!dir || passend < dir)) {
1336bfd817adSflorian username = host;
1337bfd817adSflorian pass = userend + 1;
1338bfd817adSflorian host = passend + 1;
1339bfd817adSflorian *userend = *passend = '\0';
1340bfd817adSflorian passagain = strchr(host, '@');
1341bfd817adSflorian if (strchr(pass, '@') != NULL ||
1342bfd817adSflorian (passagain != NULL && passagain < dir)) {
1343bfd817adSflorian warnx(at_encoding_warning);
1344bfd817adSflorian username = pass = NULL;
1345bfd817adSflorian goto bad_ftp_url;
1346bfd817adSflorian }
1347bfd817adSflorian
1348bfd817adSflorian if (EMPTYSTRING(username)) {
1349bfd817adSflorian bad_ftp_url:
1350bfd817adSflorian warnx("Invalid URL: %s", argv[argpos]);
1351bfd817adSflorian rval = argpos + 1;
1352bfd817adSflorian username = pass = NULL;
1353bfd817adSflorian continue;
1354bfd817adSflorian }
1355bfd817adSflorian username = urldecode(username);
1356bfd817adSflorian pass = urldecode(pass);
1357bfd817adSflorian }
1358bfd817adSflorian
1359bfd817adSflorian /* check [host]:port, or [host] */
1360bfd817adSflorian if (host[0] == '[') {
1361bfd817adSflorian cp = strchr(host, ']');
1362bfd817adSflorian if (cp && (!dir || cp < dir)) {
1363bfd817adSflorian if (cp + 1 == dir || cp[1] == ':') {
1364bfd817adSflorian host++;
1365bfd817adSflorian *cp++ = '\0';
1366bfd817adSflorian } else
1367bfd817adSflorian cp = NULL;
1368bfd817adSflorian } else
1369bfd817adSflorian cp = host;
1370bfd817adSflorian } else
1371bfd817adSflorian cp = host;
1372bfd817adSflorian
1373bfd817adSflorian /* split off host[:port] if there is */
1374bfd817adSflorian if (cp) {
1375bfd817adSflorian portnum = strchr(cp, ':');
1376bfd817adSflorian pathstart = strchr(cp, '/');
1377bfd817adSflorian /* : in path is not a port # indicator */
1378bfd817adSflorian if (portnum && pathstart &&
1379bfd817adSflorian pathstart < portnum)
1380bfd817adSflorian portnum = NULL;
1381bfd817adSflorian
1382bfd817adSflorian if (!portnum)
1383bfd817adSflorian ;
1384bfd817adSflorian else {
1385bfd817adSflorian if (!dir)
1386bfd817adSflorian ;
1387bfd817adSflorian else if (portnum + 1 < dir) {
1388bfd817adSflorian *portnum++ = '\0';
1389bfd817adSflorian /*
1390bfd817adSflorian * XXX should check if portnum
1391bfd817adSflorian * is decimal number
1392bfd817adSflorian */
1393bfd817adSflorian } else {
1394bfd817adSflorian /* empty portnum */
1395bfd817adSflorian goto bad_ftp_url;
1396bfd817adSflorian }
1397bfd817adSflorian }
1398bfd817adSflorian } else
1399bfd817adSflorian portnum = NULL;
1400bfd817adSflorian } else { /* classic style `host:file' */
1401bfd817adSflorian dir = strchr(host, ':');
1402bfd817adSflorian }
1403bfd817adSflorian if (EMPTYSTRING(host)) {
1404bfd817adSflorian rval = argpos + 1;
1405bfd817adSflorian continue;
1406bfd817adSflorian }
1407bfd817adSflorian
1408bfd817adSflorian /*
1409bfd817adSflorian * If dir is NULL, the file wasn't specified
1410bfd817adSflorian * (URL looked something like ftp://host)
1411bfd817adSflorian */
1412bfd817adSflorian if (dir != NULL)
1413bfd817adSflorian *dir++ = '\0';
1414bfd817adSflorian
1415bfd817adSflorian /*
1416bfd817adSflorian * Extract the file and (if present) directory name.
1417bfd817adSflorian */
1418bfd817adSflorian if (!EMPTYSTRING(dir)) {
1419bfd817adSflorian cp = strrchr(dir, '/');
1420bfd817adSflorian if (cp != NULL) {
1421bfd817adSflorian *cp++ = '\0';
1422bfd817adSflorian file = cp;
1423bfd817adSflorian } else {
1424bfd817adSflorian file = dir;
1425bfd817adSflorian dir = NULL;
1426bfd817adSflorian }
1427bfd817adSflorian }
1428bfd817adSflorian #ifndef SMALL
1429bfd817adSflorian if (debug)
1430bfd817adSflorian fprintf(ttyout,
1431bfd817adSflorian "user %s:%s host %s port %s dir %s file %s\n",
1432bfd817adSflorian username, pass ? "XXXX" : NULL, host, portnum,
1433bfd817adSflorian dir, file);
1434bfd817adSflorian #endif /* !SMALL */
1435bfd817adSflorian
1436bfd817adSflorian /*
1437bfd817adSflorian * Set up the connection.
1438bfd817adSflorian */
1439bfd817adSflorian if (connected)
1440bfd817adSflorian disconnect(0, NULL);
1441bfd817adSflorian xargv[0] = __progname;
1442bfd817adSflorian xargv[1] = host;
1443bfd817adSflorian xargv[2] = NULL;
1444bfd817adSflorian xargc = 2;
1445bfd817adSflorian if (!EMPTYSTRING(portnum)) {
1446bfd817adSflorian xargv[2] = portnum;
1447bfd817adSflorian xargv[3] = NULL;
1448bfd817adSflorian xargc = 3;
1449bfd817adSflorian }
1450bfd817adSflorian oautologin = autologin;
1451bfd817adSflorian if (username == NULL)
1452bfd817adSflorian anonftp = 1;
1453bfd817adSflorian else {
1454bfd817adSflorian anonftp = 0;
1455bfd817adSflorian autologin = 0;
1456bfd817adSflorian }
1457bfd817adSflorian setpeer(xargc, xargv);
1458bfd817adSflorian autologin = oautologin;
1459bfd817adSflorian if (connected == 0 ||
1460bfd817adSflorian (connected == 1 && autologin && (username == NULL ||
1461bfd817adSflorian !ftp_login(host, username, pass)))) {
1462bfd817adSflorian warnx("Can't connect or login to host `%s'", host);
1463bfd817adSflorian rval = argpos + 1;
1464bfd817adSflorian continue;
1465bfd817adSflorian }
1466bfd817adSflorian
1467bfd817adSflorian /* Always use binary transfers. */
1468bfd817adSflorian setbinary(0, NULL);
1469bfd817adSflorian
1470bfd817adSflorian dirhasglob = filehasglob = 0;
1471bfd817adSflorian if (doglob) {
1472bfd817adSflorian if (!EMPTYSTRING(dir) &&
1473bfd817adSflorian strpbrk(dir, "*?[]{}") != NULL)
1474bfd817adSflorian dirhasglob = 1;
1475bfd817adSflorian if (!EMPTYSTRING(file) &&
1476bfd817adSflorian strpbrk(file, "*?[]{}") != NULL)
1477bfd817adSflorian filehasglob = 1;
1478bfd817adSflorian }
1479bfd817adSflorian
1480bfd817adSflorian /* Change directories, if necessary. */
1481bfd817adSflorian if (!EMPTYSTRING(dir) && !dirhasglob) {
1482bfd817adSflorian xargv[0] = "cd";
1483bfd817adSflorian xargv[1] = dir;
1484bfd817adSflorian xargv[2] = NULL;
1485bfd817adSflorian cd(2, xargv);
1486bfd817adSflorian if (!dirchange) {
1487bfd817adSflorian rval = argpos + 1;
1488bfd817adSflorian continue;
1489bfd817adSflorian }
1490bfd817adSflorian }
1491bfd817adSflorian
1492bfd817adSflorian if (EMPTYSTRING(file)) {
1493bfd817adSflorian #ifndef SMALL
1494bfd817adSflorian rval = -1;
1495bfd817adSflorian #else /* !SMALL */
1496bfd817adSflorian recvrequest("NLST", "-", NULL, "w", 0, 0);
1497bfd817adSflorian rval = 0;
1498bfd817adSflorian #endif /* !SMALL */
1499bfd817adSflorian continue;
1500bfd817adSflorian }
1501bfd817adSflorian
1502bfd817adSflorian if (verbose)
1503bfd817adSflorian fprintf(ttyout, "Retrieving %s/%s\n", dir ? dir : "", file);
1504bfd817adSflorian
1505bfd817adSflorian if (dirhasglob) {
1506bfd817adSflorian snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
1507bfd817adSflorian file = rempath;
1508bfd817adSflorian }
1509bfd817adSflorian
1510bfd817adSflorian /* Fetch the file(s). */
1511bfd817adSflorian xargc = 2;
1512bfd817adSflorian xargv[0] = "get";
1513bfd817adSflorian xargv[1] = file;
1514bfd817adSflorian xargv[2] = NULL;
1515bfd817adSflorian if (dirhasglob || filehasglob) {
1516bfd817adSflorian int ointeractive;
1517bfd817adSflorian
1518bfd817adSflorian ointeractive = interactive;
1519bfd817adSflorian interactive = 0;
1520bfd817adSflorian xargv[0] = "mget";
1521bfd817adSflorian #ifndef SMALL
1522bfd817adSflorian if (resume) {
1523bfd817adSflorian xargc = 3;
1524bfd817adSflorian xargv[1] = "-c";
1525bfd817adSflorian xargv[2] = file;
1526bfd817adSflorian xargv[3] = NULL;
1527bfd817adSflorian }
1528bfd817adSflorian #endif /* !SMALL */
1529bfd817adSflorian mget(xargc, xargv);
1530bfd817adSflorian interactive = ointeractive;
1531bfd817adSflorian } else {
1532bfd817adSflorian if (outfile != NULL) {
1533bfd817adSflorian xargv[2] = outfile;
1534bfd817adSflorian xargv[3] = NULL;
1535bfd817adSflorian xargc++;
1536bfd817adSflorian }
1537bfd817adSflorian #ifndef SMALL
1538bfd817adSflorian if (resume)
1539bfd817adSflorian reget(xargc, xargv);
1540bfd817adSflorian else
1541bfd817adSflorian #endif /* !SMALL */
1542bfd817adSflorian get(xargc, xargv);
1543bfd817adSflorian }
1544bfd817adSflorian
1545bfd817adSflorian if ((code / 100) != COMPLETE)
1546bfd817adSflorian rval = argpos + 1;
1547bfd817adSflorian }
1548bfd817adSflorian if (connected && rval != -1)
1549bfd817adSflorian disconnect(0, NULL);
1550bfd817adSflorian return (rval);
1551bfd817adSflorian }
1552bfd817adSflorian
1553bfd817adSflorian char *
urldecode(const char * str)1554bfd817adSflorian urldecode(const char *str)
1555bfd817adSflorian {
1556bfd817adSflorian char *ret, c;
1557bfd817adSflorian int i, reallen;
1558bfd817adSflorian
1559bfd817adSflorian if (str == NULL)
1560bfd817adSflorian return NULL;
1561bfd817adSflorian if ((ret = malloc(strlen(str) + 1)) == NULL)
1562bfd817adSflorian err(1, "Can't allocate memory for URL decoding");
1563bfd817adSflorian for (i = 0, reallen = 0; str[i] != '\0'; i++, reallen++, ret++) {
1564bfd817adSflorian c = str[i];
1565bfd817adSflorian if (c == '+') {
1566bfd817adSflorian *ret = ' ';
1567bfd817adSflorian continue;
1568bfd817adSflorian }
1569bfd817adSflorian
1570bfd817adSflorian /* Cannot use strtol here because next char
1571bfd817adSflorian * after %xx may be a digit.
1572bfd817adSflorian */
1573bfd817adSflorian if (c == '%' && isxdigit((unsigned char)str[i + 1]) &&
1574bfd817adSflorian isxdigit((unsigned char)str[i + 2])) {
1575bfd817adSflorian *ret = hextochar(&str[i + 1]);
1576bfd817adSflorian i += 2;
1577bfd817adSflorian continue;
1578bfd817adSflorian }
1579bfd817adSflorian *ret = c;
1580bfd817adSflorian }
1581bfd817adSflorian *ret = '\0';
1582bfd817adSflorian
1583bfd817adSflorian return ret - reallen;
1584bfd817adSflorian }
1585bfd817adSflorian
1586194d6d9dSjca static char *
recode_credentials(const char * userinfo)1587bfd817adSflorian recode_credentials(const char *userinfo)
1588bfd817adSflorian {
1589bfd817adSflorian char *ui, *creds;
1590bfd817adSflorian size_t ulen, credsize;
1591bfd817adSflorian
1592bfd817adSflorian /* url-decode the user and pass */
1593bfd817adSflorian ui = urldecode(userinfo);
1594bfd817adSflorian
1595bfd817adSflorian ulen = strlen(ui);
1596bfd817adSflorian credsize = (ulen + 2) / 3 * 4 + 1;
1597bfd817adSflorian creds = malloc(credsize);
1598bfd817adSflorian if (creds == NULL)
1599bfd817adSflorian errx(1, "out of memory");
1600bfd817adSflorian if (b64_ntop(ui, ulen, creds, credsize) == -1)
1601bfd817adSflorian errx(1, "error in base64 encoding");
1602bfd817adSflorian free(ui);
1603bfd817adSflorian return (creds);
1604bfd817adSflorian }
1605bfd817adSflorian
1606194d6d9dSjca static char
hextochar(const char * str)1607bfd817adSflorian hextochar(const char *str)
1608bfd817adSflorian {
1609bfd817adSflorian unsigned char c, ret;
1610bfd817adSflorian
1611bfd817adSflorian c = str[0];
1612bfd817adSflorian ret = c;
1613bfd817adSflorian if (isalpha(c))
1614bfd817adSflorian ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
1615bfd817adSflorian else
1616bfd817adSflorian ret -= '0';
1617bfd817adSflorian ret *= 16;
1618bfd817adSflorian
1619bfd817adSflorian c = str[1];
1620bfd817adSflorian ret += c;
1621bfd817adSflorian if (isalpha(c))
1622bfd817adSflorian ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
1623bfd817adSflorian else
1624bfd817adSflorian ret -= '0';
1625bfd817adSflorian return ret;
1626bfd817adSflorian }
1627bfd817adSflorian
1628bfd817adSflorian int
isurl(const char * p)1629bfd817adSflorian isurl(const char *p)
1630bfd817adSflorian {
1631bfd817adSflorian
1632bfd817adSflorian if (strncasecmp(p, FTP_URL, sizeof(FTP_URL) - 1) == 0 ||
1633bfd817adSflorian strncasecmp(p, HTTP_URL, sizeof(HTTP_URL) - 1) == 0 ||
1634bfd817adSflorian #ifndef NOSSL
1635bfd817adSflorian strncasecmp(p, HTTPS_URL, sizeof(HTTPS_URL) - 1) == 0 ||
1636bfd817adSflorian #endif /* !NOSSL */
1637bfd817adSflorian strncasecmp(p, FILE_URL, sizeof(FILE_URL) - 1) == 0 ||
1638bfd817adSflorian strstr(p, ":/"))
1639bfd817adSflorian return (1);
1640bfd817adSflorian return (0);
1641bfd817adSflorian }
1642bfd817adSflorian
1643b237d516Sjca #ifndef SMALL
1644b237d516Sjca static int
ftp_printf(FILE * fp,const char * fmt,...)1645b237d516Sjca ftp_printf(FILE *fp, const char *fmt, ...)
1646b237d516Sjca {
1647b237d516Sjca va_list ap;
1648b237d516Sjca int ret;
1649b237d516Sjca
1650b237d516Sjca va_start(ap, fmt);
1651b237d516Sjca ret = vfprintf(fp, fmt, ap);
1652b237d516Sjca va_end(ap);
1653b237d516Sjca
1654b237d516Sjca if (debug) {
1655b237d516Sjca va_start(ap, fmt);
1656b237d516Sjca vfprintf(ttyout, fmt, ap);
1657b237d516Sjca va_end(ap);
1658b237d516Sjca }
1659b237d516Sjca
1660b237d516Sjca return ret;
1661b237d516Sjca }
1662b237d516Sjca #endif /* !SMALL */
1663b237d516Sjca
1664194d6d9dSjca static void
ftp_close(FILE ** fin,struct tls ** tls,int * fd)1665583394bbSjca ftp_close(FILE **fin, struct tls **tls, int *fd)
1666535fae4eSjca {
1667535fae4eSjca #ifndef NOSSL
1668535fae4eSjca int ret;
1669535fae4eSjca
1670535fae4eSjca if (*tls != NULL) {
1671535fae4eSjca if (tls_session_fd != -1)
1672535fae4eSjca dprintf(STDERR_FILENO, "tls session resumed: %s\n",
1673535fae4eSjca tls_conn_session_resumed(*tls) ? "yes" : "no");
1674535fae4eSjca do {
1675535fae4eSjca ret = tls_close(*tls);
1676535fae4eSjca } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
1677535fae4eSjca tls_free(*tls);
1678535fae4eSjca *tls = NULL;
1679535fae4eSjca }
168094d4d1aaSjca if (*fd != -1) {
168194d4d1aaSjca close(*fd);
168294d4d1aaSjca *fd = -1;
168394d4d1aaSjca }
1684535fae4eSjca #endif
1685535fae4eSjca if (*fin != NULL) {
1686535fae4eSjca fclose(*fin);
1687535fae4eSjca *fin = NULL;
1688535fae4eSjca }
1689535fae4eSjca }
169094d4d1aaSjca
1691194d6d9dSjca static const char *
sockerror(struct tls * tls)169294d4d1aaSjca sockerror(struct tls *tls)
169394d4d1aaSjca {
169494d4d1aaSjca int save_errno = errno;
169594d4d1aaSjca #ifndef NOSSL
169694d4d1aaSjca if (tls != NULL) {
169794d4d1aaSjca const char *tlserr = tls_error(tls);
169894d4d1aaSjca if (tlserr != NULL)
169994d4d1aaSjca return tlserr;
170094d4d1aaSjca }
170194d4d1aaSjca #endif
170294d4d1aaSjca return strerror(save_errno);
1703535fae4eSjca }
1704535fae4eSjca
1705bfd817adSflorian #ifndef NOSSL
1706194d6d9dSjca static int
proxy_connect(int socket,char * host,char * cookie)1707bfd817adSflorian proxy_connect(int socket, char *host, char *cookie)
1708bfd817adSflorian {
1709bfd817adSflorian int l;
1710bfd817adSflorian char buf[1024];
17118a4b8ac2Syasuoka char *connstr, *hosttail, *port;
1712bfd817adSflorian
1713bfd817adSflorian if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL &&
1714bfd817adSflorian (hosttail[1] == '\0' || hosttail[1] == ':')) {
1715bfd817adSflorian host++;
1716bfd817adSflorian *hosttail++ = '\0';
1717bfd817adSflorian } else
1718bfd817adSflorian hosttail = host;
1719bfd817adSflorian
1720bfd817adSflorian port = strrchr(hosttail, ':'); /* find portnum */
1721bfd817adSflorian if (port != NULL)
1722bfd817adSflorian *port++ = '\0';
1723bfd817adSflorian if (!port)
1724bfd817adSflorian port = "443";
1725bfd817adSflorian
1726bfd817adSflorian if (cookie) {
1727bfd817adSflorian l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n"
1728*8cc1b8faSsthen "Host: %s:%s\r\n"
1729bfd817adSflorian "Proxy-Authorization: Basic %s\r\n%s\r\n\r\n",
1730*8cc1b8faSsthen host, port, host, port, cookie, HTTP_USER_AGENT);
1731bfd817adSflorian } else {
1732*8cc1b8faSsthen l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n"
1733*8cc1b8faSsthen "Host: %s:%s\r\n%s\r\n\r\n",
1734*8cc1b8faSsthen host, port, host, port, HTTP_USER_AGENT);
1735bfd817adSflorian }
1736bfd817adSflorian
1737bfd817adSflorian if (l == -1)
1738bfd817adSflorian errx(1, "Could not allocate memory to assemble connect string!");
1739bfd817adSflorian #ifndef SMALL
1740bfd817adSflorian if (debug)
1741bfd817adSflorian printf("%s", connstr);
1742bfd817adSflorian #endif /* !SMALL */
1743bfd817adSflorian if (write(socket, connstr, l) != l)
1744bfd817adSflorian err(1, "Could not send connect string");
17458a4b8ac2Syasuoka read(socket, &buf, sizeof(buf)); /* only proxy header XXX: error handling? */
1746bfd817adSflorian free(connstr);
1747bfd817adSflorian return(200);
1748bfd817adSflorian }
174994d4d1aaSjca
1750194d6d9dSjca static int
stdio_tls_write_wrapper(void * arg,const char * buf,int len)175194d4d1aaSjca stdio_tls_write_wrapper(void *arg, const char *buf, int len)
175294d4d1aaSjca {
175394d4d1aaSjca struct tls *tls = arg;
175494d4d1aaSjca ssize_t ret;
175594d4d1aaSjca
175694d4d1aaSjca do {
175794d4d1aaSjca ret = tls_write(tls, buf, len);
175894d4d1aaSjca } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
175994d4d1aaSjca
176094d4d1aaSjca return ret;
176194d4d1aaSjca }
176294d4d1aaSjca
1763194d6d9dSjca static int
stdio_tls_read_wrapper(void * arg,char * buf,int len)176494d4d1aaSjca stdio_tls_read_wrapper(void *arg, char *buf, int len)
176594d4d1aaSjca {
176694d4d1aaSjca struct tls *tls = arg;
176794d4d1aaSjca ssize_t ret;
176894d4d1aaSjca
176994d4d1aaSjca do {
177094d4d1aaSjca ret = tls_read(tls, buf, len);
177194d4d1aaSjca } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
177294d4d1aaSjca
177394d4d1aaSjca return ret;
177494d4d1aaSjca }
1775bfd817adSflorian #endif /* !NOSSL */
1776