ftp.c revision 88769
1223328Sgavin/*- 2223328Sgavin * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav 3223328Sgavin * All rights reserved. 4223328Sgavin * 5223328Sgavin * Redistribution and use in source and binary forms, with or without 6223328Sgavin * modification, are permitted provided that the following conditions 7223328Sgavin * are met: 8223328Sgavin * 1. Redistributions of source code must retain the above copyright 9223328Sgavin * notice, this list of conditions and the following disclaimer 10223328Sgavin * in this position and unchanged. 11223328Sgavin * 2. Redistributions in binary form must reproduce the above copyright 12223328Sgavin * notice, this list of conditions and the following disclaimer in the 13223328Sgavin * documentation and/or other materials provided with the distribution. 14223328Sgavin * 3. The name of the author may not be used to endorse or promote products 15223328Sgavin * derived from this software without specific prior written permission 16223328Sgavin * 17223328Sgavin * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18223328Sgavin * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19223328Sgavin * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20223328Sgavin * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21223328Sgavin * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22223328Sgavin * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23223328Sgavin * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24223328Sgavin * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25223328Sgavin * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26223328Sgavin * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27223328Sgavin */ 28223328Sgavin 29223328Sgavin#include <sys/cdefs.h> 30223328Sgavin__FBSDID("$FreeBSD: head/lib/libfetch/ftp.c 88769 2002-01-01 14:48:09Z des $"); 31223328Sgavin 32223328Sgavin/* 33223328Sgavin * Portions of this code were taken from or based on ftpio.c: 34223328Sgavin * 35223328Sgavin * ---------------------------------------------------------------------------- 36223328Sgavin * "THE BEER-WARE LICENSE" (Revision 42): 37223328Sgavin * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you 38223328Sgavin * can do whatever you want with this stuff. If we meet some day, and you think 39223328Sgavin * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 40223328Sgavin * ---------------------------------------------------------------------------- 41223328Sgavin * 42223328Sgavin * Major Changelog: 43223328Sgavin * 44223328Sgavin * Dag-Erling Co�dan Sm�rgrav 45223328Sgavin * 9 Jun 1998 46223328Sgavin * 47223328Sgavin * Incorporated into libfetch 48223328Sgavin * 49223328Sgavin * Jordan K. Hubbard 50223328Sgavin * 17 Jan 1996 51223328Sgavin * 52223328Sgavin * Turned inside out. Now returns xfers as new file ids, not as a special 53223328Sgavin * `state' of FTP_t 54223328Sgavin * 55223328Sgavin * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $ 56223328Sgavin * 57223328Sgavin */ 58223328Sgavin 59223328Sgavin#include <sys/param.h> 60223328Sgavin#include <sys/socket.h> 61223328Sgavin#include <netinet/in.h> 62223328Sgavin 63223328Sgavin#include <ctype.h> 64223328Sgavin#include <err.h> 65223328Sgavin#include <errno.h> 66223328Sgavin#include <fcntl.h> 67223328Sgavin#include <netdb.h> 68223328Sgavin#include <stdarg.h> 69223328Sgavin#include <stdio.h> 70223328Sgavin#include <stdlib.h> 71223328Sgavin#include <string.h> 72223328Sgavin#include <time.h> 73223328Sgavin#include <unistd.h> 74223328Sgavin 75223328Sgavin#include "fetch.h" 76223328Sgavin#include "common.h" 77223328Sgavin#include "ftperr.h" 78223328Sgavin 79223328Sgavin#define FTP_ANONYMOUS_USER "anonymous" 80223328Sgavin 81223328Sgavin#define FTP_CONNECTION_ALREADY_OPEN 125 82223328Sgavin#define FTP_OPEN_DATA_CONNECTION 150 83223328Sgavin#define FTP_OK 200 84223328Sgavin#define FTP_FILE_STATUS 213 85223328Sgavin#define FTP_SERVICE_READY 220 86223328Sgavin#define FTP_TRANSFER_COMPLETE 226 87223328Sgavin#define FTP_PASSIVE_MODE 227 88223328Sgavin#define FTP_LPASSIVE_MODE 228 89223328Sgavin#define FTP_EPASSIVE_MODE 229 90223328Sgavin#define FTP_LOGGED_IN 230 91223328Sgavin#define FTP_FILE_ACTION_OK 250 92223328Sgavin#define FTP_NEED_PASSWORD 331 93223328Sgavin#define FTP_NEED_ACCOUNT 332 94223328Sgavin#define FTP_FILE_OK 350 95223328Sgavin#define FTP_SYNTAX_ERROR 500 96223328Sgavin#define FTP_PROTOCOL_ERROR 999 97223328Sgavin 98223328Sgavinstatic struct url cached_host; 99223328Sgavinstatic int cached_socket; 100223328Sgavin 101223328Sgavinstatic char *last_reply; 102223328Sgavinstatic size_t lr_size, lr_length; 103223328Sgavinstatic int last_code; 104223328Sgavin 105223328Sgavin#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 106223328Sgavin && isdigit(foo[2]) \ 107223328Sgavin && (foo[3] == ' ' || foo[3] == '\0')) 108223328Sgavin#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 109223328Sgavin && isdigit(foo[2]) && foo[3] == '-') 110223328Sgavin 111223328Sgavin/* translate IPv4 mapped IPv6 address to IPv4 address */ 112223328Sgavinstatic void 113223328Sgavinunmappedaddr(struct sockaddr_in6 *sin6) 114223328Sgavin{ 115223328Sgavin struct sockaddr_in *sin4; 116223328Sgavin u_int32_t addr; 117223328Sgavin int port; 118223328Sgavin 119223328Sgavin if (sin6->sin6_family != AF_INET6 || 120223328Sgavin !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 121223328Sgavin return; 122223328Sgavin sin4 = (struct sockaddr_in *)sin6; 123223328Sgavin addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 124223328Sgavin port = sin6->sin6_port; 125223328Sgavin memset(sin4, 0, sizeof(struct sockaddr_in)); 126223328Sgavin sin4->sin_addr.s_addr = addr; 127223328Sgavin sin4->sin_port = port; 128223328Sgavin sin4->sin_family = AF_INET; 129223328Sgavin sin4->sin_len = sizeof(struct sockaddr_in); 130223328Sgavin} 131223328Sgavin 132223328Sgavin/* 133223328Sgavin * Get server response 134223328Sgavin */ 135223328Sgavinstatic int 136223328Sgavin_ftp_chkerr(int cd) 137223328Sgavin{ 138223328Sgavin if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 139223328Sgavin _fetch_syserr(); 140223328Sgavin return -1; 141223328Sgavin } 142223328Sgavin if (isftpinfo(last_reply)) { 143223328Sgavin while (lr_length && !isftpreply(last_reply)) { 144223328Sgavin if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 145223328Sgavin _fetch_syserr(); 146223328Sgavin return -1; 147223328Sgavin } 148223328Sgavin } 149223328Sgavin } 150223328Sgavin 151223328Sgavin while (lr_length && isspace(last_reply[lr_length-1])) 152223328Sgavin lr_length--; 153223328Sgavin last_reply[lr_length] = 0; 154223328Sgavin 155223328Sgavin if (!isftpreply(last_reply)) { 156223328Sgavin _ftp_seterr(FTP_PROTOCOL_ERROR); 157223328Sgavin return -1; 158223328Sgavin } 159223328Sgavin 160223328Sgavin last_code = (last_reply[0] - '0') * 100 161223328Sgavin + (last_reply[1] - '0') * 10 162223328Sgavin + (last_reply[2] - '0'); 163223328Sgavin 164223328Sgavin return last_code; 165223328Sgavin} 166223328Sgavin 167223328Sgavin/* 168223328Sgavin * Send a command and check reply 169223328Sgavin */ 170223328Sgavinstatic int 171223328Sgavin_ftp_cmd(int cd, const char *fmt, ...) 172223328Sgavin{ 173223328Sgavin va_list ap; 174223328Sgavin size_t len; 175223328Sgavin char *msg; 176223328Sgavin int r; 177223328Sgavin 178223328Sgavin va_start(ap, fmt); 179223328Sgavin len = vasprintf(&msg, fmt, ap); 180223328Sgavin va_end(ap); 181223328Sgavin 182223328Sgavin if (msg == NULL) { 183223328Sgavin errno = ENOMEM; 184223328Sgavin _fetch_syserr(); 185223328Sgavin return -1; 186223328Sgavin } 187223328Sgavin 188223328Sgavin r = _fetch_putln(cd, msg, len); 189223328Sgavin free(msg); 190223328Sgavin 191223328Sgavin if (r == -1) { 192223328Sgavin _fetch_syserr(); 193223328Sgavin return -1; 194223328Sgavin } 195223328Sgavin 196223328Sgavin return _ftp_chkerr(cd); 197223328Sgavin} 198223328Sgavin 199223328Sgavin/* 200223328Sgavin * Return a pointer to the filename part of a path 201223328Sgavin */ 202223328Sgavinstatic const char * 203223328Sgavin_ftp_filename(const char *file) 204223328Sgavin{ 205223328Sgavin char *s; 206223328Sgavin 207223328Sgavin if ((s = strrchr(file, '/')) == NULL) 208223328Sgavin return file; 209223328Sgavin else 210223328Sgavin return s + 1; 211223328Sgavin} 212223328Sgavin 213223328Sgavin/* 214223328Sgavin * Change working directory to the directory that contains the 215223328Sgavin * specified file. 216223328Sgavin */ 217223328Sgavinstatic int 218223328Sgavin_ftp_cwd(int cd, const char *file) 219223328Sgavin{ 220223328Sgavin char *s; 221223328Sgavin int e; 222223328Sgavin 223223328Sgavin if ((s = strrchr(file, '/')) == NULL || s == file) { 224223328Sgavin e = _ftp_cmd(cd, "CWD /"); 225223328Sgavin } else { 226223328Sgavin e = _ftp_cmd(cd, "CWD %.*s", s - file, file); 227223328Sgavin } 228223328Sgavin if (e != FTP_FILE_ACTION_OK) { 229223328Sgavin _ftp_seterr(e); 230223328Sgavin return -1; 231223328Sgavin } 232223328Sgavin return 0; 233223328Sgavin} 234223328Sgavin 235223328Sgavin/* 236223328Sgavin * Request and parse file stats 237223328Sgavin */ 238223328Sgavinstatic int 239223328Sgavin_ftp_stat(int cd, const char *file, struct url_stat *us) 240223328Sgavin{ 241223328Sgavin char *ln; 242223328Sgavin const char *s; 243223328Sgavin struct tm tm; 244223328Sgavin time_t t; 245223328Sgavin int e; 246223328Sgavin 247223328Sgavin us->size = -1; 248223328Sgavin us->atime = us->mtime = 0; 249223328Sgavin 250223328Sgavin if ((s = strrchr(file, '/')) == NULL) 251223328Sgavin s = file; 252223328Sgavin else 253223328Sgavin ++s; 254223328Sgavin 255223328Sgavin if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) { 256223328Sgavin _ftp_seterr(e); 257223328Sgavin return -1; 258223328Sgavin } 259223328Sgavin for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 260223328Sgavin /* nothing */ ; 261223328Sgavin for (us->size = 0; *ln && isdigit(*ln); ln++) 262223328Sgavin us->size = us->size * 10 + *ln - '0'; 263223328Sgavin if (*ln && !isspace(*ln)) { 264223328Sgavin _ftp_seterr(FTP_PROTOCOL_ERROR); 265223328Sgavin us->size = -1; 266223328Sgavin return -1; 267223328Sgavin } 268223328Sgavin if (us->size == 0) 269223328Sgavin us->size = -1; 270223328Sgavin DEBUG(fprintf(stderr, "size: [%lld]\n", (long long)us->size)); 271223328Sgavin 272223328Sgavin if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) { 273223328Sgavin _ftp_seterr(e); 274223328Sgavin return -1; 275223328Sgavin } 276223328Sgavin for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 277223328Sgavin /* nothing */ ; 278223328Sgavin switch (strspn(ln, "0123456789")) { 279223328Sgavin case 14: 280223328Sgavin break; 281223328Sgavin case 15: 282223328Sgavin ln++; 283223328Sgavin ln[0] = '2'; 284223328Sgavin ln[1] = '0'; 285223328Sgavin break; 286223328Sgavin default: 287223328Sgavin _ftp_seterr(FTP_PROTOCOL_ERROR); 288223328Sgavin return -1; 289223328Sgavin } 290223328Sgavin if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 291223328Sgavin &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 292223328Sgavin &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { 293223328Sgavin _ftp_seterr(FTP_PROTOCOL_ERROR); 294223328Sgavin return -1; 295223328Sgavin } 296223328Sgavin tm.tm_mon--; 297223328Sgavin tm.tm_year -= 1900; 298223328Sgavin tm.tm_isdst = -1; 299223328Sgavin t = timegm(&tm); 300223328Sgavin if (t == (time_t)-1) 301223328Sgavin t = time(NULL); 302223328Sgavin us->mtime = t; 303223328Sgavin us->atime = t; 304223328Sgavin DEBUG(fprintf(stderr, "last modified: [%04d-%02d-%02d %02d:%02d:%02d]\n", 305223328Sgavin tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 306223328Sgavin tm.tm_hour, tm.tm_min, tm.tm_sec)); 307223328Sgavin return 0; 308223328Sgavin} 309223328Sgavin 310223328Sgavin/* 311223328Sgavin * I/O functions for FTP 312223328Sgavin */ 313223328Sgavinstruct ftpio { 314223328Sgavin int csd; /* Control socket descriptor */ 315223328Sgavin int dsd; /* Data socket descriptor */ 316223328Sgavin int dir; /* Direction */ 317223328Sgavin int eof; /* EOF reached */ 318223328Sgavin int err; /* Error code */ 319223328Sgavin}; 320223328Sgavin 321223328Sgavinstatic int _ftp_readfn(void *, char *, int); 322223328Sgavinstatic int _ftp_writefn(void *, const char *, int); 323223328Sgavinstatic fpos_t _ftp_seekfn(void *, fpos_t, int); 324223328Sgavinstatic int _ftp_closefn(void *); 325223328Sgavin 326223328Sgavinstatic int 327223328Sgavin_ftp_readfn(void *v, char *buf, int len) 328223328Sgavin{ 329223328Sgavin struct ftpio *io; 330223328Sgavin int r; 331223328Sgavin 332223328Sgavin io = (struct ftpio *)v; 333223328Sgavin if (io == NULL) { 334223328Sgavin errno = EBADF; 335223328Sgavin return -1; 336223328Sgavin } 337223328Sgavin if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) { 338223328Sgavin errno = EBADF; 339223328Sgavin return -1; 340223328Sgavin } 341223328Sgavin if (io->err) { 342223328Sgavin errno = io->err; 343223328Sgavin return -1; 344223328Sgavin } 345223328Sgavin if (io->eof) 346223328Sgavin return 0; 347223328Sgavin r = read(io->dsd, buf, len); 348223328Sgavin if (r > 0) 349223328Sgavin return r; 350223328Sgavin if (r == 0) { 351223328Sgavin io->eof = 1; 352223328Sgavin return 0; 353223328Sgavin } 354223328Sgavin if (errno != EINTR) 355223328Sgavin io->err = errno; 356223328Sgavin return -1; 357223328Sgavin} 358223328Sgavin 359223328Sgavinstatic int 360223328Sgavin_ftp_writefn(void *v, const char *buf, int len) 361223328Sgavin{ 362223328Sgavin struct ftpio *io; 363223328Sgavin int w; 364223328Sgavin 365223328Sgavin io = (struct ftpio *)v; 366223328Sgavin if (io == NULL) { 367223328Sgavin errno = EBADF; 368223328Sgavin return -1; 369223328Sgavin } 370223328Sgavin if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) { 371223328Sgavin errno = EBADF; 372223328Sgavin return -1; 373223328Sgavin } 374223328Sgavin if (io->err) { 375223328Sgavin errno = io->err; 376223328Sgavin return -1; 377223328Sgavin } 378223328Sgavin w = write(io->dsd, buf, len); 379223328Sgavin if (w >= 0) 380223328Sgavin return w; 381223328Sgavin if (errno != EINTR) 382223328Sgavin io->err = errno; 383223328Sgavin return -1; 384223328Sgavin} 385223328Sgavin 386223328Sgavinstatic fpos_t 387223328Sgavin_ftp_seekfn(void *v, fpos_t pos __unused, int whence __unused) 388223328Sgavin{ 389223328Sgavin struct ftpio *io; 390223328Sgavin 391223328Sgavin io = (struct ftpio *)v; 392223328Sgavin if (io == NULL) { 393223328Sgavin errno = EBADF; 394223328Sgavin return -1; 395223328Sgavin } 396223328Sgavin errno = ESPIPE; 397223328Sgavin return -1; 398223328Sgavin} 399223328Sgavin 400223328Sgavinstatic int 401223328Sgavin_ftp_closefn(void *v) 402223328Sgavin{ 403223328Sgavin struct ftpio *io; 404223328Sgavin int r; 405223328Sgavin 406223328Sgavin io = (struct ftpio *)v; 407223328Sgavin if (io == NULL) { 408223328Sgavin errno = EBADF; 409223328Sgavin return -1; 410223328Sgavin } 411223328Sgavin if (io->dir == -1) 412223328Sgavin return 0; 413223328Sgavin if (io->csd == -1 || io->dsd == -1) { 414223328Sgavin errno = EBADF; 415223328Sgavin return -1; 416223328Sgavin } 417223328Sgavin close(io->dsd); 418223328Sgavin io->dir = -1; 419223328Sgavin io->dsd = -1; 420223328Sgavin DEBUG(fprintf(stderr, "Waiting for final status\n")); 421223328Sgavin r = _ftp_chkerr(io->csd); 422223328Sgavin close(io->csd); 423223328Sgavin free(io); 424223328Sgavin return (r == FTP_TRANSFER_COMPLETE) ? 0 : -1; 425223328Sgavin} 426223328Sgavin 427223328Sgavinstatic FILE * 428223328Sgavin_ftp_setup(int csd, int dsd, int mode) 429223328Sgavin{ 430223328Sgavin struct ftpio *io; 431223328Sgavin FILE *f; 432223328Sgavin 433223328Sgavin if ((io = malloc(sizeof *io)) == NULL) 434223328Sgavin return NULL; 435223328Sgavin io->csd = dup(csd); 436223328Sgavin io->dsd = dsd; 437223328Sgavin io->dir = mode; 438223328Sgavin io->eof = io->err = 0; 439223328Sgavin f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn); 440223328Sgavin if (f == NULL) 441223328Sgavin free(io); 442223328Sgavin return f; 443223328Sgavin} 444223328Sgavin 445223328Sgavin/* 446223328Sgavin * Transfer file 447223328Sgavin */ 448223328Sgavinstatic FILE * 449223328Sgavin_ftp_transfer(int cd, const char *oper, const char *file, 450223328Sgavin int mode, off_t offset, const char *flags) 451223328Sgavin{ 452223328Sgavin struct sockaddr_storage sa; 453223328Sgavin struct sockaddr_in6 *sin6; 454223328Sgavin struct sockaddr_in *sin4; 455223328Sgavin int low, pasv, verbose; 456223328Sgavin int e, sd = -1; 457223328Sgavin socklen_t l; 458223328Sgavin char *s; 459223328Sgavin FILE *df; 460223328Sgavin 461223328Sgavin /* check flags */ 462223328Sgavin low = CHECK_FLAG('l'); 463223328Sgavin pasv = CHECK_FLAG('p'); 464223328Sgavin verbose = CHECK_FLAG('v'); 465223328Sgavin 466223328Sgavin /* passive mode */ 467223328Sgavin if (!pasv) 468223328Sgavin pasv = ((s = getenv("FTP_PASSIVE_MODE")) != NULL && 469223328Sgavin strncasecmp(s, "no", 2) != 0); 470223328Sgavin 471223328Sgavin /* find our own address, bind, and listen */ 472223328Sgavin l = sizeof sa; 473223328Sgavin if (getsockname(cd, (struct sockaddr *)&sa, &l) == -1) 474223328Sgavin goto sysouch; 475223328Sgavin if (sa.ss_family == AF_INET6) 476223328Sgavin unmappedaddr((struct sockaddr_in6 *)&sa); 477223328Sgavin 478223328Sgavin /* open data socket */ 479223328Sgavin if ((sd = socket(sa.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 480223328Sgavin _fetch_syserr(); 481223328Sgavin return NULL; 482223328Sgavin } 483223328Sgavin 484223328Sgavin if (pasv) { 485223328Sgavin u_char addr[64]; 486223328Sgavin char *ln, *p; 487223328Sgavin unsigned int i; 488223328Sgavin int port; 489223328Sgavin 490223328Sgavin /* send PASV command */ 491223328Sgavin if (verbose) 492223328Sgavin _fetch_info("setting passive mode"); 493223328Sgavin switch (sa.ss_family) { 494223328Sgavin case AF_INET: 495223328Sgavin if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 496223328Sgavin goto ouch; 497223328Sgavin break; 498223328Sgavin case AF_INET6: 499223328Sgavin if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { 500223328Sgavin if (e == -1) 501223328Sgavin goto ouch; 502223328Sgavin if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) 503223328Sgavin goto ouch; 504223328Sgavin } 505223328Sgavin break; 506223328Sgavin default: 507223328Sgavin e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 508223328Sgavin goto ouch; 509223328Sgavin } 510223328Sgavin 511223328Sgavin /* 512223328Sgavin * Find address and port number. The reply to the PASV command 513223328Sgavin * is IMHO the one and only weak point in the FTP protocol. 514223328Sgavin */ 515223328Sgavin ln = last_reply; 516223328Sgavin switch (e) { 517223328Sgavin case FTP_PASSIVE_MODE: 518223328Sgavin case FTP_LPASSIVE_MODE: 519223328Sgavin for (p = ln + 3; *p && !isdigit(*p); p++) 520223328Sgavin /* nothing */ ; 521223328Sgavin if (!*p) { 522223328Sgavin e = FTP_PROTOCOL_ERROR; 523223328Sgavin goto ouch; 524223328Sgavin } 525223328Sgavin l = (e == FTP_PASSIVE_MODE ? 6 : 21); 526223328Sgavin for (i = 0; *p && i < l; i++, p++) 527223328Sgavin addr[i] = strtol(p, &p, 10); 528223328Sgavin if (i < l) { 529223328Sgavin e = FTP_PROTOCOL_ERROR; 530223328Sgavin goto ouch; 531223328Sgavin } 532223328Sgavin break; 533223328Sgavin case FTP_EPASSIVE_MODE: 534223328Sgavin for (p = ln + 3; *p && *p != '('; p++) 535223328Sgavin /* nothing */ ; 536223328Sgavin if (!*p) { 537223328Sgavin e = FTP_PROTOCOL_ERROR; 538223328Sgavin goto ouch; 539223328Sgavin } 540223328Sgavin ++p; 541223328Sgavin if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 542223328Sgavin &port, &addr[3]) != 5 || 543223328Sgavin addr[0] != addr[1] || 544223328Sgavin addr[0] != addr[2] || addr[0] != addr[3]) { 545223328Sgavin e = FTP_PROTOCOL_ERROR; 546223328Sgavin goto ouch; 547223328Sgavin } 548223328Sgavin break; 549223328Sgavin } 550223328Sgavin 551223328Sgavin /* seek to required offset */ 552223328Sgavin if (offset) 553223328Sgavin if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 554223328Sgavin goto sysouch; 555223328Sgavin 556223328Sgavin /* construct sockaddr for data socket */ 557223328Sgavin l = sizeof sa; 558223328Sgavin if (getpeername(cd, (struct sockaddr *)&sa, &l) == -1) 559223328Sgavin goto sysouch; 560223328Sgavin if (sa.ss_family == AF_INET6) 561223328Sgavin unmappedaddr((struct sockaddr_in6 *)&sa); 562223328Sgavin switch (sa.ss_family) { 563223328Sgavin case AF_INET6: 564223328Sgavin sin6 = (struct sockaddr_in6 *)&sa; 565223328Sgavin if (e == FTP_EPASSIVE_MODE) 566223328Sgavin sin6->sin6_port = htons(port); 567223328Sgavin else { 568223328Sgavin bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 569223328Sgavin bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 570223328Sgavin } 571223328Sgavin break; 572223328Sgavin case AF_INET: 573223328Sgavin sin4 = (struct sockaddr_in *)&sa; 574223328Sgavin if (e == FTP_EPASSIVE_MODE) 575223328Sgavin sin4->sin_port = htons(port); 576223328Sgavin else { 577223328Sgavin bcopy(addr, (char *)&sin4->sin_addr, 4); 578223328Sgavin bcopy(addr + 4, (char *)&sin4->sin_port, 2); 579223328Sgavin } 580223328Sgavin break; 581223328Sgavin default: 582223328Sgavin e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 583223328Sgavin break; 584223328Sgavin } 585223328Sgavin 586223328Sgavin /* connect to data port */ 587223328Sgavin if (verbose) 588223328Sgavin _fetch_info("opening data connection"); 589223328Sgavin if (connect(sd, (struct sockaddr *)&sa, sa.ss_len) == -1) 590223328Sgavin goto sysouch; 591223328Sgavin 592223328Sgavin /* make the server initiate the transfer */ 593223328Sgavin if (verbose) 594223328Sgavin _fetch_info("initiating transfer"); 595223328Sgavin e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 596223328Sgavin if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) 597223328Sgavin goto ouch; 598223328Sgavin 599223328Sgavin } else { 600223328Sgavin u_int32_t a; 601223328Sgavin u_short p; 602223328Sgavin int arg, d; 603223328Sgavin char *ap; 604223328Sgavin char hname[INET6_ADDRSTRLEN]; 605223328Sgavin 606223328Sgavin switch (sa.ss_family) { 607223328Sgavin case AF_INET6: 608223328Sgavin ((struct sockaddr_in6 *)&sa)->sin6_port = 0; 609223328Sgavin#ifdef IPV6_PORTRANGE 610223328Sgavin arg = low ? IPV6_PORTRANGE_DEFAULT : IPV6_PORTRANGE_HIGH; 611223328Sgavin if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 612223328Sgavin (char *)&arg, sizeof(arg)) == -1) 613223328Sgavin goto sysouch; 614223328Sgavin#endif 615223328Sgavin break; 616223328Sgavin case AF_INET: 617223328Sgavin ((struct sockaddr_in *)&sa)->sin_port = 0; 618223328Sgavin arg = low ? IP_PORTRANGE_DEFAULT : IP_PORTRANGE_HIGH; 619223328Sgavin if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 620223328Sgavin (char *)&arg, sizeof arg) == -1) 621223328Sgavin goto sysouch; 622223328Sgavin break; 623223328Sgavin } 624223328Sgavin if (verbose) 625223328Sgavin _fetch_info("binding data socket"); 626223328Sgavin if (bind(sd, (struct sockaddr *)&sa, sa.ss_len) == -1) 627223328Sgavin goto sysouch; 628223328Sgavin if (listen(sd, 1) == -1) 629223328Sgavin goto sysouch; 630223328Sgavin 631223328Sgavin /* find what port we're on and tell the server */ 632223328Sgavin if (getsockname(sd, (struct sockaddr *)&sa, &l) == -1) 633223328Sgavin goto sysouch; 634223328Sgavin switch (sa.ss_family) { 635223328Sgavin case AF_INET: 636223328Sgavin sin4 = (struct sockaddr_in *)&sa; 637223328Sgavin a = ntohl(sin4->sin_addr.s_addr); 638223328Sgavin p = ntohs(sin4->sin_port); 639223328Sgavin e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 640223328Sgavin (a >> 24) & 0xff, (a >> 16) & 0xff, 641223328Sgavin (a >> 8) & 0xff, a & 0xff, 642223328Sgavin (p >> 8) & 0xff, p & 0xff); 643223328Sgavin break; 644223328Sgavin case AF_INET6: 645223328Sgavin#define UC(b) (((int)b)&0xff) 646223328Sgavin e = -1; 647223328Sgavin sin6 = (struct sockaddr_in6 *)&sa; 648223328Sgavin if (getnameinfo((struct sockaddr *)&sa, sa.ss_len, 649223328Sgavin hname, sizeof(hname), 650223328Sgavin NULL, 0, NI_NUMERICHOST) == 0) { 651223328Sgavin e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 652223328Sgavin htons(sin6->sin6_port)); 653223328Sgavin if (e == -1) 654223328Sgavin goto ouch; 655223328Sgavin } 656223328Sgavin if (e != FTP_OK) { 657223328Sgavin ap = (char *)&sin6->sin6_addr; 658223328Sgavin e = _ftp_cmd(cd, 659223328Sgavin "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 660223328Sgavin 6, 16, 661223328Sgavin UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 662223328Sgavin UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 663223328Sgavin UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 664223328Sgavin UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 665223328Sgavin 2, 666223328Sgavin (ntohs(sin6->sin6_port) >> 8) & 0xff, 667223328Sgavin ntohs(sin6->sin6_port) & 0xff); 668223328Sgavin } 669223328Sgavin break; 670223328Sgavin default: 671223328Sgavin e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 672223328Sgavin goto ouch; 673223328Sgavin } 674223328Sgavin if (e != FTP_OK) 675223328Sgavin goto ouch; 676223328Sgavin 677223328Sgavin /* seek to required offset */ 678223328Sgavin if (offset) 679223328Sgavin if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 680223328Sgavin goto sysouch; 681223328Sgavin 682223328Sgavin /* make the server initiate the transfer */ 683223328Sgavin if (verbose) 684223328Sgavin _fetch_info("initiating transfer"); 685223328Sgavin e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 686223328Sgavin if (e != FTP_OPEN_DATA_CONNECTION) 687223328Sgavin goto ouch; 688223328Sgavin 689223328Sgavin /* accept the incoming connection and go to town */ 690223328Sgavin if ((d = accept(sd, NULL, NULL)) == -1) 691223328Sgavin goto sysouch; 692223328Sgavin close(sd); 693223328Sgavin sd = d; 694223328Sgavin } 695223328Sgavin 696223328Sgavin if ((df = _ftp_setup(cd, sd, mode)) == NULL) 697223328Sgavin goto sysouch; 698223328Sgavin return df; 699223328Sgavin 700223328Sgavinsysouch: 701223328Sgavin _fetch_syserr(); 702223328Sgavin if (sd >= 0) 703223328Sgavin close(sd); 704223328Sgavin return NULL; 705223328Sgavin 706223328Sgavinouch: 707223328Sgavin if (e != -1) 708223328Sgavin _ftp_seterr(e); 709223328Sgavin if (sd >= 0) 710223328Sgavin close(sd); 71198247Smikeh return NULL; 71298247Smikeh} 71398247Smikeh 71498247Smikeh/* 71598247Smikeh * Authenticate 71698247Smikeh */ 71798247Smikehstatic int 71898247Smikeh_ftp_authenticate(int cd, struct url *url, struct url *purl) 71998247Smikeh{ 72098247Smikeh const char *user, *pwd, *logname; 72198247Smikeh char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 72298247Smikeh int e, len; 72398247Smikeh 72498247Smikeh /* XXX FTP_AUTH, and maybe .netrc */ 72598247Smikeh 72698247Smikeh /* send user name and password */ 72798247Smikeh user = url->user; 72898247Smikeh if (!user || !*user) 72998247Smikeh user = getenv("FTP_LOGIN"); 73098247Smikeh if (!user || !*user) 73198247Smikeh user = FTP_ANONYMOUS_USER; 73298247Smikeh if (purl && url->port == _fetch_default_port(url->scheme)) 73398247Smikeh e = _ftp_cmd(cd, "USER %s@%s", user, url->host); 73498247Smikeh else if (purl) 73598247Smikeh e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port); 73698247Smikeh else 73798247Smikeh e = _ftp_cmd(cd, "USER %s", user); 73898247Smikeh 73998247Smikeh /* did the server request a password? */ 74098247Smikeh if (e == FTP_NEED_PASSWORD) { 74198247Smikeh pwd = url->pwd; 74298247Smikeh if (!pwd || !*pwd) 74398247Smikeh pwd = getenv("FTP_PASSWORD"); 74498247Smikeh if (!pwd || !*pwd) { 74598247Smikeh if ((logname = getlogin()) == 0) 74698247Smikeh logname = FTP_ANONYMOUS_USER; 74798247Smikeh if ((len = snprintf(pbuf, MAXLOGNAME + 1, "%s@", logname)) < 0) 74898247Smikeh len = 0; 74998247Smikeh else if (len > MAXLOGNAME) 75098247Smikeh len = MAXLOGNAME; 75198247Smikeh gethostname(pbuf + len, sizeof pbuf - len); 75298247Smikeh pwd = pbuf; 75398247Smikeh } 75498247Smikeh e = _ftp_cmd(cd, "PASS %s", pwd); 75598247Smikeh } 75698247Smikeh 75798247Smikeh return e; 75898247Smikeh} 75998247Smikeh 76098247Smikeh/* 76198247Smikeh * Log on to FTP server 76298247Smikeh */ 76398247Smikehstatic int 76498247Smikeh_ftp_connect(struct url *url, struct url *purl, const char *flags) 76598247Smikeh{ 76698247Smikeh int cd, e, direct, verbose; 76798247Smikeh#ifdef INET6 76898247Smikeh int af = AF_UNSPEC; 76998247Smikeh#else 77098247Smikeh int af = AF_INET; 77198247Smikeh#endif 77298247Smikeh 77398247Smikeh direct = CHECK_FLAG('d'); 77498247Smikeh verbose = CHECK_FLAG('v'); 77598247Smikeh if (CHECK_FLAG('4')) 77698247Smikeh af = AF_INET; 77798247Smikeh else if (CHECK_FLAG('6')) 77898247Smikeh af = AF_INET6; 77998247Smikeh 78098247Smikeh if (direct) 78198247Smikeh purl = NULL; 78298247Smikeh 78398247Smikeh /* check for proxy */ 78498247Smikeh if (purl) { 78598247Smikeh /* XXX proxy authentication! */ 78698247Smikeh cd = _fetch_connect(purl->host, purl->port, af, verbose); 78798247Smikeh } else { 78898247Smikeh /* no proxy, go straight to target */ 78998247Smikeh cd = _fetch_connect(url->host, url->port, af, verbose); 79098247Smikeh purl = NULL; 79198247Smikeh } 79298247Smikeh 79398247Smikeh /* check connection */ 79498247Smikeh if (cd == -1) { 79598247Smikeh _fetch_syserr(); 79698247Smikeh return NULL; 79798247Smikeh } 79898247Smikeh 79998247Smikeh /* expect welcome message */ 80098247Smikeh if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 80198247Smikeh goto fouch; 80298247Smikeh 80398247Smikeh /* authenticate */ 80498247Smikeh if ((e = _ftp_authenticate(cd, url, purl)) != FTP_LOGGED_IN) 80598247Smikeh goto fouch; 80698247Smikeh 80798247Smikeh /* might as well select mode and type at once */ 80898247Smikeh#ifdef FTP_FORCE_STREAM_MODE 80998247Smikeh if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 81098247Smikeh goto fouch; 81198247Smikeh#endif 81298247Smikeh if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 81398247Smikeh goto fouch; 81498247Smikeh 81598247Smikeh /* done */ 81698247Smikeh return cd; 81798247Smikeh 81898247Smikehfouch: 81998247Smikeh if (e != -1) 82098247Smikeh _ftp_seterr(e); 82198247Smikeh close(cd); 82298247Smikeh return NULL; 82398247Smikeh} 82498247Smikeh 82598247Smikeh/* 82698247Smikeh * Disconnect from server 82798247Smikeh */ 82898247Smikehstatic void 82998247Smikeh_ftp_disconnect(int cd) 83098247Smikeh{ 83198247Smikeh (void)_ftp_cmd(cd, "QUIT"); 83298247Smikeh close(cd); 83398247Smikeh} 83498247Smikeh 83598247Smikeh/* 83698247Smikeh * Check if we're already connected 83798247Smikeh */ 83898247Smikehstatic int 83998247Smikeh_ftp_isconnected(struct url *url) 84098247Smikeh{ 84198247Smikeh return (cached_socket 84298247Smikeh && (strcmp(url->host, cached_host.host) == 0) 84398247Smikeh && (strcmp(url->user, cached_host.user) == 0) 84498247Smikeh && (strcmp(url->pwd, cached_host.pwd) == 0) 84598247Smikeh && (url->port == cached_host.port)); 84698247Smikeh} 84798247Smikeh 84898247Smikeh/* 84998247Smikeh * Check the cache, reconnect if no luck 85098247Smikeh */ 85198247Smikehstatic int 85298247Smikeh_ftp_cached_connect(struct url *url, struct url *purl, const char *flags) 85398247Smikeh{ 85498247Smikeh int e, cd; 85598247Smikeh 85698247Smikeh cd = -1; 85798247Smikeh 85898247Smikeh /* set default port */ 85998247Smikeh if (!url->port) 86098247Smikeh url->port = _fetch_default_port(url->scheme); 86198247Smikeh 86298247Smikeh /* try to use previously cached connection */ 86398247Smikeh if (_ftp_isconnected(url)) { 86498247Smikeh e = _ftp_cmd(cached_socket, "NOOP"); 86598247Smikeh if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 86698247Smikeh return cached_socket; 86798247Smikeh } 86898247Smikeh 86998247Smikeh /* connect to server */ 87098247Smikeh if ((cd = _ftp_connect(url, purl, flags)) == -1) 87198247Smikeh return -1; 87298247Smikeh if (cached_socket) 87398247Smikeh _ftp_disconnect(cached_socket); 87498247Smikeh cached_socket = cd; 87598247Smikeh memcpy(&cached_host, url, sizeof *url); 87698247Smikeh return cd; 87798247Smikeh} 87898247Smikeh 87998247Smikeh/* 88098247Smikeh * Check the proxy settings 88198247Smikeh */ 88298247Smikehstatic struct url * 88398247Smikeh_ftp_get_proxy(void) 88498247Smikeh{ 88598247Smikeh struct url *purl; 88698247Smikeh char *p; 88798247Smikeh 88898247Smikeh if (((p = getenv("FTP_PROXY")) || (p = getenv("ftp_proxy")) || 88998247Smikeh (p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) && 89098247Smikeh *p && (purl = fetchParseURL(p)) != NULL) { 89198247Smikeh if (!*purl->scheme) { 89298247Smikeh if (getenv("FTP_PROXY") || getenv("ftp_proxy")) 89398247Smikeh strcpy(purl->scheme, SCHEME_FTP); 89498247Smikeh else 89598247Smikeh strcpy(purl->scheme, SCHEME_HTTP); 89698247Smikeh } 89798247Smikeh if (!purl->port) 89898247Smikeh purl->port = _fetch_default_proxy_port(purl->scheme); 89998247Smikeh if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 || 90098247Smikeh strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 90198247Smikeh return purl; 90298247Smikeh fetchFreeURL(purl); 90398247Smikeh } 90498247Smikeh return NULL; 90598247Smikeh} 90698247Smikeh 90798247Smikeh/* 90898247Smikeh * Process an FTP request 90998247Smikeh */ 91098247SmikehFILE * 91198247Smikeh_ftp_request(struct url *url, const char *op, struct url_stat *us, 91298247Smikeh struct url *purl, const char *flags) 91398247Smikeh{ 91498247Smikeh int cd; 91598247Smikeh 91698247Smikeh /* check if we should use HTTP instead */ 91779971Sobrien if (purl && strcasecmp(purl->scheme, SCHEME_HTTP) == 0) { 91879971Sobrien if (strcmp(op, "STAT") == 0) 91979971Sobrien return _http_request(url, "HEAD", us, purl, flags); 92079971Sobrien else if (strcmp(op, "RETR") == 0) 92179971Sobrien return _http_request(url, "GET", us, purl, flags); 92279971Sobrien /* 92379971Sobrien * Our HTTP code doesn't support PUT requests yet, so try a 92479971Sobrien * direct connection. 92579971Sobrien */ 92679971Sobrien } 92779971Sobrien 92879971Sobrien /* connect to server */ 92979971Sobrien cd = _ftp_cached_connect(url, purl, flags); 93079971Sobrien if (purl) 93179971Sobrien fetchFreeURL(purl); 93279971Sobrien if (cd == NULL) 93379971Sobrien return NULL; 93479971Sobrien 93579971Sobrien /* change directory */ 936223328Sgavin if (_ftp_cwd(cd, url->doc) == -1) 93779971Sobrien return NULL; 93879971Sobrien 93979971Sobrien /* stat file */ 94079971Sobrien if (us && _ftp_stat(cd, url->doc, us) == -1 94179971Sobrien && fetchLastErrCode != FETCH_PROTO 94279971Sobrien && fetchLastErrCode != FETCH_UNAVAIL) 94379971Sobrien return NULL; 94479971Sobrien 94579971Sobrien /* just a stat */ 94679971Sobrien if (strcmp(op, "STAT") == 0) 94779971Sobrien return (FILE *)1; /* bogus return value */ 94879971Sobrien 94979971Sobrien /* initiate the transfer */ 95079971Sobrien return _ftp_transfer(cd, op, url->doc, O_RDONLY, url->offset, flags); 95179971Sobrien} 95279971Sobrien 95379971Sobrien/* 95479971Sobrien * Get and stat file 95579971Sobrien */ 956223328SgavinFILE * 95779971SobrienfetchXGetFTP(struct url *url, struct url_stat *us, const char *flags) 95879971Sobrien{ 95979971Sobrien return _ftp_request(url, "RETR", us, _ftp_get_proxy(), flags); 96079971Sobrien} 96179971Sobrien 96279971Sobrien/* 96379971Sobrien * Get file 96479971Sobrien */ 96579971SobrienFILE * 96679971SobrienfetchGetFTP(struct url *url, const char *flags) 96779971Sobrien{ 96879971Sobrien return fetchXGetFTP(url, NULL, flags); 96979971Sobrien} 97079971Sobrien 97179971Sobrien/* 97279971Sobrien * Put file 97379971Sobrien */ 97479971SobrienFILE * 97579971SobrienfetchPutFTP(struct url *url, const char *flags) 97679971Sobrien{ 97779971Sobrien 97879971Sobrien return _ftp_request(url, CHECK_FLAG('a') ? "APPE" : "STOR", NULL, 97979971Sobrien _ftp_get_proxy(), flags); 98079971Sobrien} 98179971Sobrien 98279971Sobrien/* 98379971Sobrien * Get file stats 98479971Sobrien */ 98579971Sobrienint 98679971SobrienfetchStatFTP(struct url *url, struct url_stat *us, const char *flags) 98779971Sobrien{ 98879971Sobrien 98979971Sobrien if (_ftp_request(url, "STAT", us, _ftp_get_proxy(), flags) == NULL) 99079971Sobrien return -1; 99179971Sobrien return 0; 99279971Sobrien} 99379971Sobrien 99479971Sobrien/* 99579971Sobrien * List a directory 99679971Sobrien */ 99779971Sobrienstruct url_ent * 99879971SobrienfetchListFTP(struct url *url __unused, const char *flags __unused) 99979971Sobrien{ 100079971Sobrien warnx("fetchListFTP(): not implemented"); 100179971Sobrien return NULL; 100279971Sobrien} 100379971Sobrien