1 1.242 lukem /* $NetBSD: fetch.c,v 1.242 2024/11/29 04:31:57 lukem Exp $ */ 2 1.1 lukem 3 1.1 lukem /*- 4 1.242 lukem * Copyright (c) 1997-2024 The NetBSD Foundation, Inc. 5 1.1 lukem * All rights reserved. 6 1.1 lukem * 7 1.1 lukem * This code is derived from software contributed to The NetBSD Foundation 8 1.54 lukem * by Luke Mewburn. 9 1.1 lukem * 10 1.114 lukem * This code is derived from software contributed to The NetBSD Foundation 11 1.114 lukem * by Scott Aaron Bamford. 12 1.114 lukem * 13 1.207 wiz * This code is derived from software contributed to The NetBSD Foundation 14 1.207 wiz * by Thomas Klausner. 15 1.207 wiz * 16 1.1 lukem * Redistribution and use in source and binary forms, with or without 17 1.1 lukem * modification, are permitted provided that the following conditions 18 1.1 lukem * are met: 19 1.1 lukem * 1. Redistributions of source code must retain the above copyright 20 1.1 lukem * notice, this list of conditions and the following disclaimer. 21 1.1 lukem * 2. Redistributions in binary form must reproduce the above copyright 22 1.1 lukem * notice, this list of conditions and the following disclaimer in the 23 1.1 lukem * documentation and/or other materials provided with the distribution. 24 1.1 lukem * 25 1.1 lukem * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 26 1.1 lukem * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 27 1.1 lukem * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 28 1.14 lukem * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 29 1.14 lukem * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 30 1.1 lukem * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 31 1.1 lukem * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 32 1.1 lukem * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 33 1.1 lukem * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 34 1.1 lukem * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 1.1 lukem * POSSIBILITY OF SUCH DAMAGE. 36 1.1 lukem */ 37 1.1 lukem 38 1.12 lukem #include <sys/cdefs.h> 39 1.1 lukem #ifndef lint 40 1.242 lukem __RCSID("$NetBSD: fetch.c,v 1.242 2024/11/29 04:31:57 lukem Exp $"); 41 1.1 lukem #endif /* not lint */ 42 1.1 lukem 43 1.1 lukem /* 44 1.1 lukem * FTP User Program -- Command line file retrieval 45 1.1 lukem */ 46 1.1 lukem 47 1.1 lukem #include <sys/types.h> 48 1.1 lukem #include <sys/param.h> 49 1.1 lukem #include <sys/socket.h> 50 1.30 lukem #include <sys/stat.h> 51 1.32 lukem #include <sys/time.h> 52 1.1 lukem 53 1.1 lukem #include <netinet/in.h> 54 1.1 lukem 55 1.2 lukem #include <arpa/ftp.h> 56 1.1 lukem #include <arpa/inet.h> 57 1.1 lukem 58 1.195 lukem #include <assert.h> 59 1.1 lukem #include <ctype.h> 60 1.1 lukem #include <err.h> 61 1.22 lukem #include <errno.h> 62 1.1 lukem #include <netdb.h> 63 1.1 lukem #include <fcntl.h> 64 1.1 lukem #include <stdio.h> 65 1.1 lukem #include <stdlib.h> 66 1.1 lukem #include <string.h> 67 1.1 lukem #include <unistd.h> 68 1.57 christos #include <time.h> 69 1.1 lukem 70 1.199 christos #include "ssl.h" 71 1.1 lukem #include "ftp_var.h" 72 1.100 lukem #include "version.h" 73 1.1 lukem 74 1.27 lukem typedef enum { 75 1.27 lukem UNKNOWN_URL_T=-1, 76 1.27 lukem HTTP_URL_T, 77 1.199 christos HTTPS_URL_T, 78 1.27 lukem FTP_URL_T, 79 1.53 lukem FILE_URL_T, 80 1.53 lukem CLASSIC_URL_T 81 1.27 lukem } url_t; 82 1.27 lukem 83 1.214 christos struct authinfo { 84 1.214 christos char *auth; 85 1.214 christos char *user; 86 1.214 christos char *pass; 87 1.214 christos }; 88 1.214 christos 89 1.214 christos struct urlinfo { 90 1.214 christos char *host; 91 1.214 christos char *port; 92 1.214 christos char *path; 93 1.214 christos url_t utype; 94 1.214 christos in_port_t portnum; 95 1.214 christos }; 96 1.214 christos 97 1.218 christos struct posinfo { 98 1.218 christos off_t rangestart; 99 1.218 christos off_t rangeend; 100 1.218 christos off_t entitylen; 101 1.218 christos }; 102 1.218 christos 103 1.194 joerg __dead static void aborthttp(int); 104 1.203 christos __dead static void timeouthttp(int); 105 1.147 christos #ifndef NO_AUTH 106 1.214 christos static int auth_url(const char *, char **, const struct authinfo *); 107 1.154 lukem static void base64_encode(const unsigned char *, size_t, unsigned char *); 108 1.147 christos #endif 109 1.235 christos static int go_fetch(const char *, struct urlinfo *); 110 1.111 lukem static int fetch_ftp(const char *); 111 1.235 christos static int fetch_url(const char *, const char *, char *, char *, 112 1.235 christos struct urlinfo *); 113 1.154 lukem static const char *match_token(const char **, const char *); 114 1.214 christos static int parse_url(const char *, const char *, struct urlinfo *, 115 1.235 christos struct authinfo *, struct urlinfo *); 116 1.111 lukem static void url_decode(char *); 117 1.214 christos static void freeauthinfo(struct authinfo *); 118 1.214 christos static void freeurlinfo(struct urlinfo *); 119 1.12 lukem 120 1.42 lukem static int redirect_loop; 121 1.42 lukem 122 1.12 lukem 123 1.150 lukem #define STRNEQUAL(a,b) (strncasecmp((a), (b), sizeof((b))-1) == 0) 124 1.150 lukem #define ISLWS(x) ((x)=='\r' || (x)=='\n' || (x)==' ' || (x)=='\t') 125 1.150 lukem #define SKIPLWS(x) do { while (ISLWS((*x))) x++; } while (0) 126 1.150 lukem 127 1.150 lukem 128 1.25 lukem #define ABOUT_URL "about:" /* propaganda */ 129 1.25 lukem #define FILE_URL "file://" /* file URL prefix */ 130 1.1 lukem #define FTP_URL "ftp://" /* ftp URL prefix */ 131 1.1 lukem #define HTTP_URL "http://" /* http URL prefix */ 132 1.199 christos #ifdef WITH_SSL 133 1.199 christos #define HTTPS_URL "https://" /* https URL prefix */ 134 1.1 lukem 135 1.199 christos #define IS_HTTP_TYPE(urltype) \ 136 1.199 christos (((urltype) == HTTP_URL_T) || ((urltype) == HTTPS_URL_T)) 137 1.199 christos #else 138 1.199 christos #define IS_HTTP_TYPE(urltype) \ 139 1.199 christos ((urltype) == HTTP_URL_T) 140 1.199 christos #endif 141 1.1 lukem 142 1.233 christos /** 143 1.233 christos * fwrite(3) replacement that just uses write(2). Many stdio implementations 144 1.233 christos * don't handle interrupts properly and corrupt the output. We are taking 145 1.233 christos * alarm interrupts because of the progress bar. 146 1.233 christos * 147 1.233 christos * Assumes `fp' is pristine with no prior I/O calls on it. 148 1.233 christos */ 149 1.233 christos static size_t 150 1.233 christos maxwrite(const void *buf, size_t size, size_t nmemb, FILE *fp) 151 1.233 christos { 152 1.233 christos const char *p = buf; 153 1.233 christos ssize_t nwr = 0; 154 1.233 christos ssize_t n; 155 1.233 christos int fd = fileno(fp); 156 1.233 christos 157 1.233 christos size *= nmemb; /* assume no overflow */ 158 1.233 christos 159 1.233 christos while (size > 0) { 160 1.233 christos if ((n = write(fd, p, size)) == -1) { 161 1.233 christos switch (errno) { 162 1.233 christos case EINTR: 163 1.233 christos case EAGAIN: 164 1.233 christos #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN 165 1.233 christos case EWOULDBLOCK: 166 1.233 christos #endif 167 1.233 christos continue; 168 1.233 christos default: 169 1.233 christos return nwr; 170 1.233 christos } 171 1.233 christos } 172 1.233 christos p += n; 173 1.233 christos nwr += n; 174 1.233 christos size -= n; 175 1.233 christos } 176 1.233 christos return nwr; 177 1.233 christos } 178 1.233 christos 179 1.154 lukem /* 180 1.154 lukem * Determine if token is the next word in buf (case insensitive). 181 1.154 lukem * If so, advance buf past the token and any trailing LWS, and 182 1.154 lukem * return a pointer to the token (in buf). Otherwise, return NULL. 183 1.172 christos * token may be preceded by LWS. 184 1.154 lukem * token must be followed by LWS or NUL. (I.e, don't partial match). 185 1.154 lukem */ 186 1.154 lukem static const char * 187 1.154 lukem match_token(const char **buf, const char *token) 188 1.154 lukem { 189 1.154 lukem const char *p, *orig; 190 1.154 lukem size_t tlen; 191 1.154 lukem 192 1.154 lukem tlen = strlen(token); 193 1.154 lukem p = *buf; 194 1.154 lukem SKIPLWS(p); 195 1.154 lukem orig = p; 196 1.154 lukem if (strncasecmp(p, token, tlen) != 0) 197 1.154 lukem return NULL; 198 1.154 lukem p += tlen; 199 1.154 lukem if (*p != '\0' && !ISLWS(*p)) 200 1.154 lukem return NULL; 201 1.154 lukem SKIPLWS(p); 202 1.154 lukem orig = *buf; 203 1.154 lukem *buf = p; 204 1.154 lukem return orig; 205 1.154 lukem } 206 1.154 lukem 207 1.214 christos static void 208 1.218 christos initposinfo(struct posinfo *pi) 209 1.218 christos { 210 1.218 christos pi->rangestart = pi->rangeend = pi->entitylen = -1; 211 1.218 christos } 212 1.218 christos 213 1.218 christos static void 214 1.214 christos initauthinfo(struct authinfo *ai, char *auth) 215 1.214 christos { 216 1.214 christos ai->auth = auth; 217 1.214 christos ai->user = ai->pass = 0; 218 1.214 christos } 219 1.214 christos 220 1.214 christos static void 221 1.214 christos freeauthinfo(struct authinfo *a) 222 1.214 christos { 223 1.214 christos FREEPTR(a->user); 224 1.214 christos if (a->pass != NULL) 225 1.214 christos memset(a->pass, 0, strlen(a->pass)); 226 1.214 christos FREEPTR(a->pass); 227 1.214 christos } 228 1.214 christos 229 1.214 christos static void 230 1.214 christos initurlinfo(struct urlinfo *ui) 231 1.214 christos { 232 1.214 christos ui->host = ui->port = ui->path = 0; 233 1.214 christos ui->utype = UNKNOWN_URL_T; 234 1.214 christos ui->portnum = 0; 235 1.214 christos } 236 1.214 christos 237 1.215 christos static void 238 1.215 christos copyurlinfo(struct urlinfo *dui, struct urlinfo *sui) 239 1.215 christos { 240 1.215 christos dui->host = ftp_strdup(sui->host); 241 1.215 christos dui->port = ftp_strdup(sui->port); 242 1.215 christos dui->path = ftp_strdup(sui->path); 243 1.215 christos dui->utype = sui->utype; 244 1.215 christos dui->portnum = sui->portnum; 245 1.215 christos } 246 1.215 christos 247 1.214 christos static void 248 1.214 christos freeurlinfo(struct urlinfo *ui) 249 1.214 christos { 250 1.214 christos FREEPTR(ui->host); 251 1.214 christos FREEPTR(ui->port); 252 1.214 christos FREEPTR(ui->path); 253 1.214 christos } 254 1.214 christos 255 1.147 christos #ifndef NO_AUTH 256 1.27 lukem /* 257 1.44 lukem * Generate authorization response based on given authentication challenge. 258 1.44 lukem * Returns -1 if an error occurred, otherwise 0. 259 1.44 lukem * Sets response to a malloc(3)ed string; caller should free. 260 1.44 lukem */ 261 1.44 lukem static int 262 1.214 christos auth_url(const char *challenge, char **response, const struct authinfo *auth) 263 1.44 lukem { 264 1.240 christos const char *cp, *ep, *scheme, *errormsg; 265 1.240 christos char *clear, *realm; 266 1.187 lukem char uuser[BUFSIZ], *gotpass; 267 1.187 lukem const char *upass; 268 1.82 lukem int rval; 269 1.82 lukem size_t len, clen, rlen; 270 1.44 lukem 271 1.44 lukem *response = NULL; 272 1.154 lukem clear = realm = NULL; 273 1.44 lukem rval = -1; 274 1.154 lukem cp = challenge; 275 1.154 lukem scheme = "Basic"; /* only support Basic authentication */ 276 1.187 lukem gotpass = NULL; 277 1.44 lukem 278 1.235 christos DPRINTF("%s: challenge `%s'\n", __func__, challenge); 279 1.44 lukem 280 1.154 lukem if (! match_token(&cp, scheme)) { 281 1.175 lukem warnx("Unsupported authentication challenge `%s'", 282 1.44 lukem challenge); 283 1.44 lukem goto cleanup_auth_url; 284 1.44 lukem } 285 1.44 lukem 286 1.91 lukem #define REALM "realm=\"" 287 1.150 lukem if (STRNEQUAL(cp, REALM)) 288 1.44 lukem cp += sizeof(REALM) - 1; 289 1.44 lukem else { 290 1.175 lukem warnx("Unsupported authentication challenge `%s'", 291 1.44 lukem challenge); 292 1.44 lukem goto cleanup_auth_url; 293 1.44 lukem } 294 1.154 lukem /* XXX: need to improve quoted-string parsing to support \ quoting, etc. */ 295 1.44 lukem if ((ep = strchr(cp, '\"')) != NULL) { 296 1.186 lukem len = ep - cp; 297 1.166 christos realm = (char *)ftp_malloc(len + 1); 298 1.78 lukem (void)strlcpy(realm, cp, len + 1); 299 1.44 lukem } else { 300 1.175 lukem warnx("Unsupported authentication challenge `%s'", 301 1.44 lukem challenge); 302 1.44 lukem goto cleanup_auth_url; 303 1.44 lukem } 304 1.44 lukem 305 1.154 lukem fprintf(ttyout, "Username for `%s': ", realm); 306 1.214 christos if (auth->user != NULL) { 307 1.214 christos (void)strlcpy(uuser, auth->user, sizeof(uuser)); 308 1.186 lukem fprintf(ttyout, "%s\n", uuser); 309 1.154 lukem } else { 310 1.54 lukem (void)fflush(ttyout); 311 1.188 roy if (get_line(stdin, uuser, sizeof(uuser), &errormsg) < 0) { 312 1.162 lukem warnx("%s; can't authenticate", errormsg); 313 1.54 lukem goto cleanup_auth_url; 314 1.90 lukem } 315 1.54 lukem } 316 1.214 christos if (auth->pass != NULL) 317 1.214 christos upass = auth->pass; 318 1.174 lukem else { 319 1.187 lukem gotpass = getpass("Password: "); 320 1.187 lukem if (gotpass == NULL) { 321 1.174 lukem warnx("Can't read password"); 322 1.174 lukem goto cleanup_auth_url; 323 1.174 lukem } 324 1.187 lukem upass = gotpass; 325 1.174 lukem } 326 1.44 lukem 327 1.187 lukem clen = strlen(uuser) + strlen(upass) + 2; /* user + ":" + pass + "\0" */ 328 1.166 christos clear = (char *)ftp_malloc(clen); 329 1.186 lukem (void)strlcpy(clear, uuser, clen); 330 1.78 lukem (void)strlcat(clear, ":", clen); 331 1.187 lukem (void)strlcat(clear, upass, clen); 332 1.187 lukem if (gotpass) 333 1.187 lukem memset(gotpass, 0, strlen(gotpass)); 334 1.44 lukem 335 1.64 lukem /* scheme + " " + enc + "\0" */ 336 1.74 lukem rlen = strlen(scheme) + 1 + (clen + 2) * 4 / 3 + 1; 337 1.214 christos *response = ftp_malloc(rlen); 338 1.78 lukem (void)strlcpy(*response, scheme, rlen); 339 1.72 lukem len = strlcat(*response, " ", rlen); 340 1.235 christos /* use `clen - 1' to not encode the trailing NUL */ 341 1.158 lukem base64_encode((unsigned char *)clear, clen - 1, 342 1.158 lukem (unsigned char *)*response + len); 343 1.80 lukem memset(clear, 0, clen); 344 1.44 lukem rval = 0; 345 1.44 lukem 346 1.118 lukem cleanup_auth_url: 347 1.44 lukem FREEPTR(clear); 348 1.44 lukem FREEPTR(realm); 349 1.44 lukem return (rval); 350 1.44 lukem } 351 1.44 lukem 352 1.44 lukem /* 353 1.44 lukem * Encode len bytes starting at clear using base64 encoding into encoded, 354 1.44 lukem * which should be at least ((len + 2) * 4 / 3 + 1) in size. 355 1.44 lukem */ 356 1.124 lukem static void 357 1.154 lukem base64_encode(const unsigned char *clear, size_t len, unsigned char *encoded) 358 1.44 lukem { 359 1.154 lukem static const unsigned char enc[] = 360 1.44 lukem "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 361 1.154 lukem unsigned char *cp; 362 1.187 lukem size_t i; 363 1.44 lukem 364 1.44 lukem cp = encoded; 365 1.44 lukem for (i = 0; i < len; i += 3) { 366 1.44 lukem *(cp++) = enc[((clear[i + 0] >> 2))]; 367 1.44 lukem *(cp++) = enc[((clear[i + 0] << 4) & 0x30) 368 1.44 lukem | ((clear[i + 1] >> 4) & 0x0f)]; 369 1.44 lukem *(cp++) = enc[((clear[i + 1] << 2) & 0x3c) 370 1.44 lukem | ((clear[i + 2] >> 6) & 0x03)]; 371 1.235 christos *(cp++) = enc[((clear[i + 2] ) & 0x3f)]; 372 1.44 lukem } 373 1.44 lukem *cp = '\0'; 374 1.44 lukem while (i-- > len) 375 1.44 lukem *(--cp) = '='; 376 1.44 lukem } 377 1.147 christos #endif 378 1.44 lukem 379 1.50 lukem /* 380 1.50 lukem * Decode %xx escapes in given string, `in-place'. 381 1.50 lukem */ 382 1.50 lukem static void 383 1.111 lukem url_decode(char *url) 384 1.50 lukem { 385 1.50 lukem unsigned char *p, *q; 386 1.50 lukem 387 1.50 lukem if (EMPTYSTRING(url)) 388 1.50 lukem return; 389 1.75 lukem p = q = (unsigned char *)url; 390 1.50 lukem 391 1.91 lukem #define HEXTOINT(x) (x - (isdigit(x) ? '0' : (islower(x) ? 'a' : 'A') - 10)) 392 1.50 lukem while (*p) { 393 1.50 lukem if (p[0] == '%' 394 1.50 lukem && p[1] && isxdigit((unsigned char)p[1]) 395 1.50 lukem && p[2] && isxdigit((unsigned char)p[2])) { 396 1.50 lukem *q++ = HEXTOINT(p[1]) * 16 + HEXTOINT(p[2]); 397 1.50 lukem p+=3; 398 1.50 lukem } else 399 1.50 lukem *q++ = *p++; 400 1.50 lukem } 401 1.50 lukem *q = '\0'; 402 1.50 lukem } 403 1.50 lukem 404 1.235 christos static const char * 405 1.235 christos get_port(const struct urlinfo *ui) 406 1.235 christos { 407 1.235 christos 408 1.235 christos switch(ui->utype) { 409 1.235 christos case HTTP_URL_T: 410 1.235 christos return httpport; 411 1.235 christos case FTP_URL_T: 412 1.235 christos return ftpport; 413 1.235 christos case FILE_URL_T: 414 1.235 christos return ""; 415 1.235 christos #ifdef WITH_SSL 416 1.235 christos case HTTPS_URL_T: 417 1.235 christos return httpsport; 418 1.235 christos #endif 419 1.235 christos default: 420 1.235 christos return NULL; 421 1.235 christos } 422 1.235 christos } 423 1.235 christos 424 1.235 christos static int 425 1.235 christos use_relative(const struct urlinfo *ui) 426 1.235 christos { 427 1.235 christos if (ui == NULL) 428 1.235 christos return 0; 429 1.235 christos switch (ui->utype) { 430 1.235 christos case HTTP_URL_T: 431 1.235 christos case FILE_URL_T: 432 1.235 christos #ifdef WITH_SSL 433 1.235 christos case HTTPS_URL_T: 434 1.235 christos #endif 435 1.235 christos return 1; 436 1.235 christos default: 437 1.235 christos return 0; 438 1.235 christos } 439 1.235 christos } 440 1.44 lukem 441 1.44 lukem /* 442 1.193 lukem * Parse URL of form (per RFC 3986): 443 1.145 lukem * <type>://[<user>[:<password>]@]<host>[:<port>][/<path>] 444 1.27 lukem * Returns -1 if a parse error occurred, otherwise 0. 445 1.50 lukem * It's the caller's responsibility to url_decode() the returned 446 1.50 lukem * user, pass and path. 447 1.63 lukem * 448 1.27 lukem * Sets type to url_t, each of the given char ** pointers to a 449 1.27 lukem * malloc(3)ed strings of the relevant section, and port to 450 1.41 lukem * the number given, or ftpport if ftp://, or httpport if http://. 451 1.52 lukem * 452 1.193 lukem * XXX: this is not totally RFC 3986 compliant; <path> will have the 453 1.53 lukem * leading `/' unless it's an ftp:// URL, as this makes things easier 454 1.177 lukem * for file:// and http:// URLs. ftp:// URLs have the `/' between the 455 1.141 wiz * host and the URL-path removed, but any additional leading slashes 456 1.141 wiz * in the URL-path are retained (because they imply that we should 457 1.53 lukem * later do "CWD" with a null argument). 458 1.53 lukem * 459 1.53 lukem * Examples: 460 1.141 wiz * input URL output path 461 1.53 lukem * --------- ----------- 462 1.177 lukem * "http://host" "/" 463 1.177 lukem * "http://host/" "/" 464 1.177 lukem * "http://host/path" "/path" 465 1.63 lukem * "file://host/dir/file" "dir/file" 466 1.177 lukem * "ftp://host" "" 467 1.63 lukem * "ftp://host/" "" 468 1.177 lukem * "ftp://host//" "/" 469 1.177 lukem * "ftp://host/dir/file" "dir/file" 470 1.53 lukem * "ftp://host//dir/file" "/dir/file" 471 1.27 lukem */ 472 1.214 christos 473 1.27 lukem static int 474 1.214 christos parse_url(const char *url, const char *desc, struct urlinfo *ui, 475 1.235 christos struct authinfo *auth, struct urlinfo *rui) 476 1.27 lukem { 477 1.187 lukem const char *origurl, *tport; 478 1.187 lukem char *cp, *ep, *thost; 479 1.82 lukem size_t len; 480 1.27 lukem 481 1.214 christos if (url == NULL || desc == NULL || ui == NULL || auth == NULL) 482 1.27 lukem errx(1, "parse_url: invoked with NULL argument!"); 483 1.183 lukem DPRINTF("parse_url: %s `%s'\n", desc, url); 484 1.27 lukem 485 1.82 lukem origurl = url; 486 1.27 lukem 487 1.150 lukem if (STRNEQUAL(url, HTTP_URL)) { 488 1.27 lukem url += sizeof(HTTP_URL) - 1; 489 1.214 christos ui->utype = HTTP_URL_T; 490 1.214 christos ui->portnum = HTTP_PORT; 491 1.150 lukem } else if (STRNEQUAL(url, FTP_URL)) { 492 1.27 lukem url += sizeof(FTP_URL) - 1; 493 1.214 christos ui->utype = FTP_URL_T; 494 1.214 christos ui->portnum = FTP_PORT; 495 1.150 lukem } else if (STRNEQUAL(url, FILE_URL)) { 496 1.27 lukem url += sizeof(FILE_URL) - 1; 497 1.214 christos ui->utype = FILE_URL_T; 498 1.199 christos #ifdef WITH_SSL 499 1.199 christos } else if (STRNEQUAL(url, HTTPS_URL)) { 500 1.199 christos url += sizeof(HTTPS_URL) - 1; 501 1.214 christos ui->utype = HTTPS_URL_T; 502 1.214 christos ui->portnum = HTTPS_PORT; 503 1.199 christos #endif 504 1.235 christos } else if (rui != NULL) { 505 1.235 christos copyurlinfo(ui, rui); 506 1.27 lukem } else { 507 1.27 lukem warnx("Invalid %s `%s'", desc, url); 508 1.118 lukem cleanup_parse_url: 509 1.214 christos freeauthinfo(auth); 510 1.214 christos freeurlinfo(ui); 511 1.27 lukem return (-1); 512 1.27 lukem } 513 1.27 lukem 514 1.235 christos 515 1.27 lukem if (*url == '\0') 516 1.27 lukem return (0); 517 1.27 lukem 518 1.27 lukem /* find [user[:pass]@]host[:port] */ 519 1.27 lukem ep = strchr(url, '/'); 520 1.27 lukem if (ep == NULL) 521 1.166 christos thost = ftp_strdup(url); 522 1.27 lukem else { 523 1.50 lukem len = ep - url; 524 1.166 christos thost = (char *)ftp_malloc(len + 1); 525 1.78 lukem (void)strlcpy(thost, url, len + 1); 526 1.214 christos if (ui->utype == FTP_URL_T) /* skip first / for ftp URLs */ 527 1.53 lukem ep++; 528 1.214 christos ui->path = ftp_strdup(ep); 529 1.27 lukem } 530 1.27 lukem 531 1.191 christos cp = strchr(thost, '@'); /* look for user[:pass]@ in URLs */ 532 1.54 lukem if (cp != NULL) { 533 1.214 christos if (ui->utype == FTP_URL_T) 534 1.54 lukem anonftp = 0; /* disable anonftp */ 535 1.214 christos auth->user = thost; 536 1.27 lukem *cp = '\0'; 537 1.166 christos thost = ftp_strdup(cp + 1); 538 1.214 christos cp = strchr(auth->user, ':'); 539 1.27 lukem if (cp != NULL) { 540 1.27 lukem *cp = '\0'; 541 1.214 christos auth->pass = ftp_strdup(cp + 1); 542 1.27 lukem } 543 1.214 christos url_decode(auth->user); 544 1.214 christos if (auth->pass) 545 1.214 christos url_decode(auth->pass); 546 1.63 lukem } 547 1.63 lukem 548 1.63 lukem #ifdef INET6 549 1.63 lukem /* 550 1.63 lukem * Check if thost is an encoded IPv6 address, as per 551 1.193 lukem * RFC 3986: 552 1.63 lukem * `[' ipv6-address ']' 553 1.63 lukem */ 554 1.63 lukem if (*thost == '[') { 555 1.63 lukem cp = thost + 1; 556 1.63 lukem if ((ep = strchr(cp, ']')) == NULL || 557 1.106 itojun (ep[1] != '\0' && ep[1] != ':')) { 558 1.63 lukem warnx("Invalid address `%s' in %s `%s'", 559 1.82 lukem thost, desc, origurl); 560 1.63 lukem goto cleanup_parse_url; 561 1.63 lukem } 562 1.103 lukem len = ep - cp; /* change `[xyz]' -> `xyz' */ 563 1.63 lukem memmove(thost, thost + 1, len); 564 1.63 lukem thost[len] = '\0'; 565 1.63 lukem if (! isipv6addr(thost)) { 566 1.63 lukem warnx("Invalid IPv6 address `%s' in %s `%s'", 567 1.82 lukem thost, desc, origurl); 568 1.63 lukem goto cleanup_parse_url; 569 1.63 lukem } 570 1.63 lukem cp = ep + 1; 571 1.63 lukem if (*cp == ':') 572 1.63 lukem cp++; 573 1.63 lukem else 574 1.63 lukem cp = NULL; 575 1.27 lukem } else 576 1.63 lukem #endif /* INET6 */ 577 1.177 lukem if ((cp = strchr(thost, ':')) != NULL) 578 1.184 lukem *cp++ = '\0'; 579 1.235 christos if (*thost != '\0') 580 1.235 christos ui->host = thost; 581 1.60 itojun 582 1.27 lukem /* look for [:port] */ 583 1.27 lukem if (cp != NULL) { 584 1.184 lukem unsigned long nport; 585 1.27 lukem 586 1.184 lukem nport = strtoul(cp, &ep, 10); 587 1.184 lukem if (*cp == '\0' || *ep != '\0' || 588 1.184 lukem nport < 1 || nport > MAX_IN_PORT_T) { 589 1.122 lukem warnx("Unknown port `%s' in %s `%s'", 590 1.122 lukem cp, desc, origurl); 591 1.27 lukem goto cleanup_parse_url; 592 1.27 lukem } 593 1.240 christos ui->portnum = (in_port_t)nport; 594 1.63 lukem tport = cp; 595 1.235 christos } else 596 1.235 christos tport = get_port(ui); 597 1.235 christos 598 1.82 lukem 599 1.95 lukem if (tport != NULL) 600 1.214 christos ui->port = ftp_strdup(tport); 601 1.214 christos if (ui->path == NULL) { 602 1.177 lukem const char *emptypath = "/"; 603 1.214 christos if (ui->utype == FTP_URL_T) /* skip first / for ftp URLs */ 604 1.177 lukem emptypath++; 605 1.214 christos ui->path = ftp_strdup(emptypath); 606 1.177 lukem } 607 1.27 lukem 608 1.235 christos DPRINTF("%s: user `%s' pass `%s' host %s port %s(%d) " 609 1.235 christos "path `%s'\n", __func__, 610 1.214 christos STRorNULL(auth->user), STRorNULL(auth->pass), 611 1.214 christos STRorNULL(ui->host), STRorNULL(ui->port), 612 1.214 christos ui->portnum ? ui->portnum : -1, STRorNULL(ui->path)); 613 1.27 lukem 614 1.27 lukem return (0); 615 1.27 lukem } 616 1.27 lukem 617 1.240 christos static sigjmp_buf httpabort; 618 1.2 lukem 619 1.210 christos static int 620 1.236 mlelstv ftp_socket(const struct urlinfo *ui, void **ssl, struct authinfo *auth) 621 1.210 christos { 622 1.235 christos struct addrinfo hints, *res, *res0 = NULL; 623 1.210 christos int error; 624 1.210 christos int s; 625 1.214 christos const char *host = ui->host; 626 1.214 christos const char *port = ui->port; 627 1.236 mlelstv char *fuser = NULL, *pass = NULL, *facct = NULL; 628 1.236 mlelstv int n; 629 1.214 christos 630 1.214 christos if (ui->utype != HTTPS_URL_T) 631 1.214 christos ssl = NULL; 632 1.210 christos 633 1.210 christos memset(&hints, 0, sizeof(hints)); 634 1.210 christos hints.ai_flags = 0; 635 1.210 christos hints.ai_family = family; 636 1.210 christos hints.ai_socktype = SOCK_STREAM; 637 1.210 christos hints.ai_protocol = 0; 638 1.210 christos 639 1.210 christos error = getaddrinfo(host, port, &hints, &res0); 640 1.210 christos if (error) { 641 1.210 christos warnx("Can't LOOKUP `%s:%s': %s", host, port, 642 1.210 christos (error == EAI_SYSTEM) ? strerror(errno) 643 1.210 christos : gai_strerror(error)); 644 1.210 christos return -1; 645 1.210 christos } 646 1.210 christos 647 1.210 christos if (res0->ai_canonname) 648 1.210 christos host = res0->ai_canonname; 649 1.210 christos 650 1.210 christos s = -1; 651 1.210 christos if (ssl) 652 1.210 christos *ssl = NULL; 653 1.210 christos for (res = res0; res; res = res->ai_next) { 654 1.210 christos char hname[NI_MAXHOST], sname[NI_MAXSERV]; 655 1.210 christos 656 1.210 christos ai_unmapped(res); 657 1.210 christos if (getnameinfo(res->ai_addr, res->ai_addrlen, 658 1.210 christos hname, sizeof(hname), sname, sizeof(sname), 659 1.210 christos NI_NUMERICHOST | NI_NUMERICSERV) != 0) { 660 1.210 christos strlcpy(hname, "?", sizeof(hname)); 661 1.210 christos strlcpy(sname, "?", sizeof(sname)); 662 1.210 christos } 663 1.210 christos 664 1.210 christos if (verbose && res0->ai_next) { 665 1.210 christos #ifdef INET6 666 1.210 christos if(res->ai_family == AF_INET6) { 667 1.210 christos fprintf(ttyout, "Trying [%s]:%s ...\n", 668 1.210 christos hname, sname); 669 1.210 christos } else { 670 1.210 christos #endif 671 1.210 christos fprintf(ttyout, "Trying %s:%s ...\n", 672 1.210 christos hname, sname); 673 1.210 christos #ifdef INET6 674 1.210 christos } 675 1.210 christos #endif 676 1.210 christos } 677 1.210 christos 678 1.210 christos s = socket(res->ai_family, SOCK_STREAM, res->ai_protocol); 679 1.210 christos if (s < 0) { 680 1.210 christos warn( 681 1.210 christos "Can't create socket for connection to " 682 1.210 christos "`%s:%s'", hname, sname); 683 1.210 christos continue; 684 1.210 christos } 685 1.210 christos 686 1.210 christos if (ftp_connect(s, res->ai_addr, res->ai_addrlen, 687 1.210 christos verbose || !res->ai_next) < 0) { 688 1.210 christos close(s); 689 1.210 christos s = -1; 690 1.210 christos continue; 691 1.210 christos } 692 1.210 christos 693 1.236 mlelstv if (ruserpass("", &fuser, &pass, &facct) < 0) { 694 1.236 mlelstv close(s); 695 1.236 mlelstv s = -1; 696 1.236 mlelstv continue; 697 1.236 mlelstv } 698 1.236 mlelstv 699 1.236 mlelstv if (autologin) { 700 1.236 mlelstv if (fuser != NULL && auth->user == NULL) 701 1.236 mlelstv auth->user = ftp_strdup(fuser); 702 1.236 mlelstv if (pass != NULL && auth->pass == NULL) 703 1.236 mlelstv auth->pass = ftp_strdup(pass); 704 1.236 mlelstv } 705 1.236 mlelstv 706 1.236 mlelstv for (n = 0; n < macnum; ++n) { 707 1.236 mlelstv if (!strcmp("init", macros[n].mac_name)) { 708 1.236 mlelstv (void)strlcpy(line, "$init", sizeof(line)); 709 1.236 mlelstv makeargv(); 710 1.236 mlelstv domacro(margc, margv); 711 1.236 mlelstv break; 712 1.236 mlelstv } 713 1.236 mlelstv } 714 1.236 mlelstv 715 1.210 christos #ifdef WITH_SSL 716 1.210 christos if (ssl) { 717 1.210 christos if ((*ssl = fetch_start_ssl(s, host)) == NULL) { 718 1.210 christos close(s); 719 1.210 christos s = -1; 720 1.210 christos continue; 721 1.210 christos } 722 1.210 christos } 723 1.210 christos #endif 724 1.210 christos break; 725 1.210 christos } 726 1.236 mlelstv 727 1.236 mlelstv FREEPTR(fuser); 728 1.236 mlelstv if (pass != NULL) 729 1.236 mlelstv memset(pass, 0, strlen(pass)); 730 1.236 mlelstv FREEPTR(pass); 731 1.236 mlelstv if (facct != NULL) 732 1.236 mlelstv memset(facct, 0, strlen(facct)); 733 1.236 mlelstv FREEPTR(facct); 734 1.236 mlelstv 735 1.210 christos if (res0) 736 1.210 christos freeaddrinfo(res0); 737 1.210 christos return s; 738 1.210 christos } 739 1.210 christos 740 1.211 christos static int 741 1.211 christos handle_noproxy(const char *host, in_port_t portnum) 742 1.211 christos { 743 1.211 christos 744 1.211 christos char *cp, *ep, *np, *np_copy, *np_iter, *no_proxy; 745 1.211 christos unsigned long np_port; 746 1.211 christos size_t hlen, plen; 747 1.211 christos int isproxy = 1; 748 1.211 christos 749 1.211 christos /* check URL against list of no_proxied sites */ 750 1.211 christos no_proxy = getoptionvalue("no_proxy"); 751 1.211 christos if (EMPTYSTRING(no_proxy)) 752 1.211 christos return isproxy; 753 1.211 christos 754 1.211 christos np_iter = np_copy = ftp_strdup(no_proxy); 755 1.211 christos hlen = strlen(host); 756 1.211 christos while ((cp = strsep(&np_iter, " ,")) != NULL) { 757 1.211 christos if (*cp == '\0') 758 1.211 christos continue; 759 1.211 christos if ((np = strrchr(cp, ':')) != NULL) { 760 1.235 christos *np++ = '\0'; 761 1.211 christos np_port = strtoul(np, &ep, 10); 762 1.211 christos if (*np == '\0' || *ep != '\0') 763 1.211 christos continue; 764 1.211 christos if (np_port != portnum) 765 1.211 christos continue; 766 1.211 christos } 767 1.211 christos plen = strlen(cp); 768 1.211 christos if (hlen < plen) 769 1.211 christos continue; 770 1.211 christos if (strncasecmp(host + hlen - plen, cp, plen) == 0) { 771 1.211 christos isproxy = 0; 772 1.211 christos break; 773 1.211 christos } 774 1.211 christos } 775 1.211 christos FREEPTR(np_copy); 776 1.211 christos return isproxy; 777 1.211 christos } 778 1.211 christos 779 1.212 christos static int 780 1.214 christos handle_proxy(const char *url, const char *penv, struct urlinfo *ui, 781 1.214 christos struct authinfo *pauth) 782 1.212 christos { 783 1.214 christos struct urlinfo pui; 784 1.212 christos 785 1.214 christos if (isipv6addr(ui->host) && strchr(ui->host, '%') != NULL) { 786 1.212 christos warnx("Scoped address notation `%s' disallowed via web proxy", 787 1.214 christos ui->host); 788 1.212 christos return -1; 789 1.212 christos } 790 1.212 christos 791 1.214 christos initurlinfo(&pui); 792 1.235 christos if (parse_url(penv, "proxy URL", &pui, pauth, NULL) == -1) 793 1.212 christos return -1; 794 1.212 christos 795 1.214 christos if ((!IS_HTTP_TYPE(pui.utype) && pui.utype != FTP_URL_T) || 796 1.214 christos EMPTYSTRING(pui.host) || 797 1.214 christos (! EMPTYSTRING(pui.path) && strcmp(pui.path, "/") != 0)) { 798 1.212 christos warnx("Malformed proxy URL `%s'", penv); 799 1.214 christos freeurlinfo(&pui); 800 1.212 christos return -1; 801 1.212 christos } 802 1.212 christos 803 1.214 christos FREEPTR(pui.path); 804 1.237 mlelstv #ifdef WITH_SSL 805 1.237 mlelstv if (ui->utype == HTTPS_URL_T) 806 1.237 mlelstv pui.path = ftp_strdup(ui->path); 807 1.237 mlelstv else 808 1.237 mlelstv #endif 809 1.237 mlelstv pui.path = ftp_strdup(url); 810 1.212 christos 811 1.214 christos freeurlinfo(ui); 812 1.214 christos *ui = pui; 813 1.212 christos 814 1.212 christos return 0; 815 1.212 christos } 816 1.212 christos 817 1.214 christos static void 818 1.216 nonaka print_host(FETCH *fin, const struct urlinfo *ui) 819 1.214 christos { 820 1.214 christos char *h, *p; 821 1.214 christos 822 1.216 nonaka if (strchr(ui->host, ':') == NULL) { 823 1.216 nonaka fetch_printf(fin, "Host: %s", ui->host); 824 1.216 nonaka } else { 825 1.216 nonaka /* 826 1.216 nonaka * strip off IPv6 scope identifier, since it is 827 1.216 nonaka * local to the node 828 1.216 nonaka */ 829 1.216 nonaka h = ftp_strdup(ui->host); 830 1.216 nonaka if (isipv6addr(h) && (p = strchr(h, '%')) != NULL) 831 1.216 nonaka *p = '\0'; 832 1.216 nonaka 833 1.216 nonaka fetch_printf(fin, "Host: [%s]", h); 834 1.216 nonaka free(h); 835 1.214 christos } 836 1.214 christos 837 1.216 nonaka if ((ui->utype == HTTP_URL_T && ui->portnum != HTTP_PORT) || 838 1.216 nonaka (ui->utype == HTTPS_URL_T && ui->portnum != HTTPS_PORT)) 839 1.216 nonaka fetch_printf(fin, ":%u", ui->portnum); 840 1.216 nonaka fetch_printf(fin, "\r\n"); 841 1.214 christos } 842 1.214 christos 843 1.214 christos static void 844 1.214 christos print_agent(FETCH *fin) 845 1.214 christos { 846 1.214 christos const char *useragent; 847 1.214 christos if ((useragent = getenv("FTPUSERAGENT")) != NULL) { 848 1.214 christos fetch_printf(fin, "User-Agent: %s\r\n", useragent); 849 1.214 christos } else { 850 1.214 christos fetch_printf(fin, "User-Agent: %s/%s\r\n", 851 1.214 christos FTP_PRODUCT, FTP_VERSION); 852 1.214 christos } 853 1.214 christos } 854 1.214 christos 855 1.214 christos static void 856 1.214 christos print_cache(FETCH *fin, int isproxy) 857 1.214 christos { 858 1.214 christos fetch_printf(fin, isproxy ? 859 1.214 christos "Pragma: no-cache\r\n" : 860 1.214 christos "Cache-Control: no-cache\r\n"); 861 1.214 christos } 862 1.214 christos 863 1.214 christos static int 864 1.216 nonaka print_get(FETCH *fin, int hasleading, int isproxy, const struct urlinfo *oui, 865 1.216 nonaka const struct urlinfo *ui) 866 1.214 christos { 867 1.215 christos const char *leading = hasleading ? ", " : " ("; 868 1.241 christos struct entry *np; 869 1.214 christos 870 1.214 christos if (isproxy) { 871 1.214 christos if (verbose) { 872 1.214 christos fprintf(ttyout, "%svia %s:%u", leading, 873 1.214 christos ui->host, ui->portnum); 874 1.214 christos leading = ", "; 875 1.214 christos hasleading++; 876 1.214 christos } 877 1.214 christos fetch_printf(fin, "GET %s HTTP/1.0\r\n", ui->path); 878 1.216 nonaka print_host(fin, oui); 879 1.214 christos return hasleading; 880 1.214 christos } 881 1.214 christos 882 1.214 christos fetch_printf(fin, "GET %s HTTP/1.1\r\n", ui->path); 883 1.216 nonaka print_host(fin, ui); 884 1.214 christos fetch_printf(fin, "Accept: */*\r\n"); 885 1.214 christos fetch_printf(fin, "Connection: close\r\n"); 886 1.241 christos SLIST_FOREACH(np, &custom_headers, entries) { 887 1.241 christos fetch_printf(fin, "%s\r\n", np->header); 888 1.241 christos } 889 1.241 christos 890 1.214 christos if (restart_point) { 891 1.214 christos fputs(leading, ttyout); 892 1.214 christos fetch_printf(fin, "Range: bytes=" LLF "-\r\n", 893 1.214 christos (LLT)restart_point); 894 1.214 christos fprintf(ttyout, "restarting at " LLF, (LLT)restart_point); 895 1.214 christos hasleading++; 896 1.214 christos } 897 1.214 christos return hasleading; 898 1.214 christos } 899 1.214 christos 900 1.214 christos static void 901 1.214 christos getmtime(const char *cp, time_t *mtime) 902 1.214 christos { 903 1.214 christos struct tm parsed; 904 1.214 christos const char *t; 905 1.214 christos 906 1.214 christos memset(&parsed, 0, sizeof(parsed)); 907 1.214 christos t = parse_rfc2616time(&parsed, cp); 908 1.214 christos 909 1.214 christos if (t == NULL) 910 1.214 christos return; 911 1.214 christos 912 1.214 christos parsed.tm_isdst = -1; 913 1.214 christos if (*t == '\0') 914 1.214 christos *mtime = timegm(&parsed); 915 1.214 christos 916 1.214 christos #ifndef NO_DEBUG 917 1.214 christos if (ftp_debug && *mtime != -1) { 918 1.214 christos fprintf(ttyout, "parsed time as: %s", 919 1.214 christos rfc2822time(localtime(mtime))); 920 1.214 christos } 921 1.214 christos #endif 922 1.214 christos } 923 1.214 christos 924 1.214 christos static int 925 1.216 nonaka print_proxy(FETCH *fin, int hasleading, const char *wwwauth, 926 1.216 nonaka const char *proxyauth) 927 1.214 christos { 928 1.216 nonaka const char *leading = hasleading ? ", " : " ("; 929 1.214 christos 930 1.214 christos if (wwwauth) { 931 1.214 christos if (verbose) { 932 1.214 christos fprintf(ttyout, "%swith authorization", leading); 933 1.214 christos hasleading++; 934 1.214 christos } 935 1.214 christos fetch_printf(fin, "Authorization: %s\r\n", wwwauth); 936 1.214 christos } 937 1.214 christos if (proxyauth) { 938 1.214 christos if (verbose) { 939 1.214 christos fprintf(ttyout, "%swith proxy authorization", leading); 940 1.214 christos hasleading++; 941 1.214 christos } 942 1.214 christos fetch_printf(fin, "Proxy-Authorization: %s\r\n", proxyauth); 943 1.214 christos } 944 1.214 christos return hasleading; 945 1.214 christos } 946 1.214 christos 947 1.219 christos #ifdef WITH_SSL 948 1.217 christos static void 949 1.217 christos print_connect(FETCH *fin, const struct urlinfo *ui) 950 1.217 christos { 951 1.217 christos char hname[NI_MAXHOST], *p; 952 1.217 christos const char *h; 953 1.217 christos 954 1.217 christos if (isipv6addr(ui->host)) { 955 1.217 christos /* 956 1.217 christos * strip off IPv6 scope identifier, 957 1.217 christos * since it is local to the node 958 1.217 christos */ 959 1.217 christos if ((p = strchr(ui->host, '%')) == NULL) 960 1.217 christos snprintf(hname, sizeof(hname), "[%s]", ui->host); 961 1.217 christos else 962 1.217 christos snprintf(hname, sizeof(hname), "[%.*s]", 963 1.217 christos (int)(p - ui->host), ui->host); 964 1.217 christos h = hname; 965 1.217 christos } else 966 1.217 christos h = ui->host; 967 1.217 christos 968 1.223 christos fetch_printf(fin, "CONNECT %s:%d HTTP/1.1\r\n", h, ui->portnum); 969 1.223 christos fetch_printf(fin, "Host: %s:%d\r\n", h, ui->portnum); 970 1.217 christos } 971 1.219 christos #endif 972 1.217 christos 973 1.235 christos #define C_OK 0 974 1.235 christos #define C_CLEANUP 1 975 1.235 christos #define C_IMPROPER 2 976 1.214 christos 977 1.214 christos static int 978 1.215 christos getresponseline(FETCH *fin, char *buf, size_t buflen, int *len) 979 1.214 christos { 980 1.215 christos const char *errormsg; 981 1.214 christos 982 1.214 christos alarmtimer(quit_time ? quit_time : 60); 983 1.215 christos *len = fetch_getline(fin, buf, buflen, &errormsg); 984 1.214 christos alarmtimer(0); 985 1.215 christos if (*len < 0) { 986 1.214 christos if (*errormsg == '\n') 987 1.214 christos errormsg++; 988 1.214 christos warnx("Receiving HTTP reply: %s", errormsg); 989 1.215 christos return C_CLEANUP; 990 1.214 christos } 991 1.215 christos while (*len > 0 && (ISLWS(buf[*len-1]))) 992 1.215 christos buf[--*len] = '\0'; 993 1.214 christos 994 1.215 christos if (*len) 995 1.215 christos DPRINTF("%s: received `%s'\n", __func__, buf); 996 1.215 christos return C_OK; 997 1.215 christos } 998 1.215 christos 999 1.215 christos static int 1000 1.215 christos getresponse(FETCH *fin, char **cp, size_t buflen, int *hcode) 1001 1.215 christos { 1002 1.215 christos int len, rv; 1003 1.215 christos char *ep, *buf = *cp; 1004 1.215 christos 1005 1.215 christos *hcode = 0; 1006 1.215 christos if ((rv = getresponseline(fin, buf, buflen, &len)) != C_OK) 1007 1.215 christos return rv; 1008 1.214 christos 1009 1.214 christos /* Determine HTTP response code */ 1010 1.215 christos *cp = strchr(buf, ' '); 1011 1.215 christos if (*cp == NULL) 1012 1.215 christos return C_IMPROPER; 1013 1.215 christos 1014 1.215 christos (*cp)++; 1015 1.214 christos 1016 1.215 christos *hcode = strtol(*cp, &ep, 10); 1017 1.214 christos if (*ep != '\0' && !isspace((unsigned char)*ep)) 1018 1.215 christos return C_IMPROPER; 1019 1.215 christos 1020 1.215 christos return C_OK; 1021 1.215 christos } 1022 1.215 christos 1023 1.215 christos static int 1024 1.218 christos parse_posinfo(const char **cp, struct posinfo *pi) 1025 1.218 christos { 1026 1.218 christos char *ep; 1027 1.218 christos if (!match_token(cp, "bytes")) 1028 1.218 christos return -1; 1029 1.218 christos 1030 1.218 christos if (**cp == '*') 1031 1.218 christos (*cp)++; 1032 1.218 christos else { 1033 1.218 christos pi->rangestart = STRTOLL(*cp, &ep, 10); 1034 1.218 christos if (pi->rangestart < 0 || *ep != '-') 1035 1.218 christos return -1; 1036 1.218 christos *cp = ep + 1; 1037 1.218 christos pi->rangeend = STRTOLL(*cp, &ep, 10); 1038 1.218 christos if (pi->rangeend < 0 || pi->rangeend < pi->rangestart) 1039 1.218 christos return -1; 1040 1.218 christos *cp = ep; 1041 1.218 christos } 1042 1.218 christos if (**cp != '/') 1043 1.218 christos return -1; 1044 1.218 christos (*cp)++; 1045 1.218 christos if (**cp == '*') 1046 1.218 christos (*cp)++; 1047 1.218 christos else { 1048 1.218 christos pi->entitylen = STRTOLL(*cp, &ep, 10); 1049 1.218 christos if (pi->entitylen < 0) 1050 1.218 christos return -1; 1051 1.218 christos *cp = ep; 1052 1.218 christos } 1053 1.218 christos if (**cp != '\0') 1054 1.218 christos return -1; 1055 1.218 christos 1056 1.218 christos #ifndef NO_DEBUG 1057 1.218 christos if (ftp_debug) { 1058 1.218 christos fprintf(ttyout, "parsed range as: "); 1059 1.218 christos if (pi->rangestart == -1) 1060 1.218 christos fprintf(ttyout, "*"); 1061 1.218 christos else 1062 1.218 christos fprintf(ttyout, LLF "-" LLF, (LLT)pi->rangestart, 1063 1.218 christos (LLT)pi->rangeend); 1064 1.218 christos fprintf(ttyout, "/" LLF "\n", (LLT)pi->entitylen); 1065 1.218 christos } 1066 1.218 christos #endif 1067 1.218 christos return 0; 1068 1.218 christos } 1069 1.218 christos 1070 1.228 nonaka #ifndef NO_AUTH 1071 1.228 nonaka static void 1072 1.228 nonaka do_auth(int hcode, const char *url, const char *penv, struct authinfo *wauth, 1073 1.228 nonaka struct authinfo *pauth, char **auth, const char *message, 1074 1.235 christos volatile int *rval, struct urlinfo *ui) 1075 1.228 nonaka { 1076 1.228 nonaka struct authinfo aauth; 1077 1.228 nonaka char *response; 1078 1.228 nonaka 1079 1.228 nonaka if (hcode == 401) 1080 1.228 nonaka aauth = *wauth; 1081 1.228 nonaka else 1082 1.228 nonaka aauth = *pauth; 1083 1.228 nonaka 1084 1.228 nonaka if (verbose || aauth.auth == NULL || 1085 1.228 nonaka aauth.user == NULL || aauth.pass == NULL) 1086 1.228 nonaka fprintf(ttyout, "%s\n", message); 1087 1.228 nonaka if (EMPTYSTRING(*auth)) { 1088 1.228 nonaka warnx("No authentication challenge provided by server"); 1089 1.228 nonaka return; 1090 1.228 nonaka } 1091 1.228 nonaka 1092 1.228 nonaka if (aauth.auth != NULL) { 1093 1.228 nonaka char reply[10]; 1094 1.228 nonaka 1095 1.228 nonaka fprintf(ttyout, "Authorization failed. Retry (y/n)? "); 1096 1.228 nonaka if (get_line(stdin, reply, sizeof(reply), NULL) < 0) { 1097 1.228 nonaka return; 1098 1.228 nonaka } 1099 1.228 nonaka if (tolower((unsigned char)reply[0]) != 'y') 1100 1.228 nonaka return; 1101 1.228 nonaka 1102 1.228 nonaka aauth.user = NULL; 1103 1.228 nonaka aauth.pass = NULL; 1104 1.228 nonaka } 1105 1.228 nonaka 1106 1.228 nonaka if (auth_url(*auth, &response, &aauth) == 0) { 1107 1.228 nonaka *rval = fetch_url(url, penv, 1108 1.228 nonaka hcode == 401 ? pauth->auth : response, 1109 1.235 christos hcode == 401 ? response : wauth->auth, 1110 1.235 christos ui); 1111 1.228 nonaka memset(response, 0, strlen(response)); 1112 1.228 nonaka FREEPTR(response); 1113 1.228 nonaka } 1114 1.228 nonaka } 1115 1.228 nonaka #endif 1116 1.228 nonaka 1117 1.218 christos static int 1118 1.215 christos negotiate_connection(FETCH *fin, const char *url, const char *penv, 1119 1.218 christos struct posinfo *pi, time_t *mtime, struct authinfo *wauth, 1120 1.222 christos struct authinfo *pauth, volatile int *rval, volatile int *ischunked, 1121 1.235 christos char **auth, struct urlinfo *ui) 1122 1.215 christos { 1123 1.215 christos int len, hcode, rv; 1124 1.239 christos char *buf = NULL, *ep; 1125 1.215 christos const char *cp, *token; 1126 1.235 christos char *location, *message; 1127 1.215 christos 1128 1.215 christos *auth = message = location = NULL; 1129 1.215 christos 1130 1.239 christos buf = ftp_malloc(ftp_buflen); 1131 1.239 christos 1132 1.215 christos /* Read the response */ 1133 1.215 christos ep = buf; 1134 1.239 christos switch (getresponse(fin, &ep, ftp_buflen, &hcode)) { 1135 1.215 christos case C_CLEANUP: 1136 1.215 christos goto cleanup_fetch_url; 1137 1.215 christos case C_IMPROPER: 1138 1.214 christos goto improper; 1139 1.215 christos case C_OK: 1140 1.215 christos message = ftp_strdup(ep); 1141 1.215 christos break; 1142 1.215 christos } 1143 1.214 christos 1144 1.214 christos /* Read the rest of the header. */ 1145 1.214 christos 1146 1.214 christos for (;;) { 1147 1.239 christos if ((rv = getresponseline(fin, buf, ftp_buflen, &len)) != C_OK) 1148 1.214 christos goto cleanup_fetch_url; 1149 1.214 christos if (len == 0) 1150 1.214 christos break; 1151 1.214 christos 1152 1.214 christos /* 1153 1.214 christos * Look for some headers 1154 1.214 christos */ 1155 1.214 christos 1156 1.214 christos cp = buf; 1157 1.214 christos 1158 1.214 christos if (match_token(&cp, "Content-Length:")) { 1159 1.214 christos filesize = STRTOLL(cp, &ep, 10); 1160 1.214 christos if (filesize < 0 || *ep != '\0') 1161 1.214 christos goto improper; 1162 1.214 christos DPRINTF("%s: parsed len as: " LLF "\n", 1163 1.214 christos __func__, (LLT)filesize); 1164 1.214 christos 1165 1.214 christos } else if (match_token(&cp, "Content-Range:")) { 1166 1.218 christos if (parse_posinfo(&cp, pi) == -1) 1167 1.214 christos goto improper; 1168 1.214 christos if (! restart_point) { 1169 1.214 christos warnx( 1170 1.214 christos "Received unexpected Content-Range header"); 1171 1.214 christos goto cleanup_fetch_url; 1172 1.214 christos } 1173 1.214 christos 1174 1.214 christos } else if (match_token(&cp, "Last-Modified:")) { 1175 1.214 christos getmtime(cp, mtime); 1176 1.214 christos 1177 1.214 christos } else if (match_token(&cp, "Location:")) { 1178 1.214 christos location = ftp_strdup(cp); 1179 1.214 christos DPRINTF("%s: parsed location as `%s'\n", 1180 1.214 christos __func__, cp); 1181 1.214 christos 1182 1.214 christos } else if (match_token(&cp, "Transfer-Encoding:")) { 1183 1.214 christos if (match_token(&cp, "binary")) { 1184 1.214 christos warnx( 1185 1.214 christos "Bogus transfer encoding `binary' (fetching anyway)"); 1186 1.214 christos continue; 1187 1.214 christos } 1188 1.214 christos if (! (token = match_token(&cp, "chunked"))) { 1189 1.214 christos warnx( 1190 1.214 christos "Unsupported transfer encoding `%s'", 1191 1.214 christos token); 1192 1.214 christos goto cleanup_fetch_url; 1193 1.214 christos } 1194 1.214 christos (*ischunked)++; 1195 1.214 christos DPRINTF("%s: using chunked encoding\n", 1196 1.214 christos __func__); 1197 1.214 christos 1198 1.214 christos } else if (match_token(&cp, "Proxy-Authenticate:") 1199 1.214 christos || match_token(&cp, "WWW-Authenticate:")) { 1200 1.214 christos if (! (token = match_token(&cp, "Basic"))) { 1201 1.214 christos DPRINTF("%s: skipping unknown auth " 1202 1.214 christos "scheme `%s'\n", __func__, token); 1203 1.214 christos continue; 1204 1.214 christos } 1205 1.215 christos FREEPTR(*auth); 1206 1.215 christos *auth = ftp_strdup(token); 1207 1.214 christos DPRINTF("%s: parsed auth as `%s'\n", 1208 1.214 christos __func__, cp); 1209 1.214 christos } 1210 1.214 christos 1211 1.214 christos } 1212 1.214 christos /* finished parsing header */ 1213 1.214 christos 1214 1.214 christos switch (hcode) { 1215 1.214 christos case 200: 1216 1.214 christos break; 1217 1.214 christos case 206: 1218 1.214 christos if (! restart_point) { 1219 1.214 christos warnx("Not expecting partial content header"); 1220 1.214 christos goto cleanup_fetch_url; 1221 1.214 christos } 1222 1.214 christos break; 1223 1.214 christos case 300: 1224 1.214 christos case 301: 1225 1.214 christos case 302: 1226 1.214 christos case 303: 1227 1.214 christos case 305: 1228 1.214 christos case 307: 1229 1.214 christos if (EMPTYSTRING(location)) { 1230 1.214 christos warnx( 1231 1.214 christos "No redirection Location provided by server"); 1232 1.214 christos goto cleanup_fetch_url; 1233 1.214 christos } 1234 1.214 christos if (redirect_loop++ > 5) { 1235 1.214 christos warnx("Too many redirections requested"); 1236 1.214 christos goto cleanup_fetch_url; 1237 1.214 christos } 1238 1.214 christos if (hcode == 305) { 1239 1.214 christos if (verbose) 1240 1.214 christos fprintf(ttyout, "Redirected via %s\n", 1241 1.214 christos location); 1242 1.214 christos *rval = fetch_url(url, location, 1243 1.235 christos pauth->auth, wauth->auth, ui); 1244 1.214 christos } else { 1245 1.214 christos if (verbose) 1246 1.214 christos fprintf(ttyout, "Redirected to %s\n", 1247 1.214 christos location); 1248 1.235 christos *rval = go_fetch(location, ui); 1249 1.214 christos } 1250 1.214 christos goto cleanup_fetch_url; 1251 1.214 christos #ifndef NO_AUTH 1252 1.214 christos case 401: 1253 1.214 christos case 407: 1254 1.235 christos do_auth(hcode, url, penv, wauth, pauth, auth, message, rval, 1255 1.235 christos ui); 1256 1.214 christos goto cleanup_fetch_url; 1257 1.214 christos #endif 1258 1.214 christos default: 1259 1.214 christos if (message) 1260 1.214 christos warnx("Error retrieving file `%s'", message); 1261 1.214 christos else 1262 1.214 christos warnx("Unknown error retrieving file"); 1263 1.214 christos goto cleanup_fetch_url; 1264 1.214 christos } 1265 1.214 christos rv = C_OK; 1266 1.214 christos goto out; 1267 1.214 christos 1268 1.214 christos cleanup_fetch_url: 1269 1.214 christos rv = C_CLEANUP; 1270 1.214 christos goto out; 1271 1.214 christos improper: 1272 1.214 christos rv = C_IMPROPER; 1273 1.214 christos goto out; 1274 1.214 christos out: 1275 1.239 christos FREEPTR(buf); 1276 1.214 christos FREEPTR(message); 1277 1.214 christos FREEPTR(location); 1278 1.214 christos return rv; 1279 1.214 christos } /* end of ftp:// or http:// specific setup */ 1280 1.214 christos 1281 1.215 christos #ifdef WITH_SSL 1282 1.215 christos static int 1283 1.228 nonaka connectmethod(FETCH *fin, const char *url, const char *penv, 1284 1.228 nonaka struct urlinfo *oui, struct urlinfo *ui, struct authinfo *wauth, 1285 1.228 nonaka struct authinfo *pauth, char **auth, int *hasleading, volatile int *rval) 1286 1.215 christos { 1287 1.215 christos void *ssl; 1288 1.215 christos int hcode, rv; 1289 1.215 christos const char *cp; 1290 1.239 christos char *buf = NULL, *ep; 1291 1.215 christos char *message = NULL; 1292 1.215 christos 1293 1.217 christos print_connect(fin, oui); 1294 1.215 christos 1295 1.215 christos print_agent(fin); 1296 1.216 nonaka *hasleading = print_proxy(fin, *hasleading, NULL, pauth->auth); 1297 1.215 christos 1298 1.215 christos if (verbose && *hasleading) 1299 1.215 christos fputs(")\n", ttyout); 1300 1.215 christos *hasleading = 0; 1301 1.215 christos 1302 1.215 christos fetch_printf(fin, "\r\n"); 1303 1.215 christos if (fetch_flush(fin) == EOF) { 1304 1.215 christos warn("Writing HTTP request"); 1305 1.215 christos alarmtimer(0); 1306 1.215 christos goto cleanup_fetch_url; 1307 1.215 christos } 1308 1.215 christos alarmtimer(0); 1309 1.215 christos 1310 1.239 christos buf = ftp_malloc(ftp_buflen); 1311 1.239 christos 1312 1.215 christos /* Read the response */ 1313 1.215 christos ep = buf; 1314 1.239 christos switch (getresponse(fin, &ep, ftp_buflen, &hcode)) { 1315 1.215 christos case C_CLEANUP: 1316 1.215 christos goto cleanup_fetch_url; 1317 1.215 christos case C_IMPROPER: 1318 1.215 christos goto improper; 1319 1.215 christos case C_OK: 1320 1.215 christos message = ftp_strdup(ep); 1321 1.215 christos break; 1322 1.215 christos } 1323 1.235 christos 1324 1.215 christos for (;;) { 1325 1.215 christos int len; 1326 1.239 christos if (getresponseline(fin, buf, ftp_buflen, &len) != C_OK) 1327 1.215 christos goto cleanup_fetch_url; 1328 1.215 christos if (len == 0) 1329 1.215 christos break; 1330 1.221 nonaka 1331 1.221 nonaka cp = buf; 1332 1.215 christos if (match_token(&cp, "Proxy-Authenticate:")) { 1333 1.215 christos const char *token; 1334 1.215 christos if (!(token = match_token(&cp, "Basic"))) { 1335 1.215 christos DPRINTF( 1336 1.215 christos "%s: skipping unknown auth scheme `%s'\n", 1337 1.215 christos __func__, token); 1338 1.215 christos continue; 1339 1.215 christos } 1340 1.215 christos FREEPTR(*auth); 1341 1.215 christos *auth = ftp_strdup(token); 1342 1.215 christos DPRINTF("%s: parsed auth as " "`%s'\n", __func__, cp); 1343 1.215 christos } 1344 1.215 christos } 1345 1.215 christos 1346 1.215 christos /* finished parsing header */ 1347 1.215 christos switch (hcode) { 1348 1.215 christos case 200: 1349 1.215 christos break; 1350 1.226 nonaka #ifndef NO_AUTH 1351 1.226 nonaka case 407: 1352 1.235 christos do_auth(hcode, url, penv, wauth, pauth, auth, message, rval, 1353 1.235 christos ui); 1354 1.226 nonaka goto cleanup_fetch_url; 1355 1.226 nonaka #endif 1356 1.215 christos default: 1357 1.215 christos if (message) 1358 1.215 christos warnx("Error proxy connect " "`%s'", message); 1359 1.215 christos else 1360 1.215 christos warnx("Unknown error proxy " "connect"); 1361 1.215 christos goto cleanup_fetch_url; 1362 1.215 christos } 1363 1.215 christos 1364 1.228 nonaka if ((ssl = fetch_start_ssl(fetch_fileno(fin), oui->host)) == NULL) 1365 1.215 christos goto cleanup_fetch_url; 1366 1.215 christos fetch_set_ssl(fin, ssl); 1367 1.215 christos 1368 1.215 christos rv = C_OK; 1369 1.215 christos goto out; 1370 1.215 christos improper: 1371 1.215 christos rv = C_IMPROPER; 1372 1.215 christos goto out; 1373 1.215 christos cleanup_fetch_url: 1374 1.215 christos rv = C_CLEANUP; 1375 1.215 christos goto out; 1376 1.215 christos out: 1377 1.239 christos FREEPTR(buf); 1378 1.215 christos FREEPTR(message); 1379 1.215 christos return rv; 1380 1.215 christos } 1381 1.215 christos #endif 1382 1.214 christos 1383 1.1 lukem /* 1384 1.50 lukem * Retrieve URL, via a proxy if necessary, using HTTP. 1385 1.50 lukem * If proxyenv is set, use that for the proxy, otherwise try ftp_proxy or 1386 1.200 christos * http_proxy/https_proxy as appropriate. 1387 1.50 lukem * Supports HTTP redirects. 1388 1.127 tacha * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection 1389 1.41 lukem * is still open (e.g, ftp xfer with trailing /) 1390 1.1 lukem */ 1391 1.12 lukem static int 1392 1.235 christos fetch_url(const char *url, const char *proxyenv, char *proxyauth, 1393 1.235 christos char *wwwauth, struct urlinfo *rui) 1394 1.1 lukem { 1395 1.203 christos sigfunc volatile oldint; 1396 1.203 christos sigfunc volatile oldpipe; 1397 1.203 christos sigfunc volatile oldalrm; 1398 1.203 christos sigfunc volatile oldquit; 1399 1.173 christos int volatile s; 1400 1.95 lukem struct stat sb; 1401 1.173 christos int volatile isproxy; 1402 1.235 christos int volatile rval, ischunked; 1403 1.187 lukem size_t flen; 1404 1.73 lukem static size_t bufsize; 1405 1.73 lukem static char *xferbuf; 1406 1.214 christos const char *cp; 1407 1.173 christos char *ep; 1408 1.230 christos char *volatile auth; 1409 1.173 christos char *volatile savefile; 1410 1.173 christos char *volatile location; 1411 1.173 christos char *volatile message; 1412 1.173 christos char *volatile decodedpath; 1413 1.235 christos struct authinfo wauth, pauth; 1414 1.218 christos struct posinfo pi; 1415 1.218 christos off_t hashbytes; 1416 1.173 christos int (*volatile closefunc)(FILE *); 1417 1.199 christos FETCH *volatile fin; 1418 1.173 christos FILE *volatile fout; 1419 1.203 christos const char *volatile penv = proxyenv; 1420 1.216 nonaka struct urlinfo ui, oui; 1421 1.42 lukem time_t mtime; 1422 1.210 christos void *ssl = NULL; 1423 1.1 lukem 1424 1.203 christos DPRINTF("%s: `%s' proxyenv `%s'\n", __func__, url, STRorNULL(penv)); 1425 1.183 lukem 1426 1.232 lukem oldquit = oldalrm = oldint = oldpipe = SIG_ERR; 1427 1.22 lukem closefunc = NULL; 1428 1.199 christos fin = NULL; 1429 1.199 christos fout = NULL; 1430 1.1 lukem s = -1; 1431 1.176 lukem savefile = NULL; 1432 1.44 lukem auth = location = message = NULL; 1433 1.214 christos ischunked = isproxy = 0; 1434 1.42 lukem rval = 1; 1435 1.214 christos 1436 1.214 christos initurlinfo(&ui); 1437 1.225 christos initurlinfo(&oui); 1438 1.214 christos initauthinfo(&wauth, wwwauth); 1439 1.214 christos initauthinfo(&pauth, proxyauth); 1440 1.214 christos 1441 1.215 christos decodedpath = NULL; 1442 1.1 lukem 1443 1.203 christos if (sigsetjmp(httpabort, 1)) 1444 1.203 christos goto cleanup_fetch_url; 1445 1.203 christos 1446 1.235 christos if (parse_url(url, "URL", &ui, &wauth, rui) == -1) 1447 1.42 lukem goto cleanup_fetch_url; 1448 1.5 lukem 1449 1.216 nonaka copyurlinfo(&oui, &ui); 1450 1.215 christos 1451 1.214 christos if (ui.utype == FILE_URL_T && ! EMPTYSTRING(ui.host) 1452 1.214 christos && strcasecmp(ui.host, "localhost") != 0) { 1453 1.27 lukem warnx("No support for non local file URL `%s'", url); 1454 1.42 lukem goto cleanup_fetch_url; 1455 1.9 lukem } 1456 1.27 lukem 1457 1.214 christos if (EMPTYSTRING(ui.path)) { 1458 1.214 christos if (ui.utype == FTP_URL_T) { 1459 1.52 lukem rval = fetch_ftp(url); 1460 1.42 lukem goto cleanup_fetch_url; 1461 1.42 lukem } 1462 1.235 christos if (!IS_HTTP_TYPE(ui.utype) || outfile == NULL) { 1463 1.27 lukem warnx("Invalid URL (no file after host) `%s'", url); 1464 1.42 lukem goto cleanup_fetch_url; 1465 1.27 lukem } 1466 1.9 lukem } 1467 1.1 lukem 1468 1.214 christos decodedpath = ftp_strdup(ui.path); 1469 1.50 lukem url_decode(decodedpath); 1470 1.50 lukem 1471 1.22 lukem if (outfile) 1472 1.206 christos savefile = outfile; 1473 1.22 lukem else { 1474 1.50 lukem cp = strrchr(decodedpath, '/'); /* find savefile */ 1475 1.29 lukem if (cp != NULL) 1476 1.166 christos savefile = ftp_strdup(cp + 1); 1477 1.22 lukem else 1478 1.166 christos savefile = ftp_strdup(decodedpath); 1479 1.227 christos /* 1480 1.227 christos * Use the first URL we requested not the name after a 1481 1.227 christos * possible redirect, but careful to save it because our 1482 1.227 christos * "safety" check is the match to outfile. 1483 1.227 christos */ 1484 1.227 christos outfile = ftp_strdup(savefile); 1485 1.22 lukem } 1486 1.203 christos DPRINTF("%s: savefile `%s'\n", __func__, savefile); 1487 1.9 lukem if (EMPTYSTRING(savefile)) { 1488 1.214 christos if (ui.utype == FTP_URL_T) { 1489 1.52 lukem rval = fetch_ftp(url); 1490 1.42 lukem goto cleanup_fetch_url; 1491 1.42 lukem } 1492 1.175 lukem warnx("No file after directory (you must specify an " 1493 1.140 grant "output file) `%s'", url); 1494 1.42 lukem goto cleanup_fetch_url; 1495 1.9 lukem } 1496 1.1 lukem 1497 1.95 lukem restart_point = 0; 1498 1.25 lukem filesize = -1; 1499 1.218 christos initposinfo(&pi); 1500 1.25 lukem mtime = -1; 1501 1.95 lukem if (restartautofetch) { 1502 1.206 christos if (stat(savefile, &sb) == 0) 1503 1.95 lukem restart_point = sb.st_size; 1504 1.95 lukem } 1505 1.214 christos if (ui.utype == FILE_URL_T) { /* file:// URLs */ 1506 1.25 lukem direction = "copied"; 1507 1.199 christos fin = fetch_open(decodedpath, "r"); 1508 1.25 lukem if (fin == NULL) { 1509 1.175 lukem warn("Can't open `%s'", decodedpath); 1510 1.42 lukem goto cleanup_fetch_url; 1511 1.5 lukem } 1512 1.199 christos if (fstat(fetch_fileno(fin), &sb) == 0) { 1513 1.25 lukem mtime = sb.st_mtime; 1514 1.25 lukem filesize = sb.st_size; 1515 1.25 lukem } 1516 1.95 lukem if (restart_point) { 1517 1.235 christos if (lseek(fetch_fileno(fin), restart_point, SEEK_SET) 1518 1.235 christos < 0) { 1519 1.175 lukem warn("Can't seek to restart `%s'", 1520 1.95 lukem decodedpath); 1521 1.95 lukem goto cleanup_fetch_url; 1522 1.95 lukem } 1523 1.95 lukem } 1524 1.95 lukem if (verbose) { 1525 1.95 lukem fprintf(ttyout, "Copying %s", decodedpath); 1526 1.95 lukem if (restart_point) 1527 1.121 lukem fprintf(ttyout, " (restarting at " LLF ")", 1528 1.121 lukem (LLT)restart_point); 1529 1.95 lukem fputs("\n", ttyout); 1530 1.95 lukem } 1531 1.199 christos if (0 == rcvbuf_size) { 1532 1.199 christos rcvbuf_size = 8 * 1024; /* XXX */ 1533 1.199 christos } 1534 1.25 lukem } else { /* ftp:// or http:// URLs */ 1535 1.65 lukem int hasleading; 1536 1.236 mlelstv static char hostnamebuf[MAXHOSTNAMELEN]; 1537 1.236 mlelstv 1538 1.236 mlelstv (void)strlcpy(hostnamebuf, ui.host, sizeof(hostnamebuf)); 1539 1.236 mlelstv hostname = hostnamebuf; 1540 1.65 lukem 1541 1.203 christos if (penv == NULL) { 1542 1.201 christos #ifdef WITH_SSL 1543 1.214 christos if (ui.utype == HTTPS_URL_T) 1544 1.203 christos penv = getoptionvalue("https_proxy"); 1545 1.201 christos #endif 1546 1.214 christos if (penv == NULL && IS_HTTP_TYPE(ui.utype)) 1547 1.203 christos penv = getoptionvalue("http_proxy"); 1548 1.214 christos else if (ui.utype == FTP_URL_T) 1549 1.203 christos penv = getoptionvalue("ftp_proxy"); 1550 1.42 lukem } 1551 1.25 lukem direction = "retrieved"; 1552 1.203 christos if (! EMPTYSTRING(penv)) { /* use proxy */ 1553 1.27 lukem 1554 1.214 christos isproxy = handle_noproxy(ui.host, ui.portnum); 1555 1.27 lukem 1556 1.214 christos if (isproxy == 0 && ui.utype == FTP_URL_T) { 1557 1.211 christos rval = fetch_ftp(url); 1558 1.211 christos goto cleanup_fetch_url; 1559 1.25 lukem } 1560 1.1 lukem 1561 1.27 lukem if (isproxy) { 1562 1.180 lukem if (restart_point) { 1563 1.212 christos warnx( 1564 1.212 christos "Can't restart via proxy URL `%s'", 1565 1.203 christos penv); 1566 1.180 lukem goto cleanup_fetch_url; 1567 1.180 lukem } 1568 1.214 christos if (handle_proxy(url, penv, &ui, &pauth) < 0) 1569 1.112 itojun goto cleanup_fetch_url; 1570 1.27 lukem } 1571 1.203 christos } /* ! EMPTYSTRING(penv) */ 1572 1.25 lukem 1573 1.236 mlelstv s = ftp_socket(&ui, &ssl, &wauth); 1574 1.98 itojun if (s < 0) { 1575 1.214 christos warnx("Can't connect to `%s:%s'", ui.host, ui.port); 1576 1.98 itojun goto cleanup_fetch_url; 1577 1.98 itojun } 1578 1.24 lukem 1579 1.203 christos oldalrm = xsignal(SIGALRM, timeouthttp); 1580 1.203 christos alarmtimer(quit_time ? quit_time : 60); 1581 1.199 christos fin = fetch_fdopen(s, "r+"); 1582 1.199 christos fetch_set_ssl(fin, ssl); 1583 1.203 christos alarmtimer(0); 1584 1.199 christos 1585 1.203 christos alarmtimer(quit_time ? quit_time : 60); 1586 1.25 lukem /* 1587 1.27 lukem * Construct and send the request. 1588 1.25 lukem */ 1589 1.65 lukem if (verbose) 1590 1.65 lukem fprintf(ttyout, "Requesting %s\n", url); 1591 1.112 itojun 1592 1.215 christos hasleading = 0; 1593 1.215 christos #ifdef WITH_SSL 1594 1.215 christos if (isproxy && oui.utype == HTTPS_URL_T) { 1595 1.228 nonaka switch (connectmethod(fin, url, penv, &oui, &ui, 1596 1.230 christos &wauth, &pauth, __UNVOLATILE(&auth), &hasleading, 1597 1.230 christos &rval)) { 1598 1.215 christos case C_CLEANUP: 1599 1.215 christos goto cleanup_fetch_url; 1600 1.215 christos case C_IMPROPER: 1601 1.215 christos goto improper; 1602 1.215 christos case C_OK: 1603 1.215 christos break; 1604 1.215 christos default: 1605 1.215 christos abort(); 1606 1.215 christos } 1607 1.215 christos } 1608 1.215 christos #endif 1609 1.215 christos 1610 1.216 nonaka hasleading = print_get(fin, hasleading, isproxy, &oui, &ui); 1611 1.214 christos 1612 1.214 christos if (flushcache) 1613 1.214 christos print_cache(fin, isproxy); 1614 1.214 christos 1615 1.214 christos print_agent(fin); 1616 1.216 nonaka hasleading = print_proxy(fin, hasleading, wauth.auth, 1617 1.215 christos auth ? NULL : pauth.auth); 1618 1.214 christos if (hasleading) { 1619 1.216 nonaka hasleading = 0; 1620 1.214 christos if (verbose) 1621 1.214 christos fputs(")\n", ttyout); 1622 1.25 lukem } 1623 1.214 christos 1624 1.199 christos fetch_printf(fin, "\r\n"); 1625 1.199 christos if (fetch_flush(fin) == EOF) { 1626 1.25 lukem warn("Writing HTTP request"); 1627 1.203 christos alarmtimer(0); 1628 1.42 lukem goto cleanup_fetch_url; 1629 1.25 lukem } 1630 1.203 christos alarmtimer(0); 1631 1.1 lukem 1632 1.218 christos switch (negotiate_connection(fin, url, penv, &pi, 1633 1.230 christos &mtime, &wauth, &pauth, &rval, &ischunked, 1634 1.235 christos __UNVOLATILE(&auth), &ui)) { 1635 1.214 christos case C_OK: 1636 1.214 christos break; 1637 1.214 christos case C_CLEANUP: 1638 1.42 lukem goto cleanup_fetch_url; 1639 1.214 christos case C_IMPROPER: 1640 1.42 lukem goto improper; 1641 1.52 lukem default: 1642 1.214 christos abort(); 1643 1.44 lukem } 1644 1.214 christos } 1645 1.42 lukem 1646 1.206 christos /* Open the output file. */ 1647 1.206 christos 1648 1.206 christos /* 1649 1.206 christos * Only trust filenames with special meaning if they came from 1650 1.206 christos * the command line 1651 1.206 christos */ 1652 1.206 christos if (outfile == savefile) { 1653 1.206 christos if (strcmp(savefile, "-") == 0) { 1654 1.206 christos fout = stdout; 1655 1.206 christos } else if (*savefile == '|') { 1656 1.206 christos oldpipe = xsignal(SIGPIPE, SIG_IGN); 1657 1.206 christos fout = popen(savefile + 1, "w"); 1658 1.206 christos if (fout == NULL) { 1659 1.206 christos warn("Can't execute `%s'", savefile + 1); 1660 1.206 christos goto cleanup_fetch_url; 1661 1.206 christos } 1662 1.206 christos closefunc = pclose; 1663 1.22 lukem } 1664 1.206 christos } 1665 1.206 christos if (fout == NULL) { 1666 1.238 mlelstv if (restart_point && ( 1667 1.238 mlelstv (pi.rangeend != -1 && pi.rangeend <= restart_point) || 1668 1.218 christos (pi.rangestart == -1 && 1669 1.238 mlelstv filesize != -1 && filesize <= restart_point))) { 1670 1.129 yamt /* already done */ 1671 1.129 yamt if (verbose) 1672 1.129 yamt fprintf(ttyout, "already done\n"); 1673 1.129 yamt rval = 0; 1674 1.129 yamt goto cleanup_fetch_url; 1675 1.129 yamt } 1676 1.218 christos if (restart_point && pi.rangestart != -1) { 1677 1.218 christos if (pi.entitylen != -1) 1678 1.218 christos filesize = pi.entitylen; 1679 1.218 christos if (pi.rangestart != restart_point) { 1680 1.95 lukem warnx( 1681 1.95 lukem "Size of `%s' differs from save file `%s'", 1682 1.95 lukem url, savefile); 1683 1.95 lukem goto cleanup_fetch_url; 1684 1.95 lukem } 1685 1.95 lukem fout = fopen(savefile, "a"); 1686 1.95 lukem } else 1687 1.95 lukem fout = fopen(savefile, "w"); 1688 1.22 lukem if (fout == NULL) { 1689 1.27 lukem warn("Can't open `%s'", savefile); 1690 1.42 lukem goto cleanup_fetch_url; 1691 1.22 lukem } 1692 1.22 lukem closefunc = fclose; 1693 1.1 lukem } 1694 1.1 lukem 1695 1.22 lukem /* Trap signals */ 1696 1.203 christos oldquit = xsignal(SIGQUIT, psummary); 1697 1.203 christos oldint = xsignal(SIGINT, aborthttp); 1698 1.2 lukem 1699 1.195 lukem assert(rcvbuf_size > 0); 1700 1.187 lukem if ((size_t)rcvbuf_size > bufsize) { 1701 1.73 lukem if (xferbuf) 1702 1.73 lukem (void)free(xferbuf); 1703 1.73 lukem bufsize = rcvbuf_size; 1704 1.166 christos xferbuf = ftp_malloc(bufsize); 1705 1.73 lukem } 1706 1.73 lukem 1707 1.1 lukem bytes = 0; 1708 1.1 lukem hashbytes = mark; 1709 1.232 lukem if (oldalrm != SIG_ERR) { 1710 1.204 christos (void)xsignal(SIGALRM, oldalrm); 1711 1.232 lukem oldalrm = SIG_ERR; 1712 1.204 christos } 1713 1.1 lukem progressmeter(-1); 1714 1.2 lukem 1715 1.24 lukem /* Finally, suck down the file. */ 1716 1.45 lukem do { 1717 1.77 lukem long chunksize; 1718 1.181 lukem short lastchunk; 1719 1.45 lukem 1720 1.45 lukem chunksize = 0; 1721 1.181 lukem lastchunk = 0; 1722 1.181 lukem /* read chunk-size */ 1723 1.45 lukem if (ischunked) { 1724 1.240 christos if (fetch_getln(xferbuf, (int)bufsize, fin) == NULL) { 1725 1.181 lukem warnx("Unexpected EOF reading chunk-size"); 1726 1.45 lukem goto cleanup_fetch_url; 1727 1.45 lukem } 1728 1.181 lukem errno = 0; 1729 1.73 lukem chunksize = strtol(xferbuf, &ep, 16); 1730 1.181 lukem if (ep == xferbuf) { 1731 1.181 lukem warnx("Invalid chunk-size"); 1732 1.181 lukem goto cleanup_fetch_url; 1733 1.181 lukem } 1734 1.181 lukem if (errno == ERANGE || chunksize < 0) { 1735 1.181 lukem errno = ERANGE; 1736 1.181 lukem warn("Chunk-size `%.*s'", 1737 1.182 lukem (int)(ep-xferbuf), xferbuf); 1738 1.181 lukem goto cleanup_fetch_url; 1739 1.181 lukem } 1740 1.103 lukem 1741 1.103 lukem /* 1742 1.235 christos * XXX: Work around bug in Apache 1.3.9 and 1743 1.123 lukem * 1.3.11, which incorrectly put trailing 1744 1.181 lukem * space after the chunk-size. 1745 1.103 lukem */ 1746 1.123 lukem while (*ep == ' ') 1747 1.103 lukem ep++; 1748 1.103 lukem 1749 1.181 lukem /* skip [ chunk-ext ] */ 1750 1.181 lukem if (*ep == ';') { 1751 1.181 lukem while (*ep && *ep != '\r') 1752 1.181 lukem ep++; 1753 1.181 lukem } 1754 1.181 lukem 1755 1.45 lukem if (strcmp(ep, "\r\n") != 0) { 1756 1.181 lukem warnx("Unexpected data following chunk-size"); 1757 1.45 lukem goto cleanup_fetch_url; 1758 1.45 lukem } 1759 1.203 christos DPRINTF("%s: got chunk-size of " LLF "\n", __func__, 1760 1.183 lukem (LLT)chunksize); 1761 1.181 lukem if (chunksize == 0) { 1762 1.181 lukem lastchunk = 1; 1763 1.181 lukem goto chunkdone; 1764 1.181 lukem } 1765 1.45 lukem } 1766 1.59 lukem /* transfer file or chunk */ 1767 1.240 christos for (;;) { 1768 1.59 lukem struct timeval then, now, td; 1769 1.205 christos volatile off_t bufrem; 1770 1.59 lukem 1771 1.71 lukem if (rate_get) 1772 1.71 lukem (void)gettimeofday(&then, NULL); 1773 1.187 lukem bufrem = rate_get ? rate_get : (off_t)bufsize; 1774 1.101 lukem if (ischunked) 1775 1.101 lukem bufrem = MIN(chunksize, bufrem); 1776 1.59 lukem while (bufrem > 0) { 1777 1.231 christos size_t nr = MIN((off_t)bufsize, bufrem); 1778 1.199 christos flen = fetch_read(xferbuf, sizeof(char), 1779 1.231 christos nr, fin); 1780 1.231 christos if (flen == 0) { 1781 1.231 christos if (fetch_error(fin)) 1782 1.231 christos goto chunkerror; 1783 1.59 lukem goto chunkdone; 1784 1.231 christos } 1785 1.187 lukem bytes += flen; 1786 1.187 lukem bufrem -= flen; 1787 1.233 christos if (maxwrite(xferbuf, sizeof(char), flen, fout) 1788 1.187 lukem != flen) { 1789 1.59 lukem warn("Writing `%s'", savefile); 1790 1.59 lukem goto cleanup_fetch_url; 1791 1.59 lukem } 1792 1.101 lukem if (hash && !progress) { 1793 1.101 lukem while (bytes >= hashbytes) { 1794 1.101 lukem (void)putc('#', ttyout); 1795 1.101 lukem hashbytes += mark; 1796 1.101 lukem } 1797 1.101 lukem (void)fflush(ttyout); 1798 1.101 lukem } 1799 1.101 lukem if (ischunked) { 1800 1.187 lukem chunksize -= flen; 1801 1.101 lukem if (chunksize <= 0) 1802 1.101 lukem break; 1803 1.45 lukem } 1804 1.59 lukem } 1805 1.59 lukem if (rate_get) { 1806 1.240 christos for (;;) { 1807 1.59 lukem (void)gettimeofday(&now, NULL); 1808 1.59 lukem timersub(&now, &then, &td); 1809 1.59 lukem if (td.tv_sec > 0) 1810 1.59 lukem break; 1811 1.59 lukem usleep(1000000 - td.tv_usec); 1812 1.59 lukem } 1813 1.59 lukem } 1814 1.101 lukem if (ischunked && chunksize <= 0) 1815 1.101 lukem break; 1816 1.1 lukem } 1817 1.45 lukem /* read CRLF after chunk*/ 1818 1.59 lukem chunkdone: 1819 1.45 lukem if (ischunked) { 1820 1.240 christos if (fetch_getln(xferbuf, (int)bufsize, fin) == NULL) { 1821 1.203 christos alarmtimer(0); 1822 1.181 lukem warnx("Unexpected EOF reading chunk CRLF"); 1823 1.181 lukem goto cleanup_fetch_url; 1824 1.181 lukem } 1825 1.73 lukem if (strcmp(xferbuf, "\r\n") != 0) { 1826 1.45 lukem warnx("Unexpected data following chunk"); 1827 1.45 lukem goto cleanup_fetch_url; 1828 1.1 lukem } 1829 1.181 lukem if (lastchunk) 1830 1.181 lukem break; 1831 1.1 lukem } 1832 1.45 lukem } while (ischunked); 1833 1.181 lukem 1834 1.181 lukem /* XXX: deal with optional trailer & CRLF here? */ 1835 1.231 christos chunkerror: 1836 1.1 lukem if (hash && !progress && bytes > 0) { 1837 1.1 lukem if (bytes < mark) 1838 1.22 lukem (void)putc('#', ttyout); 1839 1.22 lukem (void)putc('\n', ttyout); 1840 1.1 lukem } 1841 1.199 christos if (fetch_error(fin)) { 1842 1.25 lukem warn("Reading file"); 1843 1.42 lukem goto cleanup_fetch_url; 1844 1.1 lukem } 1845 1.2 lukem progressmeter(1); 1846 1.25 lukem (void)fflush(fout); 1847 1.25 lukem if (closefunc == fclose && mtime != -1) { 1848 1.25 lukem struct timeval tval[2]; 1849 1.25 lukem 1850 1.25 lukem (void)gettimeofday(&tval[0], NULL); 1851 1.25 lukem tval[1].tv_sec = mtime; 1852 1.25 lukem tval[1].tv_usec = 0; 1853 1.31 lukem (*closefunc)(fout); 1854 1.31 lukem fout = NULL; 1855 1.31 lukem 1856 1.31 lukem if (utimes(savefile, tval) == -1) { 1857 1.25 lukem fprintf(ttyout, 1858 1.25 lukem "Can't change modification time to %s", 1859 1.179 lukem rfc2822time(localtime(&mtime))); 1860 1.25 lukem } 1861 1.25 lukem } 1862 1.25 lukem if (bytes > 0) 1863 1.25 lukem ptransfer(0); 1864 1.242 lukem 1865 1.242 lukem /* fail if short transfer when filesize is known */ 1866 1.242 lukem if (filesize >= 0 && (bytes + restart_point < filesize)) 1867 1.242 lukem goto cleanup_fetch_url; 1868 1.242 lukem 1869 1.136 lukem bytes = 0; 1870 1.1 lukem 1871 1.42 lukem rval = 0; 1872 1.42 lukem goto cleanup_fetch_url; 1873 1.11 lukem 1874 1.118 lukem improper: 1875 1.214 christos warnx("Improper response from `%s:%s'", ui.host, ui.port); 1876 1.11 lukem 1877 1.118 lukem cleanup_fetch_url: 1878 1.232 lukem if (oldint != SIG_ERR) 1879 1.203 christos (void)xsignal(SIGINT, oldint); 1880 1.232 lukem if (oldpipe != SIG_ERR) 1881 1.203 christos (void)xsignal(SIGPIPE, oldpipe); 1882 1.232 lukem if (oldalrm != SIG_ERR) 1883 1.203 christos (void)xsignal(SIGALRM, oldalrm); 1884 1.232 lukem if (oldquit != SIG_ERR) 1885 1.232 lukem (void)xsignal(SIGQUIT, oldquit); 1886 1.24 lukem if (fin != NULL) 1887 1.199 christos fetch_close(fin); 1888 1.24 lukem else if (s != -1) 1889 1.1 lukem close(s); 1890 1.22 lukem if (closefunc != NULL && fout != NULL) 1891 1.22 lukem (*closefunc)(fout); 1892 1.206 christos if (savefile != outfile) 1893 1.206 christos FREEPTR(savefile); 1894 1.214 christos freeurlinfo(&ui); 1895 1.215 christos freeurlinfo(&oui); 1896 1.214 christos freeauthinfo(&wauth); 1897 1.214 christos freeauthinfo(&pauth); 1898 1.50 lukem FREEPTR(decodedpath); 1899 1.44 lukem FREEPTR(auth); 1900 1.42 lukem FREEPTR(location); 1901 1.42 lukem FREEPTR(message); 1902 1.42 lukem return (rval); 1903 1.1 lukem } 1904 1.1 lukem 1905 1.1 lukem /* 1906 1.50 lukem * Abort a HTTP retrieval 1907 1.2 lukem */ 1908 1.194 joerg static void 1909 1.240 christos aborthttp(int notused __unused) 1910 1.2 lukem { 1911 1.88 lukem char msgbuf[100]; 1912 1.203 christos int len; 1913 1.2 lukem 1914 1.148 lukem sigint_raised = 1; 1915 1.2 lukem alarmtimer(0); 1916 1.203 christos if (fromatty) { 1917 1.203 christos len = snprintf(msgbuf, sizeof(msgbuf), 1918 1.203 christos "\n%s: HTTP fetch aborted.\n", getprogname()); 1919 1.203 christos if (len > 0) 1920 1.203 christos write(fileno(ttyout), msgbuf, len); 1921 1.203 christos } 1922 1.203 christos siglongjmp(httpabort, 1); 1923 1.203 christos } 1924 1.203 christos 1925 1.203 christos static void 1926 1.240 christos timeouthttp(int notused __unused) 1927 1.203 christos { 1928 1.203 christos char msgbuf[100]; 1929 1.203 christos int len; 1930 1.203 christos 1931 1.203 christos alarmtimer(0); 1932 1.203 christos if (fromatty) { 1933 1.203 christos len = snprintf(msgbuf, sizeof(msgbuf), 1934 1.203 christos "\n%s: HTTP fetch timeout.\n", getprogname()); 1935 1.203 christos if (len > 0) 1936 1.203 christos write(fileno(ttyout), msgbuf, len); 1937 1.203 christos } 1938 1.88 lukem siglongjmp(httpabort, 1); 1939 1.2 lukem } 1940 1.2 lukem 1941 1.2 lukem /* 1942 1.50 lukem * Retrieve ftp URL or classic ftp argument using FTP. 1943 1.63 lukem * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection 1944 1.42 lukem * is still open (e.g, ftp xfer with trailing /) 1945 1.42 lukem */ 1946 1.42 lukem static int 1947 1.111 lukem fetch_ftp(const char *url) 1948 1.42 lukem { 1949 1.42 lukem char *cp, *xargv[5], rempath[MAXPATHLEN]; 1950 1.214 christos char *dir, *file; 1951 1.187 lukem char cmdbuf[MAXPATHLEN]; 1952 1.187 lukem char dirbuf[4]; 1953 1.186 lukem int dirhasglob, filehasglob, rval, transtype, xargc; 1954 1.160 lukem int oanonftp, oautologin; 1955 1.235 christos struct authinfo auth; 1956 1.214 christos struct urlinfo ui; 1957 1.42 lukem 1958 1.235 christos DPRINTF("%s: `%s'\n", __func__, url); 1959 1.214 christos dir = file = NULL; 1960 1.42 lukem rval = 1; 1961 1.186 lukem transtype = TYPE_I; 1962 1.42 lukem 1963 1.214 christos initurlinfo(&ui); 1964 1.214 christos initauthinfo(&auth, NULL); 1965 1.214 christos 1966 1.150 lukem if (STRNEQUAL(url, FTP_URL)) { 1967 1.235 christos if ((parse_url(url, "URL", &ui, &auth, NULL) == -1) || 1968 1.214 christos (auth.user != NULL && *auth.user == '\0') || 1969 1.214 christos EMPTYSTRING(ui.host)) { 1970 1.42 lukem warnx("Invalid URL `%s'", url); 1971 1.42 lukem goto cleanup_fetch_ftp; 1972 1.42 lukem } 1973 1.53 lukem /* 1974 1.53 lukem * Note: Don't url_decode(path) here. We need to keep the 1975 1.53 lukem * distinction between "/" and "%2F" until later. 1976 1.53 lukem */ 1977 1.53 lukem 1978 1.53 lukem /* check for trailing ';type=[aid]' */ 1979 1.235 christos if (! EMPTYSTRING(ui.path) 1980 1.235 christos && (cp = strrchr(ui.path, ';')) != NULL) { 1981 1.53 lukem if (strcasecmp(cp, ";type=a") == 0) 1982 1.186 lukem transtype = TYPE_A; 1983 1.53 lukem else if (strcasecmp(cp, ";type=i") == 0) 1984 1.186 lukem transtype = TYPE_I; 1985 1.53 lukem else if (strcasecmp(cp, ";type=d") == 0) { 1986 1.53 lukem warnx( 1987 1.53 lukem "Directory listing via a URL is not supported"); 1988 1.53 lukem goto cleanup_fetch_ftp; 1989 1.53 lukem } else { 1990 1.53 lukem warnx("Invalid suffix `%s' in URL `%s'", cp, 1991 1.53 lukem url); 1992 1.53 lukem goto cleanup_fetch_ftp; 1993 1.53 lukem } 1994 1.53 lukem *cp = 0; 1995 1.53 lukem } 1996 1.97 lukem } else { /* classic style `[user@]host:[file]' */ 1997 1.214 christos ui.utype = CLASSIC_URL_T; 1998 1.214 christos ui.host = ftp_strdup(url); 1999 1.214 christos cp = strchr(ui.host, '@'); 2000 1.97 lukem if (cp != NULL) { 2001 1.97 lukem *cp = '\0'; 2002 1.214 christos auth.user = ui.host; 2003 1.97 lukem anonftp = 0; /* disable anonftp */ 2004 1.214 christos ui.host = ftp_strdup(cp + 1); 2005 1.97 lukem } 2006 1.214 christos cp = strchr(ui.host, ':'); 2007 1.42 lukem if (cp != NULL) { 2008 1.42 lukem *cp = '\0'; 2009 1.214 christos ui.path = ftp_strdup(cp + 1); 2010 1.42 lukem } 2011 1.42 lukem } 2012 1.214 christos if (EMPTYSTRING(ui.host)) 2013 1.42 lukem goto cleanup_fetch_ftp; 2014 1.42 lukem 2015 1.50 lukem /* Extract the file and (if present) directory name. */ 2016 1.214 christos dir = ui.path; 2017 1.42 lukem if (! EMPTYSTRING(dir)) { 2018 1.53 lukem /* 2019 1.97 lukem * If we are dealing with classic `[user@]host:[path]' syntax, 2020 1.97 lukem * then a path of the form `/file' (resulting from input of the 2021 1.97 lukem * form `host:/file') means that we should do "CWD /" before 2022 1.235 christos * retrieving the file. So we set dir="/" and file="file". 2023 1.53 lukem * 2024 1.97 lukem * But if we are dealing with URLs like `ftp://host/path' then 2025 1.97 lukem * a path of the form `/file' (resulting from a URL of the form 2026 1.97 lukem * `ftp://host//file') means that we should do `CWD ' (with an 2027 1.235 christos * empty argument) before retrieving the file. So we set 2028 1.53 lukem * dir="" and file="file". 2029 1.53 lukem * 2030 1.97 lukem * If the path does not contain / at all, we set dir=NULL. 2031 1.97 lukem * (We get a path without any slashes if we are dealing with 2032 1.97 lukem * classic `[user@]host:[file]' or URL `ftp://host/file'.) 2033 1.53 lukem * 2034 1.97 lukem * In all other cases, we set dir to a string that does not 2035 1.97 lukem * include the final '/' that separates the dir part from the 2036 1.97 lukem * file part of the path. (This will be the empty string if 2037 1.97 lukem * and only if we are dealing with a path of the form `/file' 2038 1.97 lukem * resulting from an URL of the form `ftp://host//file'.) 2039 1.53 lukem */ 2040 1.42 lukem cp = strrchr(dir, '/'); 2041 1.214 christos if (cp == dir && ui.utype == CLASSIC_URL_T) { 2042 1.50 lukem file = cp + 1; 2043 1.187 lukem (void)strlcpy(dirbuf, "/", sizeof(dirbuf)); 2044 1.187 lukem dir = dirbuf; 2045 1.50 lukem } else if (cp != NULL) { 2046 1.42 lukem *cp++ = '\0'; 2047 1.42 lukem file = cp; 2048 1.42 lukem } else { 2049 1.42 lukem file = dir; 2050 1.42 lukem dir = NULL; 2051 1.42 lukem } 2052 1.63 lukem } else 2053 1.63 lukem dir = NULL; 2054 1.214 christos if (ui.utype == FTP_URL_T && file != NULL) { 2055 1.157 lukem url_decode(file); 2056 1.53 lukem /* but still don't url_decode(dir) */ 2057 1.53 lukem } 2058 1.235 christos DPRINTF("%s: user `%s' pass `%s' host %s port %s " 2059 1.235 christos "path `%s' dir `%s' file `%s'\n", __func__, 2060 1.214 christos STRorNULL(auth.user), STRorNULL(auth.pass), 2061 1.214 christos STRorNULL(ui.host), STRorNULL(ui.port), 2062 1.214 christos STRorNULL(ui.path), STRorNULL(dir), STRorNULL(file)); 2063 1.42 lukem 2064 1.42 lukem dirhasglob = filehasglob = 0; 2065 1.224 maya if (doglob && 2066 1.224 maya (ui.utype == CLASSIC_URL_T || ui.utype == FTP_URL_T)) { 2067 1.42 lukem if (! EMPTYSTRING(dir) && strpbrk(dir, "*?[]{}") != NULL) 2068 1.42 lukem dirhasglob = 1; 2069 1.42 lukem if (! EMPTYSTRING(file) && strpbrk(file, "*?[]{}") != NULL) 2070 1.42 lukem filehasglob = 1; 2071 1.42 lukem } 2072 1.42 lukem 2073 1.50 lukem /* Set up the connection */ 2074 1.160 lukem oanonftp = anonftp; 2075 1.50 lukem if (connected) 2076 1.50 lukem disconnect(0, NULL); 2077 1.160 lukem anonftp = oanonftp; 2078 1.187 lukem (void)strlcpy(cmdbuf, getprogname(), sizeof(cmdbuf)); 2079 1.187 lukem xargv[0] = cmdbuf; 2080 1.214 christos xargv[1] = ui.host; 2081 1.50 lukem xargv[2] = NULL; 2082 1.50 lukem xargc = 2; 2083 1.214 christos if (ui.port) { 2084 1.214 christos xargv[2] = ui.port; 2085 1.50 lukem xargv[3] = NULL; 2086 1.50 lukem xargc = 3; 2087 1.50 lukem } 2088 1.50 lukem oautologin = autologin; 2089 1.116 lukem /* don't autologin in setpeer(), use ftp_login() below */ 2090 1.116 lukem autologin = 0; 2091 1.50 lukem setpeer(xargc, xargv); 2092 1.50 lukem autologin = oautologin; 2093 1.116 lukem if ((connected == 0) || 2094 1.214 christos (connected == 1 && !ftp_login(ui.host, auth.user, auth.pass))) { 2095 1.189 drochner warnx("Can't connect or login to host `%s:%s'", 2096 1.214 christos ui.host, ui.port ? ui.port : "?"); 2097 1.50 lukem goto cleanup_fetch_ftp; 2098 1.50 lukem } 2099 1.42 lukem 2100 1.186 lukem switch (transtype) { 2101 1.53 lukem case TYPE_A: 2102 1.118 lukem setascii(1, xargv); 2103 1.53 lukem break; 2104 1.53 lukem case TYPE_I: 2105 1.118 lukem setbinary(1, xargv); 2106 1.53 lukem break; 2107 1.53 lukem default: 2108 1.235 christos errx(1, "%s: unknown transfer type %d", __func__, transtype); 2109 1.53 lukem } 2110 1.53 lukem 2111 1.63 lukem /* 2112 1.63 lukem * Change directories, if necessary. 2113 1.63 lukem * 2114 1.63 lukem * Note: don't use EMPTYSTRING(dir) below, because 2115 1.63 lukem * dir=="" means something different from dir==NULL. 2116 1.63 lukem */ 2117 1.53 lukem if (dir != NULL && !dirhasglob) { 2118 1.53 lukem char *nextpart; 2119 1.42 lukem 2120 1.53 lukem /* 2121 1.97 lukem * If we are dealing with a classic `[user@]host:[path]' 2122 1.97 lukem * (urltype is CLASSIC_URL_T) then we have a raw directory 2123 1.53 lukem * name (not encoded in any way) and we can change 2124 1.53 lukem * directories in one step. 2125 1.53 lukem * 2126 1.53 lukem * If we are dealing with an `ftp://host/path' URL 2127 1.193 lukem * (urltype is FTP_URL_T), then RFC 3986 says we need to 2128 1.53 lukem * send a separate CWD command for each unescaped "/" 2129 1.53 lukem * in the path, and we have to interpret %hex escaping 2130 1.235 christos * *after* we find the slashes. It's possible to get 2131 1.53 lukem * empty components here, (from multiple adjacent 2132 1.193 lukem * slashes in the path) and RFC 3986 says that we should 2133 1.53 lukem * still do `CWD ' (with a null argument) in such cases. 2134 1.53 lukem * 2135 1.63 lukem * Many ftp servers don't support `CWD ', so if there's an 2136 1.63 lukem * error performing that command, bail out with a descriptive 2137 1.63 lukem * message. 2138 1.53 lukem * 2139 1.53 lukem * Examples: 2140 1.53 lukem * 2141 1.63 lukem * host: dir="", urltype=CLASSIC_URL_T 2142 1.63 lukem * logged in (to default directory) 2143 1.53 lukem * host:file dir=NULL, urltype=CLASSIC_URL_T 2144 1.53 lukem * "RETR file" 2145 1.63 lukem * host:dir/ dir="dir", urltype=CLASSIC_URL_T 2146 1.63 lukem * "CWD dir", logged in 2147 1.63 lukem * ftp://host/ dir="", urltype=FTP_URL_T 2148 1.63 lukem * logged in (to default directory) 2149 1.63 lukem * ftp://host/dir/ dir="dir", urltype=FTP_URL_T 2150 1.63 lukem * "CWD dir", logged in 2151 1.53 lukem * ftp://host/file dir=NULL, urltype=FTP_URL_T 2152 1.53 lukem * "RETR file" 2153 1.53 lukem * ftp://host//file dir="", urltype=FTP_URL_T 2154 1.63 lukem * "CWD ", "RETR file" 2155 1.53 lukem * host:/file dir="/", urltype=CLASSIC_URL_T 2156 1.53 lukem * "CWD /", "RETR file" 2157 1.53 lukem * ftp://host///file dir="/", urltype=FTP_URL_T 2158 1.63 lukem * "CWD ", "CWD ", "RETR file" 2159 1.53 lukem * ftp://host/%2F/file dir="%2F", urltype=FTP_URL_T 2160 1.53 lukem * "CWD /", "RETR file" 2161 1.53 lukem * ftp://host/foo/file dir="foo", urltype=FTP_URL_T 2162 1.53 lukem * "CWD foo", "RETR file" 2163 1.53 lukem * ftp://host/foo/bar/file dir="foo/bar" 2164 1.53 lukem * "CWD foo", "CWD bar", "RETR file" 2165 1.53 lukem * ftp://host//foo/bar/file dir="/foo/bar" 2166 1.63 lukem * "CWD ", "CWD foo", "CWD bar", "RETR file" 2167 1.53 lukem * ftp://host/foo//bar/file dir="foo//bar" 2168 1.63 lukem * "CWD foo", "CWD ", "CWD bar", "RETR file" 2169 1.53 lukem * ftp://host/%2F/foo/bar/file dir="%2F/foo/bar" 2170 1.53 lukem * "CWD /", "CWD foo", "CWD bar", "RETR file" 2171 1.53 lukem * ftp://host/%2Ffoo/bar/file dir="%2Ffoo/bar" 2172 1.53 lukem * "CWD /foo", "CWD bar", "RETR file" 2173 1.235 christos * ftp://host/%2Ffoo%2Fbar/file dir="%2Ffoo%2Fbar" 2174 1.53 lukem * "CWD /foo/bar", "RETR file" 2175 1.53 lukem * ftp://host/%2Ffoo%2Fbar%2Ffile dir=NULL 2176 1.53 lukem * "RETR /foo/bar/file" 2177 1.53 lukem * 2178 1.53 lukem * Note that we don't need `dir' after this point. 2179 1.53 lukem */ 2180 1.53 lukem do { 2181 1.214 christos if (ui.utype == FTP_URL_T) { 2182 1.53 lukem nextpart = strchr(dir, '/'); 2183 1.53 lukem if (nextpart) { 2184 1.53 lukem *nextpart = '\0'; 2185 1.53 lukem nextpart++; 2186 1.53 lukem } 2187 1.53 lukem url_decode(dir); 2188 1.53 lukem } else 2189 1.53 lukem nextpart = NULL; 2190 1.235 christos DPRINTF("%s: dir `%s', nextpart `%s'\n", __func__, 2191 1.183 lukem STRorNULL(dir), STRorNULL(nextpart)); 2192 1.214 christos if (ui.utype == FTP_URL_T || *dir != '\0') { 2193 1.187 lukem (void)strlcpy(cmdbuf, "cd", sizeof(cmdbuf)); 2194 1.187 lukem xargv[0] = cmdbuf; 2195 1.53 lukem xargv[1] = dir; 2196 1.53 lukem xargv[2] = NULL; 2197 1.53 lukem dirchange = 0; 2198 1.53 lukem cd(2, xargv); 2199 1.63 lukem if (! dirchange) { 2200 1.63 lukem if (*dir == '\0' && code == 500) 2201 1.63 lukem fprintf(stderr, 2202 1.63 lukem "\n" 2203 1.63 lukem "ftp: The `CWD ' command (without a directory), which is required by\n" 2204 1.193 lukem " RFC 3986 to support the empty directory in the URL pathname (`//'),\n" 2205 1.193 lukem " conflicts with the server's conformance to RFC 959.\n" 2206 1.63 lukem " Try the same URL without the `//' in the URL pathname.\n" 2207 1.63 lukem "\n"); 2208 1.53 lukem goto cleanup_fetch_ftp; 2209 1.63 lukem } 2210 1.53 lukem } 2211 1.53 lukem dir = nextpart; 2212 1.53 lukem } while (dir != NULL); 2213 1.42 lukem } 2214 1.42 lukem 2215 1.42 lukem if (EMPTYSTRING(file)) { 2216 1.42 lukem rval = -1; 2217 1.42 lukem goto cleanup_fetch_ftp; 2218 1.42 lukem } 2219 1.42 lukem 2220 1.42 lukem if (dirhasglob) { 2221 1.78 lukem (void)strlcpy(rempath, dir, sizeof(rempath)); 2222 1.78 lukem (void)strlcat(rempath, "/", sizeof(rempath)); 2223 1.78 lukem (void)strlcat(rempath, file, sizeof(rempath)); 2224 1.42 lukem file = rempath; 2225 1.42 lukem } 2226 1.42 lukem 2227 1.42 lukem /* Fetch the file(s). */ 2228 1.42 lukem xargc = 2; 2229 1.187 lukem (void)strlcpy(cmdbuf, "get", sizeof(cmdbuf)); 2230 1.187 lukem xargv[0] = cmdbuf; 2231 1.42 lukem xargv[1] = file; 2232 1.42 lukem xargv[2] = NULL; 2233 1.42 lukem if (dirhasglob || filehasglob) { 2234 1.42 lukem int ointeractive; 2235 1.42 lukem 2236 1.42 lukem ointeractive = interactive; 2237 1.42 lukem interactive = 0; 2238 1.155 lukem if (restartautofetch) 2239 1.187 lukem (void)strlcpy(cmdbuf, "mreget", sizeof(cmdbuf)); 2240 1.155 lukem else 2241 1.187 lukem (void)strlcpy(cmdbuf, "mget", sizeof(cmdbuf)); 2242 1.187 lukem xargv[0] = cmdbuf; 2243 1.42 lukem mget(xargc, xargv); 2244 1.42 lukem interactive = ointeractive; 2245 1.42 lukem } else { 2246 1.229 christos char *destfile = outfile; 2247 1.229 christos if (destfile == NULL) { 2248 1.53 lukem cp = strrchr(file, '/'); /* find savefile */ 2249 1.53 lukem if (cp != NULL) 2250 1.229 christos destfile = cp + 1; 2251 1.53 lukem else 2252 1.229 christos destfile = file; 2253 1.42 lukem } 2254 1.229 christos xargv[2] = (char *)destfile; 2255 1.53 lukem xargv[3] = NULL; 2256 1.53 lukem xargc++; 2257 1.52 lukem if (restartautofetch) 2258 1.52 lukem reget(xargc, xargv); 2259 1.52 lukem else 2260 1.52 lukem get(xargc, xargv); 2261 1.42 lukem } 2262 1.42 lukem 2263 1.42 lukem if ((code / 100) == COMPLETE) 2264 1.42 lukem rval = 0; 2265 1.42 lukem 2266 1.118 lukem cleanup_fetch_ftp: 2267 1.214 christos freeurlinfo(&ui); 2268 1.214 christos freeauthinfo(&auth); 2269 1.42 lukem return (rval); 2270 1.42 lukem } 2271 1.42 lukem 2272 1.42 lukem /* 2273 1.42 lukem * Retrieve the given file to outfile. 2274 1.42 lukem * Supports arguments of the form: 2275 1.42 lukem * "host:path", "ftp://host/path" if $ftpproxy, call fetch_url() else 2276 1.42 lukem * call fetch_ftp() 2277 1.50 lukem * "http://host/path" call fetch_url() to use HTTP 2278 1.42 lukem * "file:///path" call fetch_url() to copy 2279 1.42 lukem * "about:..." print a message 2280 1.42 lukem * 2281 1.42 lukem * Returns 1 on failure, 0 on completed xfer, -1 if ftp connection 2282 1.42 lukem * is still open (e.g, ftp xfer with trailing /) 2283 1.42 lukem */ 2284 1.42 lukem static int 2285 1.235 christos go_fetch(const char *url, struct urlinfo *rui) 2286 1.42 lukem { 2287 1.186 lukem char *proxyenv; 2288 1.240 christos const char *p; 2289 1.42 lukem 2290 1.147 christos #ifndef NO_ABOUT 2291 1.42 lukem /* 2292 1.42 lukem * Check for about:* 2293 1.42 lukem */ 2294 1.150 lukem if (STRNEQUAL(url, ABOUT_URL)) { 2295 1.42 lukem url += sizeof(ABOUT_URL) -1; 2296 1.144 lukem if (strcasecmp(url, "ftp") == 0 || 2297 1.144 lukem strcasecmp(url, "tnftp") == 0) { 2298 1.87 lukem fputs( 2299 1.143 salo "This version of ftp has been enhanced by Luke Mewburn <lukem (at) NetBSD.org>\n" 2300 1.87 lukem "for the NetBSD project. Execute `man ftp' for more details.\n", ttyout); 2301 1.87 lukem } else if (strcasecmp(url, "lukem") == 0) { 2302 1.87 lukem fputs( 2303 1.87 lukem "Luke Mewburn is the author of most of the enhancements in this ftp client.\n" 2304 1.143 salo "Please email feedback to <lukem (at) NetBSD.org>.\n", ttyout); 2305 1.42 lukem } else if (strcasecmp(url, "netbsd") == 0) { 2306 1.87 lukem fputs( 2307 1.87 lukem "NetBSD is a freely available and redistributable UNIX-like operating system.\n" 2308 1.144 lukem "For more information, see http://www.NetBSD.org/\n", ttyout); 2309 1.92 lukem } else if (strcasecmp(url, "version") == 0) { 2310 1.115 lukem fprintf(ttyout, "Version: %s %s%s\n", 2311 1.115 lukem FTP_PRODUCT, FTP_VERSION, 2312 1.115 lukem #ifdef INET6 2313 1.115 lukem "" 2314 1.115 lukem #else 2315 1.115 lukem " (-IPv6)" 2316 1.115 lukem #endif 2317 1.115 lukem ); 2318 1.42 lukem } else { 2319 1.42 lukem fprintf(ttyout, "`%s' is an interesting topic.\n", url); 2320 1.42 lukem } 2321 1.92 lukem fputs("\n", ttyout); 2322 1.42 lukem return (0); 2323 1.42 lukem } 2324 1.147 christos #endif 2325 1.42 lukem 2326 1.42 lukem /* 2327 1.42 lukem * Check for file:// and http:// URLs. 2328 1.42 lukem */ 2329 1.199 christos if (STRNEQUAL(url, HTTP_URL) 2330 1.199 christos #ifdef WITH_SSL 2331 1.199 christos || STRNEQUAL(url, HTTPS_URL) 2332 1.199 christos #endif 2333 1.199 christos || STRNEQUAL(url, FILE_URL)) 2334 1.235 christos return (fetch_url(url, NULL, NULL, NULL, rui)); 2335 1.42 lukem 2336 1.42 lukem /* 2337 1.196 apb * If it contains "://" but does not begin with ftp:// 2338 1.196 apb * or something that was already handled, then it's 2339 1.196 apb * unsupported. 2340 1.196 apb * 2341 1.196 apb * If it contains ":" but not "://" then we assume the 2342 1.196 apb * part before the colon is a host name, not an URL scheme, 2343 1.196 apb * so we don't try to match that here. 2344 1.196 apb */ 2345 1.196 apb if ((p = strstr(url, "://")) != NULL && ! STRNEQUAL(url, FTP_URL)) 2346 1.197 apb errx(1, "Unsupported URL scheme `%.*s'", (int)(p - url), url); 2347 1.196 apb 2348 1.196 apb /* 2349 1.235 christos * Refer to previous urlinfo if provided. This makes relative 2350 1.235 christos * redirects work. 2351 1.235 christos */ 2352 1.235 christos if (use_relative(rui)) 2353 1.235 christos return fetch_url(url, NULL, NULL, NULL, rui); 2354 1.235 christos 2355 1.235 christos /* 2356 1.42 lukem * Try FTP URL-style and host:file arguments next. 2357 1.42 lukem * If ftpproxy is set with an FTP URL, use fetch_url() 2358 1.234 andvar * Otherwise, use fetch_ftp(). 2359 1.42 lukem */ 2360 1.186 lukem proxyenv = getoptionvalue("ftp_proxy"); 2361 1.186 lukem if (!EMPTYSTRING(proxyenv) && STRNEQUAL(url, FTP_URL)) 2362 1.235 christos return (fetch_url(url, NULL, NULL, NULL, rui)); 2363 1.42 lukem 2364 1.52 lukem return (fetch_ftp(url)); 2365 1.42 lukem } 2366 1.42 lukem 2367 1.42 lukem /* 2368 1.42 lukem * Retrieve multiple files from the command line, 2369 1.42 lukem * calling go_fetch() for each file. 2370 1.42 lukem * 2371 1.42 lukem * If an ftp path has a trailing "/", the path will be cd-ed into and 2372 1.41 lukem * the connection remains open, and the function will return -1 2373 1.41 lukem * (to indicate the connection is alive). 2374 1.1 lukem * If an error occurs the return value will be the offset+1 in 2375 1.1 lukem * argv[] of the file that caused a problem (i.e, argv[x] 2376 1.1 lukem * returns x+1) 2377 1.1 lukem * Otherwise, 0 is returned if all files retrieved successfully. 2378 1.1 lukem */ 2379 1.1 lukem int 2380 1.111 lukem auto_fetch(int argc, char *argv[]) 2381 1.1 lukem { 2382 1.160 lukem volatile int argpos, rval; 2383 1.22 lukem 2384 1.160 lukem argpos = rval = 0; 2385 1.1 lukem 2386 1.88 lukem if (sigsetjmp(toplevel, 1)) { 2387 1.2 lukem if (connected) 2388 1.2 lukem disconnect(0, NULL); 2389 1.148 lukem if (rval > 0) 2390 1.148 lukem rval = argpos + 1; 2391 1.148 lukem return (rval); 2392 1.2 lukem } 2393 1.88 lukem (void)xsignal(SIGINT, intr); 2394 1.91 lukem (void)xsignal(SIGPIPE, lostpeer); 2395 1.1 lukem 2396 1.1 lukem /* 2397 1.1 lukem * Loop through as long as there's files to fetch. 2398 1.1 lukem */ 2399 1.160 lukem for (; (rval == 0) && (argpos < argc); argpos++) { 2400 1.1 lukem if (strchr(argv[argpos], ':') == NULL) 2401 1.1 lukem break; 2402 1.42 lukem redirect_loop = 0; 2403 1.90 lukem if (!anonftp) 2404 1.90 lukem anonftp = 2; /* Handle "automatic" transfers. */ 2405 1.235 christos rval = go_fetch(argv[argpos], NULL); 2406 1.52 lukem if (outfile != NULL && strcmp(outfile, "-") != 0 2407 1.229 christos && outfile[0] != '|') { 2408 1.229 christos FREEPTR(outfile); 2409 1.229 christos } 2410 1.42 lukem if (rval > 0) 2411 1.1 lukem rval = argpos + 1; 2412 1.42 lukem } 2413 1.3 lukem 2414 1.1 lukem if (connected && rval != -1) 2415 1.1 lukem disconnect(0, NULL); 2416 1.114 lukem return (rval); 2417 1.114 lukem } 2418 1.114 lukem 2419 1.114 lukem 2420 1.170 lukem /* 2421 1.170 lukem * Upload multiple files from the command line. 2422 1.170 lukem * 2423 1.170 lukem * If an error occurs the return value will be the offset+1 in 2424 1.170 lukem * argv[] of the file that caused a problem (i.e, argv[x] 2425 1.170 lukem * returns x+1) 2426 1.170 lukem * Otherwise, 0 is returned if all files uploaded successfully. 2427 1.170 lukem */ 2428 1.114 lukem int 2429 1.114 lukem auto_put(int argc, char **argv, const char *uploadserver) 2430 1.114 lukem { 2431 1.114 lukem char *uargv[4], *path, *pathsep; 2432 1.170 lukem int uargc, rval, argpos; 2433 1.159 lukem size_t len; 2434 1.187 lukem char cmdbuf[MAX_C_NAME]; 2435 1.114 lukem 2436 1.187 lukem (void)strlcpy(cmdbuf, "mput", sizeof(cmdbuf)); 2437 1.187 lukem uargv[0] = cmdbuf; 2438 1.187 lukem uargv[1] = argv[0]; 2439 1.187 lukem uargc = 2; 2440 1.114 lukem uargv[2] = uargv[3] = NULL; 2441 1.114 lukem pathsep = NULL; 2442 1.114 lukem rval = 1; 2443 1.114 lukem 2444 1.235 christos DPRINTF("%s: target `%s'\n", __func__, uploadserver); 2445 1.114 lukem 2446 1.166 christos path = ftp_strdup(uploadserver); 2447 1.114 lukem len = strlen(path); 2448 1.114 lukem if (path[len - 1] != '/' && path[len - 1] != ':') { 2449 1.114 lukem /* 2450 1.114 lukem * make sure we always pass a directory to auto_fetch 2451 1.114 lukem */ 2452 1.114 lukem if (argc > 1) { /* more than one file to upload */ 2453 1.235 christos len = strlen(uploadserver) + 2; /* path + "/" + "\0" */ 2454 1.114 lukem free(path); 2455 1.166 christos path = (char *)ftp_malloc(len); 2456 1.114 lukem (void)strlcpy(path, uploadserver, len); 2457 1.114 lukem (void)strlcat(path, "/", len); 2458 1.114 lukem } else { /* single file to upload */ 2459 1.187 lukem (void)strlcpy(cmdbuf, "put", sizeof(cmdbuf)); 2460 1.187 lukem uargv[0] = cmdbuf; 2461 1.114 lukem pathsep = strrchr(path, '/'); 2462 1.114 lukem if (pathsep == NULL) { 2463 1.114 lukem pathsep = strrchr(path, ':'); 2464 1.114 lukem if (pathsep == NULL) { 2465 1.114 lukem warnx("Invalid URL `%s'", path); 2466 1.114 lukem goto cleanup_auto_put; 2467 1.114 lukem } 2468 1.114 lukem pathsep++; 2469 1.166 christos uargv[2] = ftp_strdup(pathsep); 2470 1.114 lukem pathsep[0] = '/'; 2471 1.157 lukem } else 2472 1.166 christos uargv[2] = ftp_strdup(pathsep + 1); 2473 1.114 lukem pathsep[1] = '\0'; 2474 1.114 lukem uargc++; 2475 1.114 lukem } 2476 1.114 lukem } 2477 1.235 christos DPRINTF("%s: URL `%s' argv[2] `%s'\n", __func__, 2478 1.183 lukem path, STRorNULL(uargv[2])); 2479 1.157 lukem 2480 1.157 lukem /* connect and cwd */ 2481 1.114 lukem rval = auto_fetch(1, &path); 2482 1.114 lukem if(rval >= 0) 2483 1.114 lukem goto cleanup_auto_put; 2484 1.114 lukem 2485 1.170 lukem rval = 0; 2486 1.170 lukem 2487 1.170 lukem /* target filename provided; upload 1 file */ 2488 1.114 lukem /* XXX : is this the best way? */ 2489 1.114 lukem if (uargc == 3) { 2490 1.114 lukem uargv[1] = argv[0]; 2491 1.114 lukem put(uargc, uargv); 2492 1.170 lukem if ((code / 100) != COMPLETE) 2493 1.170 lukem rval = 1; 2494 1.170 lukem } else { /* otherwise a target dir: upload all files to it */ 2495 1.170 lukem for(argpos = 0; argv[argpos] != NULL; argpos++) { 2496 1.170 lukem uargv[1] = argv[argpos]; 2497 1.170 lukem mput(uargc, uargv); 2498 1.170 lukem if ((code / 100) != COMPLETE) { 2499 1.170 lukem rval = argpos + 1; 2500 1.170 lukem break; 2501 1.170 lukem } 2502 1.170 lukem } 2503 1.114 lukem } 2504 1.114 lukem 2505 1.118 lukem cleanup_auto_put: 2506 1.168 christos free(path); 2507 1.114 lukem FREEPTR(uargv[2]); 2508 1.1 lukem return (rval); 2509 1.1 lukem } 2510