Bug Summary

File:src/usr.bin/ftp/fetch.c
Warning:line 461, column 4
Potential leak of memory pointed to by 'sslpath'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.4 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name fetch.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/usr.bin/ftp/obj -resource-dir /usr/local/llvm16/lib/clang/16 -internal-isystem /usr/local/llvm16/lib/clang/16/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.bin/ftp/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fcf-protection=branch -fno-jump-tables -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/scan/2024-01-11-140451-98009-1 -x c /usr/src/usr.bin/ftp/fetch.c
1/* $OpenBSD: fetch.c,v 1.216 2023/06/28 17:35:06 op Exp $ */
2/* $NetBSD: fetch.c,v 1.14 1997/08/18 10:20:20 lukem Exp $ */
3
4/*-
5 * Copyright (c) 1997 The NetBSD Foundation, Inc.
6 * All rights reserved.
7 *
8 * This code is derived from software contributed to The NetBSD Foundation
9 * by Jason Thorpe and Luke Mewburn.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33/*
34 * FTP User Program -- Command line file retrieval
35 */
36
37#include <sys/types.h>
38#include <sys/socket.h>
39#include <sys/stat.h>
40
41#include <netinet/in.h>
42
43#include <arpa/ftp.h>
44#include <arpa/inet.h>
45
46#include <ctype.h>
47#include <err.h>
48#include <libgen.h>
49#include <netdb.h>
50#include <fcntl.h>
51#include <signal.h>
52#include <vis.h>
53#include <stdio.h>
54#include <stdarg.h>
55#include <errno(*__errno()).h>
56#include <stdlib.h>
57#include <string.h>
58#include <unistd.h>
59#include <resolv.h>
60#include <utime.h>
61
62#ifndef NOSSL
63#include <tls.h>
64#else /* !NOSSL */
65struct tls;
66#endif /* !NOSSL */
67
68#include "ftp_var.h"
69#include "cmds.h"
70
71static int file_get(const char *, const char *);
72static int url_get(const char *, const char *, const char *, int);
73static int save_chunked(FILE *, struct tls *, int , char *, size_t);
74static void aborthttp(int);
75static char hextochar(const char *);
76static char *urldecode(const char *);
77static char *recode_credentials(const char *_userinfo);
78static void ftp_close(FILE **, struct tls **, int *);
79static const char *sockerror(struct tls *);
80#ifdef SMALL
81#define ftp_printf(fp, ...) fprintf(fp, __VA_ARGS__)
82#else
83static int ftp_printf(FILE *, const char *, ...);
84#endif /* SMALL */
85#ifndef NOSSL
86static int proxy_connect(int, char *, char *);
87static int stdio_tls_write_wrapper(void *, const char *, int);
88static int stdio_tls_read_wrapper(void *, char *, int);
89#endif /* !NOSSL */
90
91#define FTP_URL"ftp://" "ftp://" /* ftp URL prefix */
92#define HTTP_URL"http://" "http://" /* http URL prefix */
93#define HTTPS_URL"https://" "https://" /* https URL prefix */
94#define FILE_URL"file:" "file:" /* file URL prefix */
95#define FTP_PROXY"ftp_proxy" "ftp_proxy" /* env var with ftp proxy location */
96#define HTTP_PROXY"http_proxy" "http_proxy" /* env var with http proxy location */
97
98#define EMPTYSTRING(x)((x) == ((void *)0) || (*(x) == '\0')) ((x) == NULL((void *)0) || (*(x) == '\0'))
99
100static const char at_encoding_warning[] =
101 "Extra `@' characters in usernames and passwords should be encoded as %%40";
102
103static jmp_buf httpabort;
104
105static int redirect_loop;
106static int retried;
107
108/*
109 * Determine whether the character needs encoding, per RFC2396.
110 */
111static int
112to_encode(const char *c0)
113{
114 /* 2.4.3. Excluded US-ASCII Characters */
115 const char *excluded_chars =
116 " " /* space */
117 "<>#\"" /* delims (modulo "%", see below) */
118 "{}|\\^[]`" /* unwise */
119 ;
120 const unsigned char *c = (const unsigned char *)c0;
121
122 /*
123 * No corresponding graphic US-ASCII.
124 * Control characters and octets not used in US-ASCII.
125 */
126 return (iscntrl(*c) || !isascii(*c) ||
127
128 /*
129 * '%' is also reserved, if is not followed by two
130 * hexadecimal digits.
131 */
132 strchr(excluded_chars, *c) != NULL((void *)0) ||
133 (*c == '%' && (!isxdigit(c[1]) || !isxdigit(c[2]))));
134}
135
136/*
137 * Encode given URL, per RFC2396.
138 * Allocate and return string to the caller.
139 */
140static char *
141url_encode(const char *path)
142{
143 size_t i, length, new_length;
144 char *epath, *epathp;
145
146 length = new_length = strlen(path);
147
148 /*
149 * First pass:
150 * Count characters to encode and determine length of the final URL.
151 */
152 for (i = 0; i < length; i++)
153 if (to_encode(path + i))
154 new_length += 2;
155
156 epath = epathp = malloc(new_length + 1); /* One more for '\0'. */
157 if (epath == NULL((void *)0))
158 err(1, "Can't allocate memory for URL encoding");
159
160 /*
161 * Second pass:
162 * Encode, and copy final URL.
163 */
164 for (i = 0; i < length; i++)
165 if (to_encode(path + i)) {
166 snprintf(epathp, 4, "%%" "%02x",
167 (unsigned char)path[i]);
168 epathp += 3;
169 } else
170 *(epathp++) = path[i];
171
172 *epathp = '\0';
173 return (epath);
174}
175
176/*
177 * Copy a local file (used by the OpenBSD installer).
178 * Returns -1 on failure, 0 on success
179 */
180static int
181file_get(const char *path, const char *outfile)
182{
183 struct stat st;
184 int fd, out = -1, rval = -1, save_errno;
185 volatile sig_t oldintr, oldinti;
186 const char *savefile;
187 char *buf = NULL((void *)0), *cp, *pathbuf = NULL((void *)0);
188 const size_t buflen = 128 * 1024;
189 off_t hashbytes;
190 ssize_t len, wlen;
191
192 direction = "received";
193
194 fd = open(path, O_RDONLY0x0000);
195 if (fd == -1) {
196 warn("Can't open file %s", path);
197 return -1;
198 }
199
200 if (fstat(fd, &st) == -1)
201 filesize = -1;
202 else
203 filesize = st.st_size;
204
205 if (outfile != NULL((void *)0))
206 savefile = outfile;
207 else {
208 if (path[strlen(path) - 1] == '/') /* Consider no file */
209 savefile = NULL((void *)0); /* after dir invalid. */
210 else {
211 pathbuf = strdup(path);
212 if (pathbuf == NULL((void *)0))
213 errx(1, "Can't allocate memory for filename");
214 savefile = basename(pathbuf);
215 }
216 }
217
218 if (EMPTYSTRING(savefile)((savefile) == ((void *)0) || (*(savefile) == '\0'))) {
219 warnx("No filename after directory (use -o): %s", path);
220 goto cleanup_copy;
221 }
222
223 /* Open the output file. */
224 if (!pipeout) {
225 out = open(savefile, O_CREAT0x0200 | O_WRONLY0x0001 | O_TRUNC0x0400, 0666);
226 if (out == -1) {
227 warn("Can't open %s", savefile);
228 goto cleanup_copy;
229 }
230 } else
231 out = fileno(stdout)(!__isthreaded ? (((&__sF[1]))->_file) : (fileno)((&
__sF[1])))
;
232
233 if ((buf = malloc(buflen)) == NULL((void *)0))
234 errx(1, "Can't allocate memory for transfer buffer");
235
236 /* Trap signals */
237 oldintr = NULL((void *)0);
238 oldinti = NULL((void *)0);
239 if (setjmp(httpabort)) {
240 if (oldintr)
241 (void)signal(SIGINT2, oldintr);
242 if (oldinti)
243 (void)signal(SIGINFO29, oldinti);
244 goto cleanup_copy;
245 }
246 oldintr = signal(SIGINT2, aborthttp);
247
248 bytes = 0;
249 hashbytes = mark;
250 progressmeter(-1, path);
251
252 /* Finally, suck down the file. */
253 oldinti = signal(SIGINFO29, psummary);
254 while ((len = read(fd, buf, buflen)) > 0) {
255 bytes += len;
256 for (cp = buf; len > 0; len -= wlen, cp += wlen) {
257 if ((wlen = write(out, cp, len)) == -1) {
258 warn("Writing %s", savefile);
259 signal(SIGINT2, oldintr);
260 signal(SIGINFO29, oldinti);
261 goto cleanup_copy;
262 }
263 }
264 if (hash && !progress) {
265 while (bytes >= hashbytes) {
266 (void)putc('#', ttyout)(!__isthreaded ? __sputc('#', ttyout) : (putc)('#', ttyout));
267 hashbytes += mark;
268 }
269 (void)fflush(ttyout);
270 }
271 }
272 save_errno = errno(*__errno());
273 signal(SIGINT2, oldintr);
274 signal(SIGINFO29, oldinti);
275 if (hash && !progress && bytes > 0) {
276 if (bytes < mark)
277 (void)putc('#', ttyout)(!__isthreaded ? __sputc('#', ttyout) : (putc)('#', ttyout));
278 (void)putc('\n', ttyout)(!__isthreaded ? __sputc('\n', ttyout) : (putc)('\n', ttyout)
)
;
279 (void)fflush(ttyout);
280 }
281 if (len == -1) {
282 warnc(save_errno, "Reading from file");
283 goto cleanup_copy;
284 }
285 progressmeter(1, NULL((void *)0));
286 if (verbose)
287 ptransfer(0);
288
289 rval = 0;
290
291cleanup_copy:
292 free(buf);
293 free(pathbuf);
294 if (out >= 0 && out != fileno(stdout)(!__isthreaded ? (((&__sF[1]))->_file) : (fileno)((&
__sF[1])))
)
295 close(out);
296 close(fd);
297
298 return rval;
299}
300
301/*
302 * Retrieve URL, via the proxy in $proxyvar if necessary.
303 * Returns -1 on failure, 0 on success
304 */
305static int
306url_get(const char *origline, const char *proxyenv, const char *outfile, int lastfile)
307{
308 char pbuf[NI_MAXSERV32], hbuf[NI_MAXHOST256], *cp, *portnum, *path, ststr[4];
309 char *hosttail, *cause = "unknown", *newline, *host, *port, *buf = NULL((void *)0);
310 char *epath, *redirurl, *loctail, *h, *p, gerror[200];
311 int error, isftpurl = 0, isredirect = 0, rval = -1;
312 int isunavail = 0, retryafter = -1;
313 struct addrinfo hints, *res0, *res;
314 const char *savefile;
315 char *pathbuf = NULL((void *)0);
316 char *proxyurl = NULL((void *)0);
317 char *credentials = NULL((void *)0), *proxy_credentials = NULL((void *)0);
318 int fd = -1, out = -1;
319 volatile sig_t oldintr, oldinti;
320 FILE *fin = NULL((void *)0);
321 off_t hashbytes;
322 const char *errstr;
323 ssize_t len, wlen;
324 size_t bufsize;
325 char *proxyhost = NULL((void *)0);
326#ifndef NOSSL
327 char *sslpath = NULL((void *)0), *sslhost = NULL((void *)0);
328 int ishttpsurl = 0;
329#endif /* !NOSSL */
330#ifndef SMALL
331 char *full_host = NULL((void *)0);
332 const char *scheme;
333 char *locbase;
334 struct addrinfo *ares = NULL((void *)0);
335 char tmbuf[32];
336 time_t mtime = 0;
337 struct stat stbuf;
338 struct tm lmt = { 0 };
339 struct timespec ts[2];
340#endif /* !SMALL */
341 struct tls *tls = NULL((void *)0);
342 int status;
343 int save_errno;
344 const size_t buflen = 128 * 1024;
345 int chunked = 0;
346
347 direction = "received";
348
349 newline = strdup(origline);
350 if (newline == NULL((void *)0))
1
Assuming 'newline' is not equal to NULL
2
Taking false branch
351 errx(1, "Can't allocate memory to parse URL");
352 if (strncasecmp(newline, HTTP_URL"http://", sizeof(HTTP_URL"http://") - 1) == 0) {
3
Assuming the condition is false
4
Taking false branch
353 host = newline + sizeof(HTTP_URL"http://") - 1;
354#ifndef SMALL
355 scheme = HTTP_URL"http://";
356#endif /* !SMALL */
357 } else if (strncasecmp(newline, FTP_URL"ftp://", sizeof(FTP_URL"ftp://") - 1) == 0) {
5
Assuming the condition is false
6
Taking false branch
358 host = newline + sizeof(FTP_URL"ftp://") - 1;
359 isftpurl = 1;
360#ifndef SMALL
361 scheme = FTP_URL"ftp://";
362#endif /* !SMALL */
363 } else if (strncasecmp(newline, HTTPS_URL"https://", sizeof(HTTPS_URL"https://") - 1) == 0) {
7
Taking true branch
364#ifndef NOSSL
365 host = newline + sizeof(HTTPS_URL"https://") - 1;
366 ishttpsurl = 1;
367#else
368 errx(1, "%s: No HTTPS support", newline);
369#endif /* !NOSSL */
370#ifndef SMALL
371 scheme = HTTPS_URL"https://";
372#endif /* !SMALL */
373 } else
374 errx(1, "%s: URL not permitted", newline);
375
376 path = strchr(host, '/'); /* Find path */
377
378 /*
379 * Look for auth header in host.
380 * Basic auth from RFC 2617, valid characters for path are in
381 * RFC 3986 section 3.3.
382 */
383 if (!isftpurl
7.1
'isftpurl' is 0
) {
8
Taking true branch
384 p = strchr(host, '@');
385 if (p != NULL((void *)0) && (path == NULL((void *)0) || p < path)) {
9
Assuming 'p' is equal to NULL
386 *p++ = '\0';
387 credentials = recode_credentials(host);
388
389 /* Overwrite userinfo */
390 memmove(host, p, strlen(p) + 1);
391 path = strchr(host, '/');
392 }
393 }
394
395 if (EMPTYSTRING(path)((path) == ((void *)0) || (*(path) == '\0'))) {
10
Assuming 'path' is equal to null
396 if (outfile) { /* No slash, but */
11
Assuming 'outfile' is non-null
12
Taking true branch
397 path = strchr(host,'\0'); /* we have outfile. */
398 goto noslash;
13
Control jumps to line 414
399 }
400 if (isftpurl)
401 goto noftpautologin;
402 warnx("No `/' after host (use -o): %s", origline);
403 goto cleanup_url_get;
404 }
405 *path++ = '\0';
406 if (EMPTYSTRING(path)((path) == ((void *)0) || (*(path) == '\0')) && !outfile) {
407 if (isftpurl)
408 goto noftpautologin;
409 warnx("No filename after host (use -o): %s", origline);
410 goto cleanup_url_get;
411 }
412
413noslash:
414 if (outfile
13.1
'outfile' is non-null
)
14
Taking true branch
415 savefile = outfile;
416 else {
417 if (path[strlen(path) - 1] == '/') /* Consider no file */
418 savefile = NULL((void *)0); /* after dir invalid. */
419 else {
420 pathbuf = strdup(path);
421 if (pathbuf == NULL((void *)0))
422 errx(1, "Can't allocate memory for filename");
423 savefile = basename(pathbuf);
424 }
425 }
426
427 if (EMPTYSTRING(savefile)((savefile) == ((void *)0) || (*(savefile) == '\0'))) {
15
Assuming the condition is false
428 if (isftpurl)
429 goto noftpautologin;
430 warnx("No filename after directory (use -o): %s", origline);
431 goto cleanup_url_get;
432 }
433
434#ifndef SMALL
435 if (resume && pipeout) {
16
Assuming 'resume' is 0
436 warnx("can't append to stdout");
437 goto cleanup_url_get;
438 }
439#endif /* !SMALL */
440
441 if (proxyenv != NULL((void *)0)) { /* use proxy */
17
Assuming 'proxyenv' is not equal to NULL
18
Taking true branch
442#ifndef NOSSL
443 if (ishttpsurl
18.1
'ishttpsurl' is 1
) {
19
Taking true branch
444 sslpath = strdup(path);
20
Memory is allocated
445 sslhost = strdup(host);
446 if (! sslpath || ! sslhost)
21
Assuming 'sslpath' is non-null
22
Assuming 'sslhost' is non-null
23
Taking false branch
447 errx(1, "Can't allocate memory for https path/host.");
448 }
449#endif /* !NOSSL */
450 proxyhost = strdup(host);
451 if (proxyhost == NULL((void *)0))
24
Assuming 'proxyhost' is not equal to NULL
25
Taking false branch
452 errx(1, "Can't allocate memory for proxy host.");
453 proxyurl = strdup(proxyenv);
454 if (proxyurl == NULL((void *)0))
26
Assuming 'proxyurl' is not equal to NULL
27
Taking false branch
455 errx(1, "Can't allocate memory for proxy URL.");
456 if (strncasecmp(proxyurl, HTTP_URL"http://", sizeof(HTTP_URL"http://") - 1) == 0)
28
Assuming the condition is false
29
Taking false branch
457 host = proxyurl + sizeof(HTTP_URL"http://") - 1;
458 else if (strncasecmp(proxyurl, FTP_URL"ftp://", sizeof(FTP_URL"ftp://") - 1) == 0)
30
Assuming the condition is false
31
Taking false branch
459 host = proxyurl + sizeof(FTP_URL"ftp://") - 1;
460 else {
461 warnx("Malformed proxy URL: %s", proxyenv);
32
Potential leak of memory pointed to by 'sslpath'
462 goto cleanup_url_get;
463 }
464 if (EMPTYSTRING(host)((host) == ((void *)0) || (*(host) == '\0'))) {
465 warnx("Malformed proxy URL: %s", proxyenv);
466 goto cleanup_url_get;
467 }
468 if (*--path == '\0')
469 *path = '/'; /* add / back to real path */
470 path = strchr(host, '/'); /* remove trailing / on host */
471 if (!EMPTYSTRING(path)((path) == ((void *)0) || (*(path) == '\0')))
472 *path++ = '\0'; /* i guess this ++ is useless */
473
474 path = strchr(host, '@'); /* look for credentials in proxy */
475 if (!EMPTYSTRING(path)((path) == ((void *)0) || (*(path) == '\0'))) {
476 *path = '\0';
477 if (strchr(host, ':') == NULL((void *)0)) {
478 warnx("Malformed proxy URL: %s", proxyenv);
479 goto cleanup_url_get;
480 }
481 proxy_credentials = recode_credentials(host);
482 *path = '@'; /* restore @ in proxyurl */
483
484 /*
485 * This removes the password from proxyurl,
486 * filling with stars
487 */
488 for (host = 1 + strchr(proxyurl + 5, ':'); *host != '@';
489 host++)
490 *host = '*';
491
492 host = path + 1;
493 }
494
495 path = newline;
496 }
497
498 if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL((void *)0) &&
499 (hosttail[1] == '\0' || hosttail[1] == ':')) {
500 host++;
501 *hosttail++ = '\0';
502#ifndef SMALL
503 if (asprintf(&full_host, "[%s]", host) == -1)
504 errx(1, "Cannot allocate memory for hostname");
505#endif /* !SMALL */
506 } else
507 hosttail = host;
508
509 portnum = strrchr(hosttail, ':'); /* find portnum */
510 if (portnum != NULL((void *)0))
511 *portnum++ = '\0';
512#ifndef NOSSL
513 port = portnum ? portnum : (ishttpsurl ? httpsport : httpport);
514#else /* !NOSSL */
515 port = portnum ? portnum : httpport;
516#endif /* !NOSSL */
517
518#ifndef SMALL
519 if (full_host == NULL((void *)0))
520 if ((full_host = strdup(host)) == NULL((void *)0))
521 errx(1, "Cannot allocate memory for hostname");
522 if (debug)
523 fprintf(ttyout, "host %s, port %s, path %s, "
524 "save as %s, auth %s.\n", host, port, path,
525 savefile, credentials ? credentials : "none");
526#endif /* !SMALL */
527
528 memset(&hints, 0, sizeof(hints));
529 hints.ai_family = family;
530 hints.ai_socktype = SOCK_STREAM1;
531 error = getaddrinfo(host, port, &hints, &res0);
532 /*
533 * If the services file is corrupt/missing, fall back
534 * on our hard-coded defines.
535 */
536 if (error == EAI_SERVICE-8 && port == httpport) {
537 snprintf(pbuf, sizeof(pbuf), "%d", HTTP_PORT80);
538 error = getaddrinfo(host, pbuf, &hints, &res0);
539#ifndef NOSSL
540 } else if (error == EAI_SERVICE-8 && port == httpsport) {
541 snprintf(pbuf, sizeof(pbuf), "%d", HTTPS_PORT443);
542 error = getaddrinfo(host, pbuf, &hints, &res0);
543#endif /* !NOSSL */
544 }
545 if (error) {
546 warnx("%s: %s", host, gai_strerror(error));
547 goto cleanup_url_get;
548 }
549
550#ifndef SMALL
551 if (srcaddr) {
552 hints.ai_flags |= AI_NUMERICHOST4;
553 error = getaddrinfo(srcaddr, NULL((void *)0), &hints, &ares);
554 if (error) {
555 warnx("%s: %s", srcaddr, gai_strerror(error));
556 goto cleanup_url_get;
557 }
558 }
559#endif /* !SMALL */
560
561 /* ensure consistent order of the output */
562 if (verbose)
563 setvbuf(ttyout, NULL((void *)0), _IOLBF1, 0);
564
565 fd = -1;
566 for (res = res0; res; res = res->ai_next) {
567 if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf,
568 sizeof(hbuf), NULL((void *)0), 0, NI_NUMERICHOST1) != 0)
569 strlcpy(hbuf, "(unknown)", sizeof(hbuf));
570 if (verbose)
571 fprintf(ttyout, "Trying %s...\n", hbuf);
572
573 fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
574 if (fd == -1) {
575 cause = "socket";
576 continue;
577 }
578
579#ifndef SMALL
580 if (srcaddr) {
581 if (ares->ai_family != res->ai_family) {
582 close(fd);
583 fd = -1;
584 errno(*__errno()) = EINVAL22;
585 cause = "bind";
586 continue;
587 }
588 if (bind(fd, ares->ai_addr, ares->ai_addrlen) == -1) {
589 save_errno = errno(*__errno());
590 close(fd);
591 errno(*__errno()) = save_errno;
592 fd = -1;
593 cause = "bind";
594 continue;
595 }
596 }
597#endif /* !SMALL */
598
599 error = timed_connect(fd, res->ai_addr, res->ai_addrlen,
600 connect_timeout);
601 if (error != 0) {
602 save_errno = errno(*__errno());
603 close(fd);
604 errno(*__errno()) = save_errno;
605 fd = -1;
606 cause = "connect";
607 continue;
608 }
609
610 /* get port in numeric */
611 if (getnameinfo(res->ai_addr, res->ai_addrlen, NULL((void *)0), 0,
612 pbuf, sizeof(pbuf), NI_NUMERICSERV2) == 0)
613 port = pbuf;
614 else
615 port = NULL((void *)0);
616
617#ifndef NOSSL
618 if (proxyenv && sslhost)
619 proxy_connect(fd, sslhost, proxy_credentials);
620#endif /* !NOSSL */
621 break;
622 }
623 freeaddrinfo(res0);
624#ifndef SMALL
625 if (srcaddr)
626 freeaddrinfo(ares);
627#endif /* !SMALL */
628 if (fd < 0) {
629 warn("%s", cause);
630 goto cleanup_url_get;
631 }
632
633#ifndef NOSSL
634 if (ishttpsurl) {
635 ssize_t ret;
636 if (proxyenv && sslpath) {
637 ishttpsurl = 0;
638 proxyurl = NULL((void *)0);
639 path = sslpath;
640 }
641 if (sslhost == NULL((void *)0)) {
642 sslhost = strdup(host);
643 if (sslhost == NULL((void *)0))
644 errx(1, "Can't allocate memory for https host.");
645 }
646 if ((tls = tls_client()) == NULL((void *)0)) {
647 fprintf(ttyout, "failed to create SSL client\n");
648 goto cleanup_url_get;
649 }
650 if (tls_configure(tls, tls_config) != 0) {
651 fprintf(ttyout, "TLS configuration failure: %s\n",
652 tls_error(tls));
653 goto cleanup_url_get;
654 }
655 if (tls_connect_socket(tls, fd, sslhost) != 0) {
656 fprintf(ttyout, "TLS connect failure: %s\n", tls_error(tls));
657 goto cleanup_url_get;
658 }
659 do {
660 ret = tls_handshake(tls);
661 } while (ret == TLS_WANT_POLLIN-2 || ret == TLS_WANT_POLLOUT-3);
662 if (ret != 0) {
663 fprintf(ttyout, "TLS handshake failure: %s\n", tls_error(tls));
664 goto cleanup_url_get;
665 }
666 fin = funopen(tls, stdio_tls_read_wrapper,
667 stdio_tls_write_wrapper, NULL((void *)0), NULL((void *)0));
668 } else {
669 fin = fdopen(fd, "r+");
670 fd = -1;
671 }
672#else /* !NOSSL */
673 fin = fdopen(fd, "r+");
674 fd = -1;
675#endif /* !NOSSL */
676
677#ifdef SMALL
678 if (lastfile) {
679 if (pipeout) {
680 if (pledge("stdio rpath inet dns tty", NULL((void *)0)) == -1)
681 err(1, "pledge");
682 } else {
683 if (pledge("stdio rpath wpath cpath inet dns tty", NULL((void *)0)) == -1)
684 err(1, "pledge");
685 }
686 }
687#endif
688
689 /*
690 * Construct and send the request. Proxy requests don't want leading /.
691 */
692#ifndef NOSSL
693 cookie_get(host, path, ishttpsurl, &buf);
694#endif /* !NOSSL */
695
696 epath = url_encode(path);
697 if (proxyurl) {
698 if (verbose) {
699 fprintf(ttyout, "Requesting %s (via %s)\n",
700 origline, proxyurl);
701 }
702 /*
703 * Host: directive must use the destination host address for
704 * the original URI (path).
705 */
706 ftp_printf(fin, "GET %s HTTP/1.1\r\n"
707 "Connection: close\r\n"
708 "Host: %s\r\n%s%s\r\n",
709 epath, proxyhost, buf ? buf : "", httpuseragent);
710 if (credentials)
711 ftp_printf(fin, "Authorization: Basic %s\r\n",
712 credentials);
713 if (proxy_credentials)
714 ftp_printf(fin, "Proxy-Authorization: Basic %s\r\n",
715 proxy_credentials);
716 ftp_printf(fin, "\r\n");
717 } else {
718 if (verbose)
719 fprintf(ttyout, "Requesting %s\n", origline);
720#ifndef SMALL
721 if (resume || timestamp) {
722 if (stat(savefile, &stbuf) == 0) {
723 if (resume)
724 restart_point = stbuf.st_size;
725 if (timestamp)
726 mtime = stbuf.st_mtimest_mtim.tv_sec;
727 } else {
728 restart_point = 0;
729 mtime = 0;
730 }
731 }
732#endif /* SMALL */
733 ftp_printf(fin,
734 "GET /%s HTTP/1.1\r\n"
735 "Connection: close\r\n"
736 "Host: ", epath);
737 if (proxyhost) {
738 ftp_printf(fin, "%s", proxyhost);
739 port = NULL((void *)0);
740 } else if (strchr(host, ':')) {
741 /*
742 * strip off scoped address portion, since it's
743 * local to node
744 */
745 h = strdup(host);
746 if (h == NULL((void *)0))
747 errx(1, "Can't allocate memory.");
748 if ((p = strchr(h, '%')) != NULL((void *)0))
749 *p = '\0';
750 ftp_printf(fin, "[%s]", h);
751 free(h);
752 } else
753 ftp_printf(fin, "%s", host);
754
755 /*
756 * Send port number only if it's specified and does not equal
757 * 80. Some broken HTTP servers get confused if you explicitly
758 * send them the port number.
759 */
760#ifndef NOSSL
761 if (port && strcmp(port, (ishttpsurl ? "443" : "80")) != 0)
762 ftp_printf(fin, ":%s", port);
763 if (restart_point)
764 ftp_printf(fin, "\r\nRange: bytes=%lld-",
765 (long long)restart_point);
766#else /* !NOSSL */
767 if (port && strcmp(port, "80") != 0)
768 ftp_printf(fin, ":%s", port);
769#endif /* !NOSSL */
770
771#ifndef SMALL
772 if (mtime && (http_time(mtime, tmbuf, sizeof(tmbuf)) != 0))
773 ftp_printf(fin, "\r\nIf-Modified-Since: %s", tmbuf);
774#endif /* SMALL */
775
776 ftp_printf(fin, "\r\n%s%s\r\n",
777 buf ? buf : "", httpuseragent);
778 if (credentials)
779 ftp_printf(fin, "Authorization: Basic %s\r\n",
780 credentials);
781 ftp_printf(fin, "\r\n");
782 }
783 free(epath);
784
785#ifndef NOSSL
786 free(buf);
787#endif /* !NOSSL */
788 buf = NULL((void *)0);
789 bufsize = 0;
790
791 if (fflush(fin) == EOF(-1)) {
792 warnx("Writing HTTP request: %s", sockerror(tls));
793 goto cleanup_url_get;
794 }
795 if ((len = getline(&buf, &bufsize, fin)) == -1) {
796 warnx("Receiving HTTP reply: %s", sockerror(tls));
797 goto cleanup_url_get;
798 }
799
800 while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n'))
801 buf[--len] = '\0';
802#ifndef SMALL
803 if (debug)
804 fprintf(ttyout, "received '%s'\n", buf);
805#endif /* !SMALL */
806
807 cp = strchr(buf, ' ');
808 if (cp == NULL((void *)0))
809 goto improper;
810 else
811 cp++;
812
813 strlcpy(ststr, cp, sizeof(ststr));
814 status = strtonum(ststr, 200, 503, &errstr);
815 if (errstr) {
816 strnvis(gerror, cp, sizeof gerror, VIS_SAFE0x20);
817 warnx("Error retrieving %s: %s", origline, gerror);
818 goto cleanup_url_get;
819 }
820
821 switch (status) {
822 case 200: /* OK */
823#ifndef SMALL
824 /*
825 * When we request a partial file, and we receive an HTTP 200
826 * it is a good indication that the server doesn't support
827 * range requests, and is about to send us the entire file.
828 * If the restart_point == 0, then we are not actually
829 * requesting a partial file, and an HTTP 200 is appropriate.
830 */
831 if (resume && restart_point != 0) {
832 warnx("Server does not support resume.");
833 restart_point = resume = 0;
834 }
835 /* FALLTHROUGH */
836 case 206: /* Partial Content */
837#endif /* !SMALL */
838 break;
839 case 301: /* Moved Permanently */
840 case 302: /* Found */
841 case 303: /* See Other */
842 case 307: /* Temporary Redirect */
843 case 308: /* Permanent Redirect (RFC 7538) */
844 isredirect++;
845 if (redirect_loop++ > 10) {
846 warnx("Too many redirections requested");
847 goto cleanup_url_get;
848 }
849 break;
850#ifndef SMALL
851 case 304: /* Not Modified */
852 warnx("File is not modified on the server");
853 goto cleanup_url_get;
854 case 416: /* Requested Range Not Satisfiable */
855 warnx("File is already fully retrieved.");
856 goto cleanup_url_get;
857#endif /* !SMALL */
858 case 503:
859 isunavail = 1;
860 break;
861 default:
862 strnvis(gerror, cp, sizeof gerror, VIS_SAFE0x20);
863 warnx("Error retrieving %s: %s", origline, gerror);
864 goto cleanup_url_get;
865 }
866
867 /*
868 * Read the rest of the header.
869 */
870 filesize = -1;
871
872 for (;;) {
873 if ((len = getline(&buf, &bufsize, fin)) == -1) {
874 warnx("Receiving HTTP reply: %s", sockerror(tls));
875 goto cleanup_url_get;
876 }
877
878 while (len > 0 && (buf[len-1] == '\r' || buf[len-1] == '\n' ||
879 buf[len-1] == ' ' || buf[len-1] == '\t'))
880 buf[--len] = '\0';
881 if (len == 0)
882 break;
883#ifndef SMALL
884 if (debug)
885 fprintf(ttyout, "received '%s'\n", buf);
886#endif /* !SMALL */
887
888 /* Look for some headers */
889 cp = buf;
890#define CONTENTLEN"Content-Length:" "Content-Length:"
891 if (strncasecmp(cp, CONTENTLEN"Content-Length:", sizeof(CONTENTLEN"Content-Length:") - 1) == 0) {
892 cp += sizeof(CONTENTLEN"Content-Length:") - 1;
893 cp += strspn(cp, " \t");
894 filesize = strtonum(cp, 0, LLONG_MAX0x7fffffffffffffffLL, &errstr);
895 if (errstr != NULL((void *)0))
896 goto improper;
897#ifndef SMALL
898 if (restart_point)
899 filesize += restart_point;
900#endif /* !SMALL */
901#define LOCATION"Location:" "Location:"
902 } else if (isredirect &&
903 strncasecmp(cp, LOCATION"Location:", sizeof(LOCATION"Location:") - 1) == 0) {
904 cp += sizeof(LOCATION"Location:") - 1;
905 cp += strspn(cp, " \t");
906 /*
907 * If there is a colon before the first slash, this URI
908 * is not relative. RFC 3986 4.2
909 */
910 if (cp[strcspn(cp, ":/")] != ':') {
911#ifdef SMALL
912 errx(1, "Relative redirect not supported");
913#else /* SMALL */
914 /* XXX doesn't handle protocol-relative URIs */
915 if (*cp == '/') {
916 locbase = NULL((void *)0);
917 cp++;
918 } else {
919 locbase = strdup(path);
920 if (locbase == NULL((void *)0))
921 errx(1, "Can't allocate memory"
922 " for location base");
923 loctail = strchr(locbase, '#');
924 if (loctail != NULL((void *)0))
925 *loctail = '\0';
926 loctail = strchr(locbase, '?');
927 if (loctail != NULL((void *)0))
928 *loctail = '\0';
929 loctail = strrchr(locbase, '/');
930 if (loctail == NULL((void *)0)) {
931 free(locbase);
932 locbase = NULL((void *)0);
933 } else
934 loctail[1] = '\0';
935 }
936 /* Construct URL from relative redirect */
937 if (asprintf(&redirurl, "%s%s%s%s/%s%s",
938 scheme, full_host,
939 portnum ? ":" : "",
940 portnum ? portnum : "",
941 locbase ? locbase : "",
942 cp) == -1)
943 errx(1, "Cannot build "
944 "redirect URL");
945 free(locbase);
946#endif /* SMALL */
947 } else if ((redirurl = strdup(cp)) == NULL((void *)0))
948 errx(1, "Cannot allocate memory for URL");
949 loctail = strchr(redirurl, '#');
950 if (loctail != NULL((void *)0))
951 *loctail = '\0';
952 if (verbose) {
953 char *visbuf;
954 if (stravis(&visbuf, redirurl, VIS_SAFE0x20) == -1)
955 err(1, "Cannot vis redirect URL");
956 fprintf(ttyout, "Redirected to %s\n", visbuf);
957 free(visbuf);
958 }
959 ftp_close(&fin, &tls, &fd);
960 rval = url_get(redirurl, proxyenv, savefile, lastfile);
961 free(redirurl);
962 goto cleanup_url_get;
963#define RETRYAFTER"Retry-After:" "Retry-After:"
964 } else if (isunavail &&
965 strncasecmp(cp, RETRYAFTER"Retry-After:", sizeof(RETRYAFTER"Retry-After:") - 1) == 0) {
966 cp += sizeof(RETRYAFTER"Retry-After:") - 1;
967 cp += strspn(cp, " \t");
968 retryafter = strtonum(cp, 0, 0, &errstr);
969 if (errstr != NULL((void *)0))
970 retryafter = -1;
971#define TRANSFER_ENCODING"Transfer-Encoding:" "Transfer-Encoding:"
972 } else if (strncasecmp(cp, TRANSFER_ENCODING"Transfer-Encoding:",
973 sizeof(TRANSFER_ENCODING"Transfer-Encoding:") - 1) == 0) {
974 cp += sizeof(TRANSFER_ENCODING"Transfer-Encoding:") - 1;
975 cp += strspn(cp, " \t");
976 if (strcasecmp(cp, "chunked") == 0)
977 chunked = 1;
978#ifndef SMALL
979#define LAST_MODIFIED"Last-Modified:" "Last-Modified:"
980 } else if (strncasecmp(cp, LAST_MODIFIED"Last-Modified:",
981 sizeof(LAST_MODIFIED"Last-Modified:") - 1) == 0) {
982 cp += sizeof(LAST_MODIFIED"Last-Modified:") - 1;
983 cp += strspn(cp, " \t");
984 if (strptime(cp, "%a, %d %h %Y %T %Z", &lmt) == NULL((void *)0))
985 server_timestamps = 0;
986#endif /* !SMALL */
987 }
988 }
989 free(buf);
990 buf = NULL((void *)0);
991
992 /* Content-Length should be ignored for Transfer-Encoding: chunked */
993 if (chunked)
994 filesize = -1;
995
996 if (isunavail) {
997 if (retried || retryafter != 0)
998 warnx("Error retrieving %s: 503 Service Unavailable",
999 origline);
1000 else {
1001 if (verbose)
1002 fprintf(ttyout, "Retrying %s\n", origline);
1003 retried = 1;
1004 ftp_close(&fin, &tls, &fd);
1005 rval = url_get(origline, proxyenv, savefile, lastfile);
1006 }
1007 goto cleanup_url_get;
1008 }
1009
1010 /* Open the output file. */
1011 if (!pipeout) {
1012#ifndef SMALL
1013 if (resume)
1014 out = open(savefile, O_CREAT0x0200 | O_WRONLY0x0001 | O_APPEND0x0008,
1015 0666);
1016 else
1017#endif /* !SMALL */
1018 out = open(savefile, O_CREAT0x0200 | O_WRONLY0x0001 | O_TRUNC0x0400,
1019 0666);
1020 if (out == -1) {
1021 warn("Can't open %s", savefile);
1022 goto cleanup_url_get;
1023 }
1024 } else {
1025 out = fileno(stdout)(!__isthreaded ? (((&__sF[1]))->_file) : (fileno)((&
__sF[1])))
;
1026#ifdef SMALL
1027 if (lastfile) {
1028 if (pledge("stdio tty", NULL((void *)0)) == -1)
1029 err(1, "pledge");
1030 }
1031#endif
1032 }
1033
1034 if ((buf = malloc(buflen)) == NULL((void *)0))
1035 errx(1, "Can't allocate memory for transfer buffer");
1036
1037 /* Trap signals */
1038 oldintr = NULL((void *)0);
1039 oldinti = NULL((void *)0);
1040 if (setjmp(httpabort)) {
1041 if (oldintr)
1042 (void)signal(SIGINT2, oldintr);
1043 if (oldinti)
1044 (void)signal(SIGINFO29, oldinti);
1045 goto cleanup_url_get;
1046 }
1047 oldintr = signal(SIGINT2, aborthttp);
1048
1049 bytes = 0;
1050 hashbytes = mark;
1051 progressmeter(-1, path);
1052
1053 /* Finally, suck down the file. */
1054 oldinti = signal(SIGINFO29, psummary);
1055 if (chunked) {
1056 error = save_chunked(fin, tls, out, buf, buflen);
1057 signal(SIGINT2, oldintr);
1058 signal(SIGINFO29, oldinti);
1059 if (error == -1)
1060 goto cleanup_url_get;
1061 } else {
1062 while ((len = fread(buf, 1, buflen, fin)) > 0) {
1063 bytes += len;
1064 for (cp = buf; len > 0; len -= wlen, cp += wlen) {
1065 if ((wlen = write(out, cp, len)) == -1) {
1066 warn("Writing %s", savefile);
1067 signal(SIGINT2, oldintr);
1068 signal(SIGINFO29, oldinti);
1069 goto cleanup_url_get;
1070 }
1071 }
1072 if (hash && !progress) {
1073 while (bytes >= hashbytes) {
1074 (void)putc('#', ttyout)(!__isthreaded ? __sputc('#', ttyout) : (putc)('#', ttyout));
1075 hashbytes += mark;
1076 }
1077 (void)fflush(ttyout);
1078 }
1079 }
1080 save_errno = errno(*__errno());
1081 signal(SIGINT2, oldintr);
1082 signal(SIGINFO29, oldinti);
1083 if (hash && !progress && bytes > 0) {
1084 if (bytes < mark)
1085 (void)putc('#', ttyout)(!__isthreaded ? __sputc('#', ttyout) : (putc)('#', ttyout));
1086 (void)putc('\n', ttyout)(!__isthreaded ? __sputc('\n', ttyout) : (putc)('\n', ttyout)
)
;
1087 (void)fflush(ttyout);
1088 }
1089 if (len == 0 && ferror(fin)(!__isthreaded ? (((fin)->_flags & 0x0040) != 0) : (ferror
)(fin))
) {
1090 errno(*__errno()) = save_errno;
1091 warnx("Reading from socket: %s", sockerror(tls));
1092 goto cleanup_url_get;
1093 }
1094 }
1095 progressmeter(1, NULL((void *)0));
1096 if (
1097#ifndef SMALL
1098 !resume &&
1099#endif /* !SMALL */
1100 filesize != -1 && len == 0 && bytes != filesize) {
1101 if (verbose)
1102 fputs("Read short file.\n", ttyout);
1103 goto cleanup_url_get;
1104 }
1105
1106 if (verbose)
1107 ptransfer(0);
1108
1109 rval = 0;
1110 goto cleanup_url_get;
1111
1112noftpautologin:
1113 warnx(
1114 "Auto-login using ftp URLs isn't supported when using $ftp_proxy");
1115 goto cleanup_url_get;
1116
1117improper:
1118 warnx("Improper response from %s", host);
1119
1120cleanup_url_get:
1121#ifndef SMALL
1122 free(full_host);
1123#endif /* !SMALL */
1124#ifndef NOSSL
1125 free(sslhost);
1126#endif /* !NOSSL */
1127 ftp_close(&fin, &tls, &fd);
1128 if (out >= 0 && out != fileno(stdout)(!__isthreaded ? (((&__sF[1]))->_file) : (fileno)((&
__sF[1])))
) {
1129#ifndef SMALL
1130 if (server_timestamps && lmt.tm_zone != 0 &&
1131 fstat(out, &stbuf) == 0 && S_ISREG(stbuf.st_mode)((stbuf.st_mode & 0170000) == 0100000) != 0) {
1132 ts[0].tv_nsec = UTIME_NOW-2L;
1133 ts[1].tv_nsec = 0;
1134 setenv("TZ", lmt.tm_zone, 1);
1135 if (((ts[1].tv_sec = mktime(&lmt)) != -1) &&
1136 (futimens(out, ts) == -1))
1137 warnx("Unable to set file modification time");
1138 }
1139#endif /* !SMALL */
1140 close(out);
1141 }
1142 free(buf);
1143 free(pathbuf);
1144 free(proxyhost);
1145 free(proxyurl);
1146 free(newline);
1147 free(credentials);
1148 free(proxy_credentials);
1149 return (rval);
1150}
1151
1152static int
1153save_chunked(FILE *fin, struct tls *tls, int out, char *buf, size_t buflen)
1154{
1155
1156 char *header = NULL((void *)0), *end, *cp;
1157 unsigned long chunksize;
1158 size_t hsize = 0, rlen, wlen;
1159 ssize_t written;
1160 char cr, lf;
1161
1162 for (;;) {
1163 if (getline(&header, &hsize, fin) == -1)
1164 break;
1165 /* strip CRLF and any optional chunk extension */
1166 header[strcspn(header, "; \t\r\n")] = '\0';
1167 errno(*__errno()) = 0;
1168 chunksize = strtoul(header, &end, 16);
1169 if (errno(*__errno()) || header[0] == '\0' || *end != '\0' ||
1170 chunksize > INT_MAX0x7fffffff) {
1171 warnx("Invalid chunk size '%s'", header);
1172 free(header);
1173 return -1;
1174 }
1175
1176 if (chunksize == 0) {
1177 /* We're done. Ignore optional trailer. */
1178 free(header);
1179 return 0;
1180 }
1181
1182 for (written = 0; chunksize != 0; chunksize -= rlen) {
1183 rlen = (chunksize < buflen) ? chunksize : buflen;
1184 rlen = fread(buf, 1, rlen, fin);
1185 if (rlen == 0)
1186 break;
1187 bytes += rlen;
1188 for (cp = buf, wlen = rlen; wlen > 0;
1189 wlen -= written, cp += written) {
1190 if ((written = write(out, cp, wlen)) == -1) {
1191 warn("Writing output file");
1192 free(header);
1193 return -1;
1194 }
1195 }
1196 }
1197
1198 if (rlen == 0 ||
1199 fread(&cr, 1, 1, fin) != 1 ||
1200 fread(&lf, 1, 1, fin) != 1)
1201 break;
1202
1203 if (cr != '\r' || lf != '\n') {
1204 warnx("Invalid chunked encoding");
1205 free(header);
1206 return -1;
1207 }
1208 }
1209 free(header);
1210
1211 if (ferror(fin)(!__isthreaded ? (((fin)->_flags & 0x0040) != 0) : (ferror
)(fin))
)
1212 warnx("Error while reading from socket: %s", sockerror(tls));
1213 else
1214 warnx("Invalid chunked encoding: short read");
1215
1216 return -1;
1217}
1218
1219/*
1220 * Abort a http retrieval
1221 */
1222static void
1223aborthttp(int signo)
1224{
1225 const char errmsg[] = "\nfetch aborted.\n";
1226
1227 write(fileno(ttyout)(!__isthreaded ? ((ttyout)->_file) : (fileno)(ttyout)), errmsg, sizeof(errmsg) - 1);
1228 longjmp(httpabort, 1);
1229}
1230
1231/*
1232 * Retrieve multiple files from the command line, transferring
1233 * files of the form "host:path", "ftp://host/path" using the
1234 * ftp protocol, and files of the form "http://host/path" using
1235 * the http protocol.
1236 * If path has a trailing "/", then return (-1);
1237 * the path will be cd-ed into and the connection remains open,
1238 * and the function will return -1 (to indicate the connection
1239 * is alive).
1240 * If an error occurs the return value will be the offset+1 in
1241 * argv[] of the file that caused a problem (i.e, argv[x]
1242 * returns x+1)
1243 * Otherwise, 0 is returned if all files retrieved successfully.
1244 */
1245int
1246auto_fetch(int argc, char *argv[], char *outfile)
1247{
1248 char *xargv[5];
1249 char *cp, *url, *host, *dir, *file, *portnum;
1250 char *username, *pass, *pathstart;
1251 char *ftpproxy, *httpproxy;
1252 int rval, xargc, lastfile;
1253 volatile int argpos;
1254 int dirhasglob, filehasglob, oautologin;
1255 char rempath[PATH_MAX1024];
1256
1257 argpos = 0;
1258
1259 if (setjmp(toplevel)) {
1260 if (connected)
1261 disconnect(0, NULL((void *)0));
1262 return (argpos + 1);
1263 }
1264 (void)signal(SIGINT2, (sig_t)intr);
1265 (void)signal(SIGPIPE13, (sig_t)lostpeer);
1266
1267 if ((ftpproxy = getenv(FTP_PROXY"ftp_proxy")) != NULL((void *)0) && *ftpproxy == '\0')
1268 ftpproxy = NULL((void *)0);
1269 if ((httpproxy = getenv(HTTP_PROXY"http_proxy")) != NULL((void *)0) && *httpproxy == '\0')
1270 httpproxy = NULL((void *)0);
1271
1272 /*
1273 * Loop through as long as there's files to fetch.
1274 */
1275 username = pass = NULL((void *)0);
1276 for (rval = 0; (rval == 0) && (argpos < argc); free(url), argpos++) {
1277 if (strchr(argv[argpos], ':') == NULL((void *)0))
1278 break;
1279
1280 free(username);
1281 free(pass);
1282 host = dir = file = portnum = username = pass = NULL((void *)0);
1283
1284 lastfile = (argv[argpos+1] == NULL((void *)0));
1285
1286 /*
1287 * We muck with the string, so we make a copy.
1288 */
1289 url = strdup(argv[argpos]);
1290 if (url == NULL((void *)0))
1291 errx(1, "Can't allocate memory for auto-fetch.");
1292
1293 if (strncasecmp(url, FILE_URL"file:", sizeof(FILE_URL"file:") - 1) == 0) {
1294 if (file_get(url + sizeof(FILE_URL"file:") - 1, outfile) == -1)
1295 rval = argpos + 1;
1296 continue;
1297 }
1298
1299 /*
1300 * Try HTTP URL-style arguments next.
1301 */
1302 if (strncasecmp(url, HTTP_URL"http://", sizeof(HTTP_URL"http://") - 1) == 0 ||
1303 strncasecmp(url, HTTPS_URL"https://", sizeof(HTTPS_URL"https://") -1) == 0) {
1304 redirect_loop = 0;
1305 retried = 0;
1306 if (url_get(url, httpproxy, outfile, lastfile) == -1)
1307 rval = argpos + 1;
1308 continue;
1309 }
1310
1311 /*
1312 * Try FTP URL-style arguments next. If ftpproxy is
1313 * set, use url_get() instead of standard ftp.
1314 * Finally, try host:file.
1315 */
1316 host = url;
1317 if (strncasecmp(url, FTP_URL"ftp://", sizeof(FTP_URL"ftp://") - 1) == 0) {
1318 char *passend, *passagain, *userend;
1319
1320 if (ftpproxy) {
1321 if (url_get(url, ftpproxy, outfile, lastfile) == -1)
1322 rval = argpos + 1;
1323 continue;
1324 }
1325 host += sizeof(FTP_URL"ftp://") - 1;
1326 dir = strchr(host, '/');
1327
1328 /* Look for [user:pass@]host[:port] */
1329
1330 /* check if we have "user:pass@" */
1331 userend = strchr(host, ':');
1332 passend = strchr(host, '@');
1333 if (passend && userend && userend < passend &&
1334 (!dir || passend < dir)) {
1335 username = host;
1336 pass = userend + 1;
1337 host = passend + 1;
1338 *userend = *passend = '\0';
1339 passagain = strchr(host, '@');
1340 if (strchr(pass, '@') != NULL((void *)0) ||
1341 (passagain != NULL((void *)0) && passagain < dir)) {
1342 warnx(at_encoding_warning);
1343 username = pass = NULL((void *)0);
1344 goto bad_ftp_url;
1345 }
1346
1347 if (EMPTYSTRING(username)((username) == ((void *)0) || (*(username) == '\0'))) {
1348bad_ftp_url:
1349 warnx("Invalid URL: %s", argv[argpos]);
1350 rval = argpos + 1;
1351 username = pass = NULL((void *)0);
1352 continue;
1353 }
1354 username = urldecode(username);
1355 pass = urldecode(pass);
1356 }
1357
1358 /* check [host]:port, or [host] */
1359 if (host[0] == '[') {
1360 cp = strchr(host, ']');
1361 if (cp && (!dir || cp < dir)) {
1362 if (cp + 1 == dir || cp[1] == ':') {
1363 host++;
1364 *cp++ = '\0';
1365 } else
1366 cp = NULL((void *)0);
1367 } else
1368 cp = host;
1369 } else
1370 cp = host;
1371
1372 /* split off host[:port] if there is */
1373 if (cp) {
1374 portnum = strchr(cp, ':');
1375 pathstart = strchr(cp, '/');
1376 /* : in path is not a port # indicator */
1377 if (portnum && pathstart &&
1378 pathstart < portnum)
1379 portnum = NULL((void *)0);
1380
1381 if (!portnum)
1382 ;
1383 else {
1384 if (!dir)
1385 ;
1386 else if (portnum + 1 < dir) {
1387 *portnum++ = '\0';
1388 /*
1389 * XXX should check if portnum
1390 * is decimal number
1391 */
1392 } else {
1393 /* empty portnum */
1394 goto bad_ftp_url;
1395 }
1396 }
1397 } else
1398 portnum = NULL((void *)0);
1399 } else { /* classic style `host:file' */
1400 dir = strchr(host, ':');
1401 }
1402 if (EMPTYSTRING(host)((host) == ((void *)0) || (*(host) == '\0'))) {
1403 rval = argpos + 1;
1404 continue;
1405 }
1406
1407 /*
1408 * If dir is NULL, the file wasn't specified
1409 * (URL looked something like ftp://host)
1410 */
1411 if (dir != NULL((void *)0))
1412 *dir++ = '\0';
1413
1414 /*
1415 * Extract the file and (if present) directory name.
1416 */
1417 if (!EMPTYSTRING(dir)((dir) == ((void *)0) || (*(dir) == '\0'))) {
1418 cp = strrchr(dir, '/');
1419 if (cp != NULL((void *)0)) {
1420 *cp++ = '\0';
1421 file = cp;
1422 } else {
1423 file = dir;
1424 dir = NULL((void *)0);
1425 }
1426 }
1427#ifndef SMALL
1428 if (debug)
1429 fprintf(ttyout,
1430 "user %s:%s host %s port %s dir %s file %s\n",
1431 username, pass ? "XXXX" : NULL((void *)0), host, portnum,
1432 dir, file);
1433#endif /* !SMALL */
1434
1435 /*
1436 * Set up the connection.
1437 */
1438 if (connected)
1439 disconnect(0, NULL((void *)0));
1440 xargv[0] = __progname;
1441 xargv[1] = host;
1442 xargv[2] = NULL((void *)0);
1443 xargc = 2;
1444 if (!EMPTYSTRING(portnum)((portnum) == ((void *)0) || (*(portnum) == '\0'))) {
1445 xargv[2] = portnum;
1446 xargv[3] = NULL((void *)0);
1447 xargc = 3;
1448 }
1449 oautologin = autologin;
1450 if (username == NULL((void *)0))
1451 anonftp = 1;
1452 else {
1453 anonftp = 0;
1454 autologin = 0;
1455 }
1456 setpeer(xargc, xargv);
1457 autologin = oautologin;
1458 if (connected == 0 ||
1459 (connected == 1 && autologin && (username == NULL((void *)0) ||
1460 !ftp_login(host, username, pass)))) {
1461 warnx("Can't connect or login to host `%s'", host);
1462 rval = argpos + 1;
1463 continue;
1464 }
1465
1466 /* Always use binary transfers. */
1467 setbinary(0, NULL((void *)0));
1468
1469 dirhasglob = filehasglob = 0;
1470 if (doglob) {
1471 if (!EMPTYSTRING(dir)((dir) == ((void *)0) || (*(dir) == '\0')) &&
1472 strpbrk(dir, "*?[]{}") != NULL((void *)0))
1473 dirhasglob = 1;
1474 if (!EMPTYSTRING(file)((file) == ((void *)0) || (*(file) == '\0')) &&
1475 strpbrk(file, "*?[]{}") != NULL((void *)0))
1476 filehasglob = 1;
1477 }
1478
1479 /* Change directories, if necessary. */
1480 if (!EMPTYSTRING(dir)((dir) == ((void *)0) || (*(dir) == '\0')) && !dirhasglob) {
1481 xargv[0] = "cd";
1482 xargv[1] = dir;
1483 xargv[2] = NULL((void *)0);
1484 cd(2, xargv);
1485 if (!dirchange) {
1486 rval = argpos + 1;
1487 continue;
1488 }
1489 }
1490
1491 if (EMPTYSTRING(file)((file) == ((void *)0) || (*(file) == '\0'))) {
1492#ifndef SMALL
1493 rval = -1;
1494#else /* !SMALL */
1495 recvrequest("NLST", "-", NULL((void *)0), "w", 0, 0);
1496 rval = 0;
1497#endif /* !SMALL */
1498 continue;
1499 }
1500
1501 if (verbose)
1502 fprintf(ttyout, "Retrieving %s/%s\n", dir ? dir : "", file);
1503
1504 if (dirhasglob) {
1505 snprintf(rempath, sizeof(rempath), "%s/%s", dir, file);
1506 file = rempath;
1507 }
1508
1509 /* Fetch the file(s). */
1510 xargc = 2;
1511 xargv[0] = "get";
1512 xargv[1] = file;
1513 xargv[2] = NULL((void *)0);
1514 if (dirhasglob || filehasglob) {
1515 int ointeractive;
1516
1517 ointeractive = interactive;
1518 interactive = 0;
1519 xargv[0] = "mget";
1520#ifndef SMALL
1521 if (resume) {
1522 xargc = 3;
1523 xargv[1] = "-c";
1524 xargv[2] = file;
1525 xargv[3] = NULL((void *)0);
1526 }
1527#endif /* !SMALL */
1528 mget(xargc, xargv);
1529 interactive = ointeractive;
1530 } else {
1531 if (outfile != NULL((void *)0)) {
1532 xargv[2] = outfile;
1533 xargv[3] = NULL((void *)0);
1534 xargc++;
1535 }
1536#ifndef SMALL
1537 if (resume)
1538 reget(xargc, xargv);
1539 else
1540#endif /* !SMALL */
1541 get(xargc, xargv);
1542 }
1543
1544 if ((code / 100) != COMPLETE2)
1545 rval = argpos + 1;
1546 }
1547 if (connected && rval != -1)
1548 disconnect(0, NULL((void *)0));
1549 return (rval);
1550}
1551
1552char *
1553urldecode(const char *str)
1554{
1555 char *ret, c;
1556 int i, reallen;
1557
1558 if (str == NULL((void *)0))
1559 return NULL((void *)0);
1560 if ((ret = malloc(strlen(str) + 1)) == NULL((void *)0))
1561 err(1, "Can't allocate memory for URL decoding");
1562 for (i = 0, reallen = 0; str[i] != '\0'; i++, reallen++, ret++) {
1563 c = str[i];
1564 if (c == '+') {
1565 *ret = ' ';
1566 continue;
1567 }
1568
1569 /* Cannot use strtol here because next char
1570 * after %xx may be a digit.
1571 */
1572 if (c == '%' && isxdigit((unsigned char)str[i + 1]) &&
1573 isxdigit((unsigned char)str[i + 2])) {
1574 *ret = hextochar(&str[i + 1]);
1575 i += 2;
1576 continue;
1577 }
1578 *ret = c;
1579 }
1580 *ret = '\0';
1581
1582 return ret - reallen;
1583}
1584
1585static char *
1586recode_credentials(const char *userinfo)
1587{
1588 char *ui, *creds;
1589 size_t ulen, credsize;
1590
1591 /* url-decode the user and pass */
1592 ui = urldecode(userinfo);
1593
1594 ulen = strlen(ui);
1595 credsize = (ulen + 2) / 3 * 4 + 1;
1596 creds = malloc(credsize);
1597 if (creds == NULL((void *)0))
1598 errx(1, "out of memory");
1599 if (b64_ntop__b64_ntop(ui, ulen, creds, credsize) == -1)
1600 errx(1, "error in base64 encoding");
1601 free(ui);
1602 return (creds);
1603}
1604
1605static char
1606hextochar(const char *str)
1607{
1608 unsigned char c, ret;
1609
1610 c = str[0];
1611 ret = c;
1612 if (isalpha(c))
1613 ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
1614 else
1615 ret -= '0';
1616 ret *= 16;
1617
1618 c = str[1];
1619 ret += c;
1620 if (isalpha(c))
1621 ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
1622 else
1623 ret -= '0';
1624 return ret;
1625}
1626
1627int
1628isurl(const char *p)
1629{
1630
1631 if (strncasecmp(p, FTP_URL"ftp://", sizeof(FTP_URL"ftp://") - 1) == 0 ||
1632 strncasecmp(p, HTTP_URL"http://", sizeof(HTTP_URL"http://") - 1) == 0 ||
1633#ifndef NOSSL
1634 strncasecmp(p, HTTPS_URL"https://", sizeof(HTTPS_URL"https://") - 1) == 0 ||
1635#endif /* !NOSSL */
1636 strncasecmp(p, FILE_URL"file:", sizeof(FILE_URL"file:") - 1) == 0 ||
1637 strstr(p, ":/"))
1638 return (1);
1639 return (0);
1640}
1641
1642#ifndef SMALL
1643static int
1644ftp_printf(FILE *fp, const char *fmt, ...)
1645{
1646 va_list ap;
1647 int ret;
1648
1649 va_start(ap, fmt)__builtin_va_start((ap), fmt);
1650 ret = vfprintf(fp, fmt, ap);
1651 va_end(ap)__builtin_va_end((ap));
1652
1653 if (debug) {
1654 va_start(ap, fmt)__builtin_va_start((ap), fmt);
1655 vfprintf(ttyout, fmt, ap);
1656 va_end(ap)__builtin_va_end((ap));
1657 }
1658
1659 return ret;
1660}
1661#endif /* !SMALL */
1662
1663static void
1664ftp_close(FILE **fin, struct tls **tls, int *fd)
1665{
1666#ifndef NOSSL
1667 int ret;
1668
1669 if (*tls != NULL((void *)0)) {
1670 if (tls_session_fd != -1)
1671 dprintf(STDERR_FILENO2, "tls session resumed: %s\n",
1672 tls_conn_session_resumed(*tls) ? "yes" : "no");
1673 do {
1674 ret = tls_close(*tls);
1675 } while (ret == TLS_WANT_POLLIN-2 || ret == TLS_WANT_POLLOUT-3);
1676 tls_free(*tls);
1677 *tls = NULL((void *)0);
1678 }
1679 if (*fd != -1) {
1680 close(*fd);
1681 *fd = -1;
1682 }
1683#endif
1684 if (*fin != NULL((void *)0)) {
1685 fclose(*fin);
1686 *fin = NULL((void *)0);
1687 }
1688}
1689
1690static const char *
1691sockerror(struct tls *tls)
1692{
1693 int save_errno = errno(*__errno());
1694#ifndef NOSSL
1695 if (tls != NULL((void *)0)) {
1696 const char *tlserr = tls_error(tls);
1697 if (tlserr != NULL((void *)0))
1698 return tlserr;
1699 }
1700#endif
1701 return strerror(save_errno);
1702}
1703
1704#ifndef NOSSL
1705static int
1706proxy_connect(int socket, char *host, char *cookie)
1707{
1708 int l;
1709 char buf[1024];
1710 char *connstr, *hosttail, *port;
1711
1712 if (*host == '[' && (hosttail = strrchr(host, ']')) != NULL((void *)0) &&
1713 (hosttail[1] == '\0' || hosttail[1] == ':')) {
1714 host++;
1715 *hosttail++ = '\0';
1716 } else
1717 hosttail = host;
1718
1719 port = strrchr(hosttail, ':'); /* find portnum */
1720 if (port != NULL((void *)0))
1721 *port++ = '\0';
1722 if (!port)
1723 port = "443";
1724
1725 if (cookie) {
1726 l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n"
1727 "Proxy-Authorization: Basic %s\r\n%s\r\n\r\n",
1728 host, port, cookie, HTTP_USER_AGENT"User-Agent: OpenBSD ftp");
1729 } else {
1730 l = asprintf(&connstr, "CONNECT %s:%s HTTP/1.1\r\n%s\r\n\r\n",
1731 host, port, HTTP_USER_AGENT"User-Agent: OpenBSD ftp");
1732 }
1733
1734 if (l == -1)
1735 errx(1, "Could not allocate memory to assemble connect string!");
1736#ifndef SMALL
1737 if (debug)
1738 printf("%s", connstr);
1739#endif /* !SMALL */
1740 if (write(socket, connstr, l) != l)
1741 err(1, "Could not send connect string");
1742 read(socket, &buf, sizeof(buf)); /* only proxy header XXX: error handling? */
1743 free(connstr);
1744 return(200);
1745}
1746
1747static int
1748stdio_tls_write_wrapper(void *arg, const char *buf, int len)
1749{
1750 struct tls *tls = arg;
1751 ssize_t ret;
1752
1753 do {
1754 ret = tls_write(tls, buf, len);
1755 } while (ret == TLS_WANT_POLLIN-2 || ret == TLS_WANT_POLLOUT-3);
1756
1757 return ret;
1758}
1759
1760static int
1761stdio_tls_read_wrapper(void *arg, char *buf, int len)
1762{
1763 struct tls *tls = arg;
1764 ssize_t ret;
1765
1766 do {
1767 ret = tls_read(tls, buf, len);
1768 } while (ret == TLS_WANT_POLLIN-2 || ret == TLS_WANT_POLLOUT-3);
1769
1770 return ret;
1771}
1772#endif /* !NOSSL */