ftp.c revision 60188
1/*- 2 * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer 10 * in this position and unchanged. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 * 28 * $FreeBSD: head/lib/libfetch/ftp.c 60188 2000-05-07 20:00:12Z des $ 29 */ 30 31/* 32 * Portions of this code were taken from or based on ftpio.c: 33 * 34 * ---------------------------------------------------------------------------- 35 * "THE BEER-WARE LICENSE" (Revision 42): 36 * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you 37 * can do whatever you want with this stuff. If we meet some day, and you think 38 * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp 39 * ---------------------------------------------------------------------------- 40 * 41 * Major Changelog: 42 * 43 * Dag-Erling Co�dan Sm�rgrav 44 * 9 Jun 1998 45 * 46 * Incorporated into libfetch 47 * 48 * Jordan K. Hubbard 49 * 17 Jan 1996 50 * 51 * Turned inside out. Now returns xfers as new file ids, not as a special 52 * `state' of FTP_t 53 * 54 * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $ 55 * 56 */ 57 58#include <sys/param.h> 59#include <sys/socket.h> 60#include <sys/uio.h> 61#include <netinet/in.h> 62 63#include <ctype.h> 64#include <errno.h> 65#include <netdb.h> 66#include <stdarg.h> 67#include <stdio.h> 68#include <stdlib.h> 69#include <string.h> 70#include <time.h> 71#include <unistd.h> 72 73#include "fetch.h" 74#include "common.h" 75#include "ftperr.h" 76 77#define FTP_ANONYMOUS_USER "ftp" 78#define FTP_ANONYMOUS_PASSWORD "ftp" 79#define FTP_DEFAULT_PORT 21 80 81#define FTP_OPEN_DATA_CONNECTION 150 82#define FTP_OK 200 83#define FTP_FILE_STATUS 213 84#define FTP_SERVICE_READY 220 85#define FTP_PASSIVE_MODE 227 86#define FTP_LOGGED_IN 230 87#define FTP_FILE_ACTION_OK 250 88#define FTP_NEED_PASSWORD 331 89#define FTP_NEED_ACCOUNT 332 90#define FTP_FILE_OK 350 91#define FTP_SYNTAX_ERROR 500 92 93static char ENDL[2] = "\r\n"; 94 95static struct url cached_host; 96static int cached_socket; 97 98static char *last_reply; 99static size_t lr_size, lr_length; 100static int last_code; 101 102#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 103 && isdigit(foo[2]) && foo[3] == ' ') 104#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 105 && isdigit(foo[2]) && foo[3] == '-') 106 107/* 108 * Get server response 109 */ 110static int 111_ftp_chkerr(int cd) 112{ 113 do { 114 if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 115 _fetch_syserr(); 116 return -1; 117 } 118#ifndef NDEBUG 119 _fetch_info("got reply '%.*s'", lr_length - 2, last_reply); 120#endif 121 } while (isftpinfo(last_reply)); 122 123 while (lr_length && isspace(last_reply[lr_length-1])) 124 lr_length--; 125 last_reply[lr_length] = 0; 126 127 if (!isftpreply(last_reply)) { 128 _ftp_seterr(999); 129 return -1; 130 } 131 132 last_code = (last_reply[0] - '0') * 100 133 + (last_reply[1] - '0') * 10 134 + (last_reply[2] - '0'); 135 136 return last_code; 137} 138 139/* 140 * Send a command and check reply 141 */ 142static int 143_ftp_cmd(int cd, char *fmt, ...) 144{ 145 va_list ap; 146 struct iovec iov[2]; 147 char *msg; 148 int r; 149 150 va_start(ap, fmt); 151 vasprintf(&msg, fmt, ap); 152 va_end(ap); 153 154 if (msg == NULL) { 155 errno = ENOMEM; 156 _fetch_syserr(); 157 return -1; 158 } 159#ifndef NDEBUG 160 _fetch_info("sending '%s'", msg); 161#endif 162 iov[0].iov_base = msg; 163 iov[0].iov_len = strlen(msg); 164 iov[1].iov_base = ENDL; 165 iov[1].iov_len = sizeof ENDL; 166 r = writev(cd, iov, 2); 167 free(msg); 168 if (r == -1) { 169 _fetch_syserr(); 170 return -1; 171 } 172 173 return _ftp_chkerr(cd); 174} 175 176/* 177 * Transfer file 178 */ 179static FILE * 180_ftp_transfer(int cd, char *oper, char *file, 181 char *mode, off_t offset, char *flags) 182{ 183 struct sockaddr_in sin; 184 int pasv, high, verbose; 185 int e, sd = -1; 186 socklen_t l; 187 char *s; 188 FILE *df; 189 190 /* check flags */ 191 pasv = (flags && strchr(flags, 'p')); 192 high = (flags && strchr(flags, 'h')); 193 verbose = (flags && strchr(flags, 'v')); 194 195 /* change directory */ 196 if (((s = strrchr(file, '/')) != NULL) && (s != file)) { 197 *s = 0; 198 if (verbose) 199 _fetch_info("changing directory to %s", file); 200 if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) { 201 *s = '/'; 202 if (e != -1) 203 _ftp_seterr(e); 204 return NULL; 205 } 206 *s++ = '/'; 207 } else { 208 if (verbose) 209 _fetch_info("changing directory to /"); 210 if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) { 211 if (e != -1) 212 _ftp_seterr(e); 213 return NULL; 214 } 215 } 216 217 /* s now points to file name */ 218 219 /* open data socket */ 220 if ((sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { 221 _fetch_syserr(); 222 return NULL; 223 } 224 225 if (pasv) { 226 u_char addr[6]; 227 char *ln, *p; 228 int i; 229 230 /* send PASV command */ 231 if (verbose) 232 _fetch_info("setting passive mode"); 233 if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 234 goto ouch; 235 236 /* 237 * Find address and port number. The reply to the PASV command 238 * is IMHO the one and only weak point in the FTP protocol. 239 */ 240 ln = last_reply; 241 for (p = ln + 3; !isdigit(*p); p++) 242 /* nothing */ ; 243 for (p--, i = 0; i < 6; i++) { 244 p++; /* skip the comma */ 245 addr[i] = strtol(p, &p, 10); 246 } 247 248 /* seek to required offset */ 249 if (offset) 250 if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 251 goto sysouch; 252 253 /* construct sockaddr for data socket */ 254 l = sizeof sin; 255 if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 256 goto sysouch; 257 bcopy(addr, (char *)&sin.sin_addr, 4); 258 bcopy(addr + 4, (char *)&sin.sin_port, 2); 259 260 /* connect to data port */ 261 if (verbose) 262 _fetch_info("opening data connection"); 263 if (connect(sd, (struct sockaddr *)&sin, sizeof sin) == -1) 264 goto sysouch; 265 266 /* make the server initiate the transfer */ 267 if (verbose) 268 _fetch_info("initiating transfer"); 269 e = _ftp_cmd(cd, "%s %s", oper, s); 270 if (e != FTP_OPEN_DATA_CONNECTION) 271 goto ouch; 272 273 } else { 274 u_int32_t a; 275 u_short p; 276 int arg, d; 277 278 /* find our own address, bind, and listen */ 279 l = sizeof sin; 280 if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) 281 goto sysouch; 282 sin.sin_port = 0; 283 arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; 284 if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 285 (char *)&arg, sizeof arg) == -1) 286 goto sysouch; 287 if (verbose) 288 _fetch_info("binding data socket"); 289 if (bind(sd, (struct sockaddr *)&sin, l) == -1) 290 goto sysouch; 291 if (listen(sd, 1) == -1) 292 goto sysouch; 293 294 /* find what port we're on and tell the server */ 295 if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 296 goto sysouch; 297 a = ntohl(sin.sin_addr.s_addr); 298 p = ntohs(sin.sin_port); 299 e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 300 (a >> 24) & 0xff, (a >> 16) & 0xff, 301 (a >> 8) & 0xff, a & 0xff, 302 (p >> 8) & 0xff, p & 0xff); 303 if (e != FTP_OK) 304 goto ouch; 305 306 /* make the server initiate the transfer */ 307 if (verbose) 308 _fetch_info("initiating transfer"); 309 e = _ftp_cmd(cd, "%s %s", oper, s); 310 if (e != FTP_OPEN_DATA_CONNECTION) 311 goto ouch; 312 313 /* accept the incoming connection and go to town */ 314 if ((d = accept(sd, NULL, NULL)) == -1) 315 goto sysouch; 316 close(sd); 317 sd = d; 318 } 319 320 if ((df = fdopen(sd, mode)) == NULL) 321 goto sysouch; 322 return df; 323 324sysouch: 325 _fetch_syserr(); 326 close(sd); 327 return NULL; 328 329ouch: 330 if (e != -1) 331 _ftp_seterr(e); 332 close(sd); 333 return NULL; 334} 335 336/* 337 * Log on to FTP server 338 */ 339static int 340_ftp_connect(char *host, int port, char *user, char *pwd, char *flags) 341{ 342 int cd, e, pp = 0, direct, verbose; 343 char *p, *q; 344 345 direct = (flags && strchr(flags, 'd')); 346 verbose = (flags && strchr(flags, 'v')); 347 348 /* check for proxy */ 349 if (!direct && (p = getenv("FTP_PROXY")) != NULL) { 350 if ((q = strchr(p, ':')) != NULL) { 351 if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) { 352 /* XXX we should emit some kind of warning */ 353 } 354 pp = atoi(q+1); 355 if (pp < 1 || pp > 65535) { 356 /* XXX we should emit some kind of warning */ 357 } 358 } 359 if (!pp) { 360 struct servent *se; 361 362 if ((se = getservbyname("ftp", "tcp")) != NULL) 363 pp = ntohs(se->s_port); 364 else 365 pp = FTP_DEFAULT_PORT; 366 } 367 if (q) 368 *q = 0; 369 cd = _fetch_connect(p, pp, verbose); 370 if (q) 371 *q = ':'; 372 } else { 373 /* no proxy, go straight to target */ 374 cd = _fetch_connect(host, port, verbose); 375 p = NULL; 376 } 377 378 /* check connection */ 379 if (cd == -1) { 380 _fetch_syserr(); 381 return NULL; 382 } 383 384 /* expect welcome message */ 385 if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 386 goto fouch; 387 388 /* send user name and password */ 389 if (!user || !*user) 390 user = FTP_ANONYMOUS_USER; 391 e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port) 392 : _ftp_cmd(cd, "USER %s", user); 393 394 /* did the server request a password? */ 395 if (e == FTP_NEED_PASSWORD) { 396 if (!pwd || !*pwd) 397 pwd = FTP_ANONYMOUS_PASSWORD; 398 e = _ftp_cmd(cd, "PASS %s", pwd); 399 } 400 401 /* did the server request an account? */ 402 if (e == FTP_NEED_ACCOUNT) 403 goto fouch; 404 405 /* we should be done by now */ 406 if (e != FTP_LOGGED_IN) 407 goto fouch; 408 409 /* might as well select mode and type at once */ 410#ifdef FTP_FORCE_STREAM_MODE 411 if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 412 goto fouch; 413#endif 414 if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 415 goto fouch; 416 417 /* done */ 418 return cd; 419 420fouch: 421 if (e != -1) 422 _ftp_seterr(e); 423 close(cd); 424 return NULL; 425} 426 427/* 428 * Disconnect from server 429 */ 430static void 431_ftp_disconnect(int cd) 432{ 433 (void)_ftp_cmd(cd, "QUIT"); 434 close(cd); 435} 436 437/* 438 * Check if we're already connected 439 */ 440static int 441_ftp_isconnected(struct url *url) 442{ 443 return (cached_socket 444 && (strcmp(url->host, cached_host.host) == 0) 445 && (strcmp(url->user, cached_host.user) == 0) 446 && (strcmp(url->pwd, cached_host.pwd) == 0) 447 && (url->port == cached_host.port)); 448} 449 450/* 451 * Check the cache, reconnect if no luck 452 */ 453static int 454_ftp_cached_connect(struct url *url, char *flags) 455{ 456 int e, cd; 457 458 cd = -1; 459 460 /* set default port */ 461 if (!url->port) { 462 struct servent *se; 463 464 if ((se = getservbyname("ftp", "tcp")) != NULL) 465 url->port = ntohs(se->s_port); 466 else 467 url->port = FTP_DEFAULT_PORT; 468 } 469 470 /* try to use previously cached connection */ 471 if (_ftp_isconnected(url)) { 472 e = _ftp_cmd(cached_socket, "NOOP"); 473 if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 474 cd = cached_socket; 475 } 476 477 /* connect to server */ 478 if (cd == -1) { 479 cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags); 480 if (cd == -1) 481 return -1; 482 if (cached_socket) 483 _ftp_disconnect(cached_socket); 484 cached_socket = cd; 485 memcpy(&cached_host, url, sizeof *url); 486 } 487 488 return cd; 489} 490 491/* 492 * Get file 493 */ 494FILE * 495fetchGetFTP(struct url *url, char *flags) 496{ 497 int cd; 498 499 /* connect to server */ 500 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 501 return NULL; 502 503 /* initiate the transfer */ 504 return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags); 505} 506 507/* 508 * Put file 509 */ 510FILE * 511fetchPutFTP(struct url *url, char *flags) 512{ 513 int cd; 514 515 /* connect to server */ 516 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 517 return NULL; 518 519 /* initiate the transfer */ 520 return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR", 521 url->doc, "w", url->offset, flags); 522} 523 524/* 525 * Get file stats 526 */ 527int 528fetchStatFTP(struct url *url, struct url_stat *us, char *flags) 529{ 530 char *ln, *s; 531 struct tm tm; 532 time_t t; 533 int e, cd; 534 535 /* connect to server */ 536 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 537 return -1; 538 539 /* change directory */ 540 if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) { 541 *s = 0; 542 if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) { 543 *s = '/'; 544 goto ouch; 545 } 546 *s++ = '/'; 547 } else { 548 if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) 549 goto ouch; 550 } 551 552 /* s now points to file name */ 553 554 if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS) 555 goto ouch; 556 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 557 /* nothing */ ; 558 for (us->size = 0; *ln && isdigit(*ln); ln++) 559 us->size = us->size * 10 + *ln - '0'; 560 if (*ln && !isspace(*ln)) { 561 _ftp_seterr(999); 562 return -1; 563 } 564 565 if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) 566 goto ouch; 567 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 568 /* nothing */ ; 569 sscanf(ln, "%04d%02d%02d%02d%02d%02d", 570 &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 571 &tm.tm_hour, &tm.tm_min, &tm.tm_sec); 572 /* XXX should check the return value from sscanf */ 573 tm.tm_mon--; 574 tm.tm_year -= 1900; 575 tm.tm_isdst = -1; 576 t = mktime(&tm); 577 if (t == (time_t)-1) 578 t = time(NULL); 579 else 580 t += tm.tm_gmtoff; 581 us->mtime = t; 582 us->atime = t; 583 return 0; 584 585ouch: 586 if (e != -1) 587 _ftp_seterr(e); 588 return -1; 589} 590 591/* 592 * List a directory 593 */ 594extern void warnx(char *, ...); 595struct url_ent * 596fetchListFTP(struct url *url, char *flags) 597{ 598 warnx("fetchListFTP(): not implemented"); 599 return NULL; 600} 601