ftp.c revision 69043
1189251Ssam/*- 2189251Ssam * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav 3214734Srpaulo * All rights reserved. 4189251Ssam * 5189251Ssam * Redistribution and use in source and binary forms, with or without 6189251Ssam * modification, are permitted provided that the following conditions 7189251Ssam * are met: 8189251Ssam * 1. Redistributions of source code must retain the above copyright 9189251Ssam * notice, this list of conditions and the following disclaimer 10189251Ssam * in this position and unchanged. 11189251Ssam * 2. Redistributions in binary form must reproduce the above copyright 12189251Ssam * notice, this list of conditions and the following disclaimer in the 13189251Ssam * documentation and/or other materials provided with the distribution. 14189251Ssam * 3. The name of the author may not be used to endorse or promote products 15189251Ssam * derived from this software without specific prior written permission 16189251Ssam * 17189251Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18214734Srpaulo * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19214734Srpaulo * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20189251Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21189251Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22189251Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23189251Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24189251Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25189251Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26189251Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27189251Ssam * 28189251Ssam * $FreeBSD: head/lib/libfetch/ftp.c 69043 2000-11-22 14:44:48Z des $ 29189251Ssam */ 30189251Ssam 31189251Ssam/* 32189251Ssam * Portions of this code were taken from or based on ftpio.c: 33189251Ssam * 34189251Ssam * ---------------------------------------------------------------------------- 35189251Ssam * "THE BEER-WARE LICENSE" (Revision 42): 36189251Ssam * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you 37189251Ssam * can do whatever you want with this stuff. If we meet some day, and you think 38189251Ssam * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 39189251Ssam * ---------------------------------------------------------------------------- 40189251Ssam * 41189251Ssam * Major Changelog: 42189251Ssam * 43189251Ssam * Dag-Erling Co�dan Sm�rgrav 44189251Ssam * 9 Jun 1998 45189251Ssam * 46189251Ssam * Incorporated into libfetch 47189251Ssam * 48209158Srpaulo * Jordan K. Hubbard 49209158Srpaulo * 17 Jan 1996 50209158Srpaulo * 51209158Srpaulo * Turned inside out. Now returns xfers as new file ids, not as a special 52209158Srpaulo * `state' of FTP_t 53209158Srpaulo * 54209158Srpaulo * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $ 55209158Srpaulo * 56209158Srpaulo */ 57209158Srpaulo 58209158Srpaulo#include <sys/param.h> 59209158Srpaulo#include <sys/socket.h> 60189251Ssam#include <netinet/in.h> 61189251Ssam 62189251Ssam#include <ctype.h> 63189251Ssam#include <errno.h> 64189251Ssam#include <fcntl.h> 65189251Ssam#include <netdb.h> 66189251Ssam#include <stdarg.h> 67189251Ssam#include <stdio.h> 68189251Ssam#include <stdlib.h> 69189251Ssam#include <string.h> 70189251Ssam#include <time.h> 71189251Ssam#include <unistd.h> 72189251Ssam 73189251Ssam#include "fetch.h" 74189251Ssam#include "common.h" 75189251Ssam#include "ftperr.h" 76189251Ssam 77209158Srpaulo#define FTP_ANONYMOUS_USER "ftp" 78189251Ssam#define FTP_ANONYMOUS_PASSWORD "ftp" 79189251Ssam 80189251Ssam#define FTP_CONNECTION_ALREADY_OPEN 125 81189251Ssam#define FTP_OPEN_DATA_CONNECTION 150 82189251Ssam#define FTP_OK 200 83189251Ssam#define FTP_FILE_STATUS 213 84189251Ssam#define FTP_SERVICE_READY 220 85189251Ssam#define FTP_TRANSFER_COMPLETE 226 86189251Ssam#define FTP_PASSIVE_MODE 227 87189251Ssam#define FTP_LPASSIVE_MODE 228 88189251Ssam#define FTP_EPASSIVE_MODE 229 89189251Ssam#define FTP_LOGGED_IN 230 90189251Ssam#define FTP_FILE_ACTION_OK 250 91189251Ssam#define FTP_NEED_PASSWORD 331 92189251Ssam#define FTP_NEED_ACCOUNT 332 93189251Ssam#define FTP_FILE_OK 350 94189251Ssam#define FTP_SYNTAX_ERROR 500 95189251Ssam#define FTP_PROTOCOL_ERROR 999 96189251Ssam 97189251Ssamstatic struct url cached_host; 98209158Srpaulostatic int cached_socket; 99189251Ssam 100189251Ssamstatic char *last_reply; 101189251Ssamstatic size_t lr_size, lr_length; 102189251Ssamstatic int last_code; 103189251Ssam 104189251Ssam#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 105189251Ssam && isdigit(foo[2]) \ 106189251Ssam && (foo[3] == ' ' || foo[3] == '\0')) 107189251Ssam#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 108189251Ssam && isdigit(foo[2]) && foo[3] == '-') 109189251Ssam 110189251Ssam/* translate IPv4 mapped IPv6 address to IPv4 address */ 111189251Ssamstatic void 112189251Ssamunmappedaddr(struct sockaddr_in6 *sin6) 113189251Ssam{ 114189251Ssam struct sockaddr_in *sin4; 115189251Ssam u_int32_t addr; 116189251Ssam int port; 117189251Ssam 118189251Ssam if (sin6->sin6_family != AF_INET6 || 119189251Ssam !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 120189251Ssam return; 121189251Ssam sin4 = (struct sockaddr_in *)sin6; 122189251Ssam addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 123189251Ssam port = sin6->sin6_port; 124189251Ssam memset(sin4, 0, sizeof(struct sockaddr_in)); 125189251Ssam sin4->sin_addr.s_addr = addr; 126189251Ssam sin4->sin_port = port; 127189251Ssam sin4->sin_family = AF_INET; 128189251Ssam sin4->sin_len = sizeof(struct sockaddr_in); 129189251Ssam} 130189251Ssam 131189251Ssam/* 132189251Ssam * Get server response 133189251Ssam */ 134189251Ssamstatic int 135189251Ssam_ftp_chkerr(int cd) 136189251Ssam{ 137189251Ssam if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 138189251Ssam _fetch_syserr(); 139189251Ssam return -1; 140189251Ssam } 141189251Ssam if (isftpinfo(last_reply)) { 142189251Ssam while (!isftpreply(last_reply)) { 143189251Ssam if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 144189251Ssam _fetch_syserr(); 145189251Ssam return -1; 146189251Ssam } 147189251Ssam } 148189251Ssam } 149189251Ssam 150189251Ssam while (lr_length && isspace(last_reply[lr_length-1])) 151189251Ssam lr_length--; 152189251Ssam last_reply[lr_length] = 0; 153189251Ssam 154189251Ssam if (!isftpreply(last_reply)) { 155189251Ssam _ftp_seterr(FTP_PROTOCOL_ERROR); 156189251Ssam return -1; 157189251Ssam } 158189251Ssam 159189251Ssam last_code = (last_reply[0] - '0') * 100 160189251Ssam + (last_reply[1] - '0') * 10 161189251Ssam + (last_reply[2] - '0'); 162189251Ssam 163189251Ssam return last_code; 164189251Ssam} 165189251Ssam 166189251Ssam/* 167189251Ssam * Send a command and check reply 168189251Ssam */ 169189251Ssamstatic int 170189251Ssam_ftp_cmd(int cd, char *fmt, ...) 171189251Ssam{ 172214734Srpaulo va_list ap; 173214734Srpaulo size_t len; 174189251Ssam char *msg; 175189251Ssam int r; 176189251Ssam 177189251Ssam va_start(ap, fmt); 178214734Srpaulo len = vasprintf(&msg, fmt, ap); 179214734Srpaulo va_end(ap); 180189251Ssam 181189251Ssam if (msg == NULL) { 182189251Ssam errno = ENOMEM; 183189251Ssam _fetch_syserr(); 184189251Ssam return -1; 185189251Ssam } 186189251Ssam 187189251Ssam r = _fetch_putln(cd, msg, len); 188189251Ssam free(msg); 189189251Ssam 190189251Ssam if (r == -1) { 191189251Ssam _fetch_syserr(); 192189251Ssam return -1; 193189251Ssam } 194189251Ssam 195189251Ssam return _ftp_chkerr(cd); 196189251Ssam} 197189251Ssam 198189251Ssam/* 199189251Ssam * Return a pointer to the filename part of a path 200189251Ssam */ 201189251Ssamstatic char * 202189251Ssam_ftp_filename(char *file) 203189251Ssam{ 204189251Ssam char *s; 205189251Ssam 206189251Ssam if ((s = strrchr(file, '/')) == NULL) 207189251Ssam return file; 208189251Ssam else 209189251Ssam return s + 1; 210189251Ssam} 211189251Ssam 212189251Ssam/* 213189251Ssam * Change working directory to the directory that contains the 214189251Ssam * specified file. 215189251Ssam */ 216189251Ssamstatic int 217189251Ssam_ftp_cwd(int cd, char *file) 218189251Ssam{ 219189251Ssam char *s; 220189251Ssam int e; 221189251Ssam 222189251Ssam if ((s = strrchr(file, '/')) == NULL || s == file) { 223189251Ssam e = _ftp_cmd(cd, "CWD /"); 224189251Ssam } else { 225189251Ssam e = _ftp_cmd(cd, "CWD %.*s", s - file, file); 226189251Ssam } 227189251Ssam if (e != FTP_FILE_ACTION_OK) { 228189251Ssam _ftp_seterr(e); 229189251Ssam return -1; 230189251Ssam } 231189251Ssam return 0; 232189251Ssam} 233189251Ssam 234189251Ssam/* 235189251Ssam * Request and parse file stats 236189251Ssam */ 237189251Ssamstatic int 238189251Ssam_ftp_stat(int cd, char *file, struct url_stat *us) 239189251Ssam{ 240189251Ssam char *ln, *s; 241189251Ssam struct tm tm; 242189251Ssam time_t t; 243189251Ssam int e; 244189251Ssam 245189251Ssam us->size = -1; 246189251Ssam us->atime = us->mtime = 0; 247189251Ssam 248189251Ssam if ((s = strrchr(file, '/')) == NULL) 249189251Ssam s = file; 250189251Ssam else 251189251Ssam ++s; 252189251Ssam 253189251Ssam if ((e = _ftp_cmd(cd, "SIZE %s", s)) != FTP_FILE_STATUS) { 254189251Ssam _ftp_seterr(e); 255189251Ssam return -1; 256189251Ssam } 257189251Ssam for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 258189251Ssam /* nothing */ ; 259189251Ssam for (us->size = 0; *ln && isdigit(*ln); ln++) 260189251Ssam us->size = us->size * 10 + *ln - '0'; 261189251Ssam if (*ln && !isspace(*ln)) { 262189251Ssam _ftp_seterr(FTP_PROTOCOL_ERROR); 263189251Ssam return -1; 264189251Ssam } 265189251Ssam if (us->size == 0) 266189251Ssam us->size = -1; 267189251Ssam DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size)); 268189251Ssam 269189251Ssam if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) { 270189251Ssam _ftp_seterr(e); 271189251Ssam return -1; 272189251Ssam } 273189251Ssam for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 274189251Ssam /* nothing */ ; 275189251Ssam switch (strspn(ln, "0123456789")) { 276189251Ssam case 14: 277189251Ssam break; 278189251Ssam case 15: 279189251Ssam ln++; 280189251Ssam ln[0] = '2'; 281189251Ssam ln[1] = '0'; 282189251Ssam break; 283189251Ssam default: 284189251Ssam _ftp_seterr(FTP_PROTOCOL_ERROR); 285189251Ssam return -1; 286189251Ssam } 287189251Ssam if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 288189251Ssam &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 289189251Ssam &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) { 290189251Ssam _ftp_seterr(FTP_PROTOCOL_ERROR); 291189251Ssam return -1; 292189251Ssam } 293189251Ssam tm.tm_mon--; 294189251Ssam tm.tm_year -= 1900; 295189251Ssam tm.tm_isdst = -1; 296189251Ssam t = timegm(&tm); 297189251Ssam if (t == (time_t)-1) 298189251Ssam t = time(NULL); 299189251Ssam us->mtime = t; 300189251Ssam us->atime = t; 301189251Ssam DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " 302189251Ssam "%02d:%02d:%02d\033[m]\n", 303189251Ssam tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 304189251Ssam tm.tm_hour, tm.tm_min, tm.tm_sec)); 305189251Ssam return 0; 306189251Ssam} 307189251Ssam 308189251Ssam/* 309189251Ssam * I/O functions for FTP 310189251Ssam */ 311189251Ssamstruct ftpio { 312189251Ssam int csd; /* Control socket descriptor */ 313189251Ssam int dsd; /* Data socket descriptor */ 314189251Ssam int dir; /* Direction */ 315189251Ssam int eof; /* EOF reached */ 316189251Ssam int err; /* Error code */ 317189251Ssam}; 318189251Ssam 319189251Ssamstatic int _ftp_readfn(void *, char *, int); 320189251Ssamstatic int _ftp_writefn(void *, const char *, int); 321214734Srpaulostatic fpos_t _ftp_seekfn(void *, fpos_t, int); 322189251Ssamstatic int _ftp_closefn(void *); 323214734Srpaulo 324189251Ssamstatic int 325214734Srpaulo_ftp_readfn(void *v, char *buf, int len) 326214734Srpaulo{ 327214734Srpaulo struct ftpio *io; 328214734Srpaulo int r; 329189251Ssam 330189251Ssam io = (struct ftpio *)v; 331189251Ssam if (io == NULL) { 332189251Ssam errno = EBADF; 333214734Srpaulo return -1; 334189251Ssam } 335189251Ssam if (io->csd == -1 || io->dsd == -1 || io->dir == O_WRONLY) { 336189251Ssam errno = EBADF; 337189251Ssam return -1; 338189251Ssam } 339214734Srpaulo if (io->err) { 340189251Ssam errno = io->err; 341189251Ssam return -1; 342189251Ssam } 343189251Ssam if (io->eof) 344189251Ssam return 0; 345189251Ssam r = read(io->dsd, buf, len); 346189251Ssam if (r > 0) 347189251Ssam return r; 348189251Ssam if (r == 0) { 349189251Ssam io->eof = 1; 350189251Ssam return _ftp_closefn(v); 351189251Ssam } 352189251Ssam io->err = errno; 353189251Ssam return -1; 354189251Ssam} 355189251Ssam 356189251Ssamstatic int 357189251Ssam_ftp_writefn(void *v, const char *buf, int len) 358214734Srpaulo{ 359189251Ssam struct ftpio *io; 360189251Ssam int w; 361189251Ssam 362189251Ssam io = (struct ftpio *)v; 363189251Ssam if (io == NULL) { 364214734Srpaulo errno = EBADF; 365189251Ssam return -1; 366189251Ssam } 367189251Ssam if (io->csd == -1 || io->dsd == -1 || io->dir == O_RDONLY) { 368189251Ssam errno = EBADF; 369189251Ssam return -1; 370189251Ssam } 371189251Ssam if (io->err) { 372189251Ssam errno = io->err; 373189251Ssam return -1; 374189251Ssam } 375189251Ssam w = write(io->dsd, buf, len); 376189251Ssam if (w >= 0) 377189251Ssam return w; 378189251Ssam io->err = errno; 379189251Ssam return -1; 380189251Ssam} 381189251Ssam 382189251Ssamstatic fpos_t 383189251Ssam_ftp_seekfn(void *v, fpos_t pos, int whence) 384189251Ssam{ 385189251Ssam struct ftpio *io; 386189251Ssam 387189251Ssam io = (struct ftpio *)v; 388189251Ssam if (io == NULL) { 389214734Srpaulo errno = EBADF; 390214734Srpaulo return -1; 391214734Srpaulo } 392189251Ssam errno = ESPIPE; 393189251Ssam return -1; 394189251Ssam} 395214734Srpaulo 396189251Ssamstatic int 397214734Srpaulo_ftp_closefn(void *v) 398189251Ssam{ 399189251Ssam struct ftpio *io; 400189251Ssam int r; 401189251Ssam 402189251Ssam io = (struct ftpio *)v; 403189251Ssam if (io == NULL) { 404189251Ssam errno = EBADF; 405189251Ssam return -1; 406189251Ssam } 407189251Ssam if (io->dir == -1) 408214734Srpaulo return 0; 409189251Ssam if (io->csd == -1 || io->dsd == -1) { 410189251Ssam errno = EBADF; 411189251Ssam return -1; 412189251Ssam } 413189251Ssam close(io->dsd); 414189251Ssam io->dir = -1; 415189251Ssam io->dsd = -1; 416189251Ssam DEBUG(fprintf(stderr, "Waiting for final status\n")); 417189251Ssam if ((r = _ftp_chkerr(io->csd)) != FTP_TRANSFER_COMPLETE) 418189251Ssam io->err = r; 419189251Ssam else 420189251Ssam io->err = 0; 421189251Ssam close(io->csd); 422189251Ssam io->csd = -1; 423189251Ssam return io->err ? -1 : 0; 424189251Ssam} 425189251Ssam 426189251Ssamstatic FILE * 427189251Ssam_ftp_setup(int csd, int dsd, int mode) 428189251Ssam{ 429189251Ssam struct ftpio *io; 430189251Ssam FILE *f; 431214734Srpaulo 432189251Ssam if ((io = malloc(sizeof *io)) == NULL) 433214734Srpaulo return NULL; 434214734Srpaulo io->csd = dup(csd); 435189251Ssam io->dsd = dsd; 436214734Srpaulo io->dir = mode; 437214734Srpaulo io->eof = io->err = 0; 438189251Ssam f = funopen(io, _ftp_readfn, _ftp_writefn, _ftp_seekfn, _ftp_closefn); 439189251Ssam if (f == NULL) 440189251Ssam free(io); 441189251Ssam return f; 442189251Ssam} 443189251Ssam 444189251Ssam/* 445189251Ssam * Transfer file 446214734Srpaulo */ 447189251Ssamstatic FILE * 448189251Ssam_ftp_transfer(int cd, char *oper, char *file, 449189251Ssam int mode, off_t offset, char *flags) 450189251Ssam{ 451214734Srpaulo struct sockaddr_storage sin; 452189251Ssam struct sockaddr_in6 *sin6; 453189251Ssam struct sockaddr_in *sin4; 454189251Ssam int pasv, high, verbose; 455189251Ssam int e, sd = -1; 456189251Ssam socklen_t l; 457189251Ssam char *s; 458214734Srpaulo FILE *df; 459214734Srpaulo 460214734Srpaulo /* check flags */ 461189251Ssam pasv = CHECK_FLAG('p'); 462189251Ssam high = CHECK_FLAG('h'); 463189251Ssam verbose = CHECK_FLAG('v'); 464214734Srpaulo 465189251Ssam /* passive mode */ 466189251Ssam if (!pasv) 467189251Ssam pasv = ((s = getenv("FTP_PASSIVE_MODE")) == NULL || 468189251Ssam strncasecmp(s, "no", 2) != 0); 469189251Ssam 470189251Ssam /* find our own address, bind, and listen */ 471189251Ssam l = sizeof sin; 472189251Ssam if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) 473189251Ssam goto sysouch; 474189251Ssam if (sin.ss_family == AF_INET6) 475189251Ssam unmappedaddr((struct sockaddr_in6 *)&sin); 476189251Ssam 477189251Ssam /* open data socket */ 478189251Ssam if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 479189251Ssam _fetch_syserr(); 480189251Ssam return NULL; 481189251Ssam } 482189251Ssam 483189251Ssam if (pasv) { 484189251Ssam u_char addr[64]; 485189251Ssam char *ln, *p; 486189251Ssam int i; 487214734Srpaulo int port; 488214734Srpaulo 489214734Srpaulo /* send PASV command */ 490214734Srpaulo if (verbose) 491189251Ssam _fetch_info("setting passive mode"); 492189251Ssam switch (sin.ss_family) { 493214734Srpaulo case AF_INET: 494214734Srpaulo if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 495189251Ssam goto ouch; 496189251Ssam break; 497189251Ssam case AF_INET6: 498189251Ssam if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { 499189251Ssam if (e == -1) 500189251Ssam goto ouch; 501189251Ssam if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) 502189251Ssam goto ouch; 503189251Ssam } 504189251Ssam break; 505189251Ssam default: 506189251Ssam e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 507189251Ssam goto ouch; 508189251Ssam } 509214734Srpaulo 510189251Ssam /* 511189251Ssam * Find address and port number. The reply to the PASV command 512189251Ssam * is IMHO the one and only weak point in the FTP protocol. 513189251Ssam */ 514189251Ssam ln = last_reply; 515189251Ssam switch (e) { 516189251Ssam case FTP_PASSIVE_MODE: 517189251Ssam case FTP_LPASSIVE_MODE: 518189251Ssam for (p = ln + 3; *p && !isdigit(*p); p++) 519189251Ssam /* nothing */ ; 520189251Ssam if (!*p) { 521189251Ssam e = FTP_PROTOCOL_ERROR; 522189251Ssam goto ouch; 523189251Ssam } 524189251Ssam l = (e == FTP_PASSIVE_MODE ? 6 : 21); 525189251Ssam for (i = 0; *p && i < l; i++, p++) 526189251Ssam addr[i] = strtol(p, &p, 10); 527189251Ssam if (i < l) { 528189251Ssam e = FTP_PROTOCOL_ERROR; 529189251Ssam goto ouch; 530189251Ssam } 531189251Ssam break; 532189251Ssam case FTP_EPASSIVE_MODE: 533189251Ssam for (p = ln + 3; *p && *p != '('; p++) 534189251Ssam /* nothing */ ; 535214734Srpaulo if (!*p) { 536189251Ssam e = FTP_PROTOCOL_ERROR; 537189251Ssam goto ouch; 538214734Srpaulo } 539214734Srpaulo ++p; 540214734Srpaulo if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 541189251Ssam &port, &addr[3]) != 5 || 542189251Ssam addr[0] != addr[1] || 543189251Ssam addr[0] != addr[2] || addr[0] != addr[3]) { 544189251Ssam e = FTP_PROTOCOL_ERROR; 545189251Ssam goto ouch; 546189251Ssam } 547189251Ssam break; 548189251Ssam } 549189251Ssam 550189251Ssam /* seek to required offset */ 551189251Ssam if (offset) 552189251Ssam if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 553189251Ssam goto sysouch; 554189251Ssam 555189251Ssam /* construct sockaddr for data socket */ 556189251Ssam l = sizeof sin; 557189251Ssam if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 558189251Ssam goto sysouch; 559189251Ssam if (sin.ss_family == AF_INET6) 560189251Ssam unmappedaddr((struct sockaddr_in6 *)&sin); 561189251Ssam switch (sin.ss_family) { 562189251Ssam case AF_INET6: 563189251Ssam sin6 = (struct sockaddr_in6 *)&sin; 564189251Ssam if (e == FTP_EPASSIVE_MODE) 565189251Ssam sin6->sin6_port = htons(port); 566189251Ssam else { 567189251Ssam bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 568189251Ssam bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 569189251Ssam } 570189251Ssam break; 571189251Ssam case AF_INET: 572189251Ssam sin4 = (struct sockaddr_in *)&sin; 573189251Ssam if (e == FTP_EPASSIVE_MODE) 574189251Ssam sin4->sin_port = htons(port); 575189251Ssam else { 576189251Ssam bcopy(addr, (char *)&sin4->sin_addr, 4); 577189251Ssam bcopy(addr + 4, (char *)&sin4->sin_port, 2); 578189251Ssam } 579189251Ssam break; 580189251Ssam default: 581189251Ssam e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 582189251Ssam break; 583189251Ssam } 584189251Ssam 585189251Ssam /* connect to data port */ 586189251Ssam if (verbose) 587189251Ssam _fetch_info("opening data connection"); 588214734Srpaulo if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 589189251Ssam goto sysouch; 590189251Ssam 591189251Ssam /* make the server initiate the transfer */ 592189251Ssam if (verbose) 593189251Ssam _fetch_info("initiating transfer"); 594214734Srpaulo e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 595189251Ssam if (e != FTP_CONNECTION_ALREADY_OPEN && e != FTP_OPEN_DATA_CONNECTION) 596189251Ssam goto ouch; 597189251Ssam 598189251Ssam } else { 599189251Ssam u_int32_t a; 600189251Ssam u_short p; 601189251Ssam int arg, d; 602189251Ssam char *ap; 603189251Ssam char hname[INET6_ADDRSTRLEN]; 604189251Ssam 605189251Ssam switch (sin.ss_family) { 606189251Ssam case AF_INET6: 607189251Ssam ((struct sockaddr_in6 *)&sin)->sin6_port = 0; 608189251Ssam#ifdef IPV6_PORTRANGE 609189251Ssam arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT; 610189251Ssam if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 611189251Ssam (char *)&arg, sizeof(arg)) == -1) 612189251Ssam goto sysouch; 613189251Ssam#endif 614189251Ssam break; 615189251Ssam case AF_INET: 616189251Ssam ((struct sockaddr_in *)&sin)->sin_port = 0; 617189251Ssam arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; 618189251Ssam if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 619189251Ssam (char *)&arg, sizeof arg) == -1) 620189251Ssam goto sysouch; 621189251Ssam break; 622189251Ssam } 623189251Ssam if (verbose) 624189251Ssam _fetch_info("binding data socket"); 625189251Ssam if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 626189251Ssam goto sysouch; 627189251Ssam if (listen(sd, 1) == -1) 628189251Ssam goto sysouch; 629189251Ssam 630189251Ssam /* find what port we're on and tell the server */ 631189251Ssam if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 632189251Ssam goto sysouch; 633214734Srpaulo switch (sin.ss_family) { 634189251Ssam case AF_INET: 635189251Ssam sin4 = (struct sockaddr_in *)&sin; 636189251Ssam a = ntohl(sin4->sin_addr.s_addr); 637189251Ssam p = ntohs(sin4->sin_port); 638189251Ssam e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 639189251Ssam (a >> 24) & 0xff, (a >> 16) & 0xff, 640214734Srpaulo (a >> 8) & 0xff, a & 0xff, 641189251Ssam (p >> 8) & 0xff, p & 0xff); 642189251Ssam break; 643189251Ssam case AF_INET6: 644189251Ssam#define UC(b) (((int)b)&0xff) 645189251Ssam e = -1; 646189251Ssam sin6 = (struct sockaddr_in6 *)&sin; 647189251Ssam if (getnameinfo((struct sockaddr *)&sin, sin.ss_len, 648189251Ssam hname, sizeof(hname), 649189251Ssam NULL, 0, NI_NUMERICHOST) == 0) { 650189251Ssam e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 651189251Ssam htons(sin6->sin6_port)); 652189251Ssam if (e == -1) 653189251Ssam goto ouch; 654189251Ssam } 655189251Ssam if (e != FTP_OK) { 656189251Ssam ap = (char *)&sin6->sin6_addr; 657189251Ssam e = _ftp_cmd(cd, 658189251Ssam "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 659189251Ssam 6, 16, 660189251Ssam UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 661189251Ssam UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 662189251Ssam UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 663189251Ssam UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 664189251Ssam 2, 665189251Ssam (ntohs(sin6->sin6_port) >> 8) & 0xff, 666189251Ssam ntohs(sin6->sin6_port) & 0xff); 667189251Ssam } 668189251Ssam break; 669189251Ssam default: 670189251Ssam e = FTP_PROTOCOL_ERROR; /* XXX: error code should be prepared */ 671189251Ssam goto ouch; 672189251Ssam } 673189251Ssam if (e != FTP_OK) 674189251Ssam goto ouch; 675189251Ssam 676189251Ssam /* seek to required offset */ 677189251Ssam if (offset) 678189251Ssam if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 679189251Ssam goto sysouch; 680189251Ssam 681189251Ssam /* make the server initiate the transfer */ 682189251Ssam if (verbose) 683189251Ssam _fetch_info("initiating transfer"); 684189251Ssam e = _ftp_cmd(cd, "%s %s", oper, _ftp_filename(file)); 685189251Ssam if (e != FTP_OPEN_DATA_CONNECTION) 686189251Ssam goto ouch; 687189251Ssam 688189251Ssam /* accept the incoming connection and go to town */ 689189251Ssam if ((d = accept(sd, NULL, NULL)) == -1) 690189251Ssam goto sysouch; 691189251Ssam close(sd); 692189251Ssam sd = d; 693189251Ssam } 694189251Ssam 695189251Ssam if ((df = _ftp_setup(cd, sd, mode)) == NULL) 696189251Ssam goto sysouch; 697189251Ssam return df; 698189251Ssam 699189251Ssamsysouch: 700189251Ssam _fetch_syserr(); 701189251Ssam if (sd >= 0) 702189251Ssam close(sd); 703189251Ssam return NULL; 704189251Ssam 705189251Ssamouch: 706189251Ssam if (e != -1) 707189251Ssam _ftp_seterr(e); 708189251Ssam if (sd >= 0) 709189251Ssam close(sd); 710189251Ssam return NULL; 711189251Ssam} 712189251Ssam 713189251Ssam/* 714189251Ssam * Log on to FTP server 715189251Ssam */ 716189251Ssamstatic int 717189251Ssam_ftp_connect(struct url *url, struct url *purl, char *flags) 718189251Ssam{ 719189251Ssam int cd, e, direct, verbose; 720189251Ssam#ifdef INET6 721189251Ssam int af = AF_UNSPEC; 722189251Ssam#else 723189251Ssam int af = AF_INET; 724189251Ssam#endif 725189251Ssam const char *logname; 726189251Ssam char *user, *pwd; 727189251Ssam char localhost[MAXHOSTNAMELEN]; 728189251Ssam char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 729189251Ssam 730189251Ssam direct = CHECK_FLAG('d'); 731189251Ssam verbose = CHECK_FLAG('v'); 732189251Ssam if (CHECK_FLAG('4')) 733189251Ssam af = AF_INET; 734189251Ssam else if (CHECK_FLAG('6')) 735189251Ssam af = AF_INET6; 736189251Ssam 737189251Ssam if (direct) 738189251Ssam purl = NULL; 739189251Ssam 740189251Ssam /* check for proxy */ 741189251Ssam if (purl) { 742189251Ssam /* XXX proxy authentication! */ 743189251Ssam cd = _fetch_connect(purl->host, purl->port, af, verbose); 744189251Ssam } else { 745189251Ssam /* no proxy, go straight to target */ 746189251Ssam cd = _fetch_connect(url->host, url->port, af, verbose); 747189251Ssam purl = NULL; 748189251Ssam } 749189251Ssam 750189251Ssam /* check connection */ 751189251Ssam if (cd == -1) { 752189251Ssam _fetch_syserr(); 753189251Ssam return NULL; 754189251Ssam } 755189251Ssam 756189251Ssam /* expect welcome message */ 757189251Ssam if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 758189251Ssam goto fouch; 759189251Ssam 760189251Ssam /* XXX FTP_AUTH, and maybe .netrc */ 761189251Ssam 762189251Ssam /* send user name and password */ 763189251Ssam user = url->user; 764189251Ssam if (!user || !*user) 765189251Ssam user = FTP_ANONYMOUS_USER; 766189251Ssam if (purl && url->port == _fetch_default_port(url->scheme)) 767189251Ssam e = _ftp_cmd(cd, "USER %s@%s", user, url->host); 768189251Ssam else if (purl) 769189251Ssam e = _ftp_cmd(cd, "USER %s@%s@%d", user, url->host, url->port); 770189251Ssam else 771189251Ssam e = _ftp_cmd(cd, "USER %s", user); 772189251Ssam 773189251Ssam /* did the server request a password? */ 774189251Ssam if (e == FTP_NEED_PASSWORD) { 775189251Ssam pwd = url->pwd; 776189251Ssam if (!pwd || !*pwd) 777189251Ssam pwd = getenv("FTP_PASSWORD"); 778189251Ssam if (!pwd || !*pwd) { 779189251Ssam if ((logname = getlogin()) == 0) 780189251Ssam logname = FTP_ANONYMOUS_PASSWORD; 781189251Ssam gethostname(localhost, sizeof localhost); 782189251Ssam snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost); 783189251Ssam pwd = pbuf; 784189251Ssam } 785189251Ssam e = _ftp_cmd(cd, "PASS %s", pwd); 786189251Ssam } 787189251Ssam 788189251Ssam /* did the server request an account? */ 789189251Ssam if (e == FTP_NEED_ACCOUNT) 790189251Ssam goto fouch; 791189251Ssam 792214734Srpaulo /* we should be done by now */ 793189251Ssam if (e != FTP_LOGGED_IN) 794189251Ssam goto fouch; 795189251Ssam 796189251Ssam /* might as well select mode and type at once */ 797189251Ssam#ifdef FTP_FORCE_STREAM_MODE 798189251Ssam if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 799189251Ssam goto fouch; 800189251Ssam#endif 801189251Ssam if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 802189251Ssam goto fouch; 803189251Ssam 804189251Ssam /* done */ 805189251Ssam return cd; 806189251Ssam 807189251Ssamfouch: 808189251Ssam if (e != -1) 809189251Ssam _ftp_seterr(e); 810189251Ssam close(cd); 811189251Ssam return NULL; 812189251Ssam} 813189251Ssam 814189251Ssam/* 815189251Ssam * Disconnect from server 816189251Ssam */ 817189251Ssamstatic void 818214734Srpaulo_ftp_disconnect(int cd) 819214734Srpaulo{ 820189251Ssam (void)_ftp_cmd(cd, "QUIT"); 821189251Ssam close(cd); 822189251Ssam} 823189251Ssam 824189251Ssam/* 825189251Ssam * Check if we're already connected 826189251Ssam */ 827189251Ssamstatic int 828189251Ssam_ftp_isconnected(struct url *url) 829189251Ssam{ 830189251Ssam return (cached_socket 831189251Ssam && (strcmp(url->host, cached_host.host) == 0) 832189251Ssam && (strcmp(url->user, cached_host.user) == 0) 833189251Ssam && (strcmp(url->pwd, cached_host.pwd) == 0) 834214734Srpaulo && (url->port == cached_host.port)); 835189251Ssam} 836189251Ssam 837189251Ssam/* 838189251Ssam * Check the cache, reconnect if no luck 839189251Ssam */ 840189251Ssamstatic int 841189251Ssam_ftp_cached_connect(struct url *url, struct url *purl, char *flags) 842189251Ssam{ 843189251Ssam int e, cd; 844189251Ssam 845189251Ssam cd = -1; 846189251Ssam 847189251Ssam /* set default port */ 848189251Ssam if (!url->port) 849189251Ssam url->port = _fetch_default_port(url->scheme); 850189251Ssam 851214734Srpaulo /* try to use previously cached connection */ 852189251Ssam if (_ftp_isconnected(url)) { 853189251Ssam e = _ftp_cmd(cached_socket, "NOOP"); 854214734Srpaulo if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 855189251Ssam return cached_socket; 856189251Ssam } 857189251Ssam 858214734Srpaulo /* connect to server */ 859214734Srpaulo if ((cd = _ftp_connect(url, purl, flags)) == -1) 860189251Ssam return -1; 861189251Ssam if (cached_socket) 862189251Ssam _ftp_disconnect(cached_socket); 863189251Ssam cached_socket = cd; 864189251Ssam memcpy(&cached_host, url, sizeof *url); 865189251Ssam return cd; 866189251Ssam} 867189251Ssam 868189251Ssam/* 869189251Ssam * Check the proxy settings 870189251Ssam */ 871189251Ssamstatic struct url * 872189251Ssam_ftp_get_proxy(void) 873189251Ssam{ 874189251Ssam struct url *purl; 875189251Ssam char *p; 876189251Ssam 877189251Ssam if (((p = getenv("FTP_PROXY")) || (p = getenv("HTTP_PROXY"))) && 878189251Ssam *p && (purl = fetchParseURL(p)) != NULL) { 879189251Ssam if (!*purl->scheme) 880189251Ssam strcpy(purl->scheme, SCHEME_HTTP); 881189251Ssam if (!purl->port) 882189251Ssam purl->port = _fetch_default_proxy_port(purl->scheme); 883189251Ssam if (strcasecmp(purl->scheme, SCHEME_FTP) == 0 || 884189251Ssam strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 885189251Ssam return purl; 886214734Srpaulo fetchFreeURL(purl); 887214734Srpaulo } 888214734Srpaulo return NULL; 889189251Ssam} 890189251Ssam 891189251Ssam/* 892189251Ssam * Get and stat file 893189251Ssam */ 894189251SsamFILE * 895189251SsamfetchXGetFTP(struct url *url, struct url_stat *us, char *flags) 896189251Ssam{ 897189251Ssam struct url *purl; 898189251Ssam int cd; 899189251Ssam 900189251Ssam /* get the proxy URL, and check if we should use HTTP instead */ 901189251Ssam if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 902189251Ssam if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 903189251Ssam return _http_request(url, "GET", us, purl, flags); 904189251Ssam } else { 905189251Ssam purl = NULL; 906189251Ssam } 907189251Ssam 908189251Ssam /* connect to server */ 909189251Ssam cd = _ftp_cached_connect(url, purl, flags); 910189251Ssam if (purl) 911189251Ssam fetchFreeURL(purl); 912189251Ssam if (cd == NULL) 913189251Ssam return NULL; 914189251Ssam 915189251Ssam /* change directory */ 916189251Ssam if (_ftp_cwd(cd, url->doc) == -1) 917189251Ssam return NULL; 918189251Ssam 919189251Ssam /* stat file */ 920189251Ssam if (us && _ftp_stat(cd, url->doc, us) == -1 921189251Ssam && fetchLastErrCode != FETCH_PROTO 922189251Ssam && fetchLastErrCode != FETCH_UNAVAIL) 923189251Ssam return NULL; 924189251Ssam 925189251Ssam /* initiate the transfer */ 926189251Ssam return _ftp_transfer(cd, "RETR", url->doc, O_RDONLY, url->offset, flags); 927189251Ssam} 928189251Ssam 929189251Ssam/* 930189251Ssam * Get file 931189251Ssam */ 932189251SsamFILE * 933189251SsamfetchGetFTP(struct url *url, char *flags) 934189251Ssam{ 935189251Ssam return fetchXGetFTP(url, NULL, flags); 936189251Ssam} 937189251Ssam 938189251Ssam/* 939189251Ssam * Put file 940189251Ssam */ 941189251SsamFILE * 942189251SsamfetchPutFTP(struct url *url, char *flags) 943189251Ssam{ 944189251Ssam struct url *purl; 945189251Ssam int cd; 946189251Ssam 947189251Ssam /* get the proxy URL, and check if we should use HTTP instead */ 948189251Ssam if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 949189251Ssam if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) 950189251Ssam /* XXX HTTP PUT is not implemented, so try without the proxy */ 951189251Ssam purl = NULL; 952189251Ssam } else { 953189251Ssam purl = NULL; 954189251Ssam } 955189251Ssam 956189251Ssam /* connect to server */ 957189251Ssam cd = _ftp_cached_connect(url, purl, flags); 958189251Ssam if (purl) 959189251Ssam fetchFreeURL(purl); 960189251Ssam if (cd == NULL) 961189251Ssam return NULL; 962189251Ssam 963189251Ssam /* change directory */ 964189251Ssam if (_ftp_cwd(cd, url->doc) == -1) 965189251Ssam return NULL; 966189251Ssam 967189251Ssam /* initiate the transfer */ 968189251Ssam return _ftp_transfer(cd, CHECK_FLAG('a') ? "APPE" : "STOR", 969189251Ssam url->doc, O_WRONLY, url->offset, flags); 970189251Ssam} 971189251Ssam 972189251Ssam/* 973189251Ssam * Get file stats 974189251Ssam */ 975189251Ssamint 976189251SsamfetchStatFTP(struct url *url, struct url_stat *us, char *flags) 977189251Ssam{ 978189251Ssam struct url *purl; 979189251Ssam int cd; 980189251Ssam 981189251Ssam /* get the proxy URL, and check if we should use HTTP instead */ 982189251Ssam if (!CHECK_FLAG('d') && (purl = _ftp_get_proxy()) != NULL) { 983189251Ssam if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0) { 984189251Ssam FILE *f; 985189251Ssam 986189251Ssam if ((f = _http_request(url, "HEAD", us, purl, flags)) == NULL) 987189251Ssam return -1; 988189251Ssam fclose(f); 989189251Ssam return 0; 990189251Ssam } 991189251Ssam } else { 992189251Ssam purl = NULL; 993189251Ssam } 994189251Ssam 995189251Ssam /* connect to server */ 996189251Ssam cd = _ftp_cached_connect(url, purl, flags); 997189251Ssam if (purl) 998189251Ssam fetchFreeURL(purl); 999189251Ssam if (cd == NULL) 1000189251Ssam return NULL; 1001189251Ssam 1002189251Ssam /* change directory */ 1003189251Ssam if (_ftp_cwd(cd, url->doc) == -1) 1004189251Ssam return -1; 1005189251Ssam 1006189251Ssam /* stat file */ 1007189251Ssam return _ftp_stat(cd, url->doc, us); 1008189251Ssam} 1009189251Ssam 1010189251Ssam/* 1011189251Ssam * List a directory 1012189251Ssam */ 1013189251Ssamextern void warnx(char *, ...); 1014189251Ssamstruct url_ent * 1015189251SsamfetchListFTP(struct url *url, char *flags) 1016189251Ssam{ 1017189251Ssam warnx("fetchListFTP(): not implemented"); 1018189251Ssam return NULL; 1019189251Ssam} 1020189251Ssam