ftp.c revision 62256
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 62256 2000-06-29 10:44:10Z 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_LPASSIVE_MODE 228 87#define FTP_EPASSIVE_MODE 229 88#define FTP_LOGGED_IN 230 89#define FTP_FILE_ACTION_OK 250 90#define FTP_NEED_PASSWORD 331 91#define FTP_NEED_ACCOUNT 332 92#define FTP_FILE_OK 350 93#define FTP_SYNTAX_ERROR 500 94 95static char ENDL[2] = "\r\n"; 96 97static struct url cached_host; 98static int cached_socket; 99 100static char *last_reply; 101static size_t lr_size, lr_length; 102static int last_code; 103 104#define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 105 && isdigit(foo[2]) \ 106 && (foo[3] == ' ' || foo[3] == '\0')) 107#define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \ 108 && isdigit(foo[2]) && foo[3] == '-') 109 110/* translate IPv4 mapped IPv6 address to IPv4 address */ 111static void 112unmappedaddr(struct sockaddr_in6 *sin6) 113{ 114 struct sockaddr_in *sin4; 115 u_int32_t addr; 116 int port; 117 118 if (sin6->sin6_family != AF_INET6 || 119 !IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) 120 return; 121 sin4 = (struct sockaddr_in *)sin6; 122 addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12]; 123 port = sin6->sin6_port; 124 memset(sin4, 0, sizeof(struct sockaddr_in)); 125 sin4->sin_addr.s_addr = addr; 126 sin4->sin_port = port; 127 sin4->sin_family = AF_INET; 128 sin4->sin_len = sizeof(struct sockaddr_in); 129} 130 131/* 132 * Get server response 133 */ 134static int 135_ftp_chkerr(int cd) 136{ 137 if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 138 _fetch_syserr(); 139 return -1; 140 } 141#ifndef NDEBUG 142 _fetch_info("got reply '%.*s'", lr_length - 2, last_reply); 143#endif 144 if (isftpinfo(last_reply)) { 145 while (!isftpreply(last_reply)) { 146 if (_fetch_getln(cd, &last_reply, &lr_size, &lr_length) == -1) { 147 _fetch_syserr(); 148 return -1; 149 } 150#ifndef NDEBUG 151 _fetch_info("got reply '%.*s'", lr_length - 2, last_reply); 152#endif 153 } 154 } 155 156 while (lr_length && isspace(last_reply[lr_length-1])) 157 lr_length--; 158 last_reply[lr_length] = 0; 159 160 if (!isftpreply(last_reply)) { 161 _ftp_seterr(999); 162 return -1; 163 } 164 165 last_code = (last_reply[0] - '0') * 100 166 + (last_reply[1] - '0') * 10 167 + (last_reply[2] - '0'); 168 169 return last_code; 170} 171 172/* 173 * Send a command and check reply 174 */ 175static int 176_ftp_cmd(int cd, char *fmt, ...) 177{ 178 va_list ap; 179 struct iovec iov[2]; 180 char *msg; 181 int r; 182 183 va_start(ap, fmt); 184 vasprintf(&msg, fmt, ap); 185 va_end(ap); 186 187 if (msg == NULL) { 188 errno = ENOMEM; 189 _fetch_syserr(); 190 return -1; 191 } 192#ifndef NDEBUG 193 _fetch_info("sending '%s'", msg); 194#endif 195 iov[0].iov_base = msg; 196 iov[0].iov_len = strlen(msg); 197 iov[1].iov_base = ENDL; 198 iov[1].iov_len = sizeof ENDL; 199 r = writev(cd, iov, 2); 200 free(msg); 201 if (r == -1) { 202 _fetch_syserr(); 203 return -1; 204 } 205 206 return _ftp_chkerr(cd); 207} 208 209/* 210 * Transfer file 211 */ 212static FILE * 213_ftp_transfer(int cd, char *oper, char *file, 214 char *mode, off_t offset, char *flags) 215{ 216 struct sockaddr_storage sin; 217 struct sockaddr_in6 *sin6; 218 struct sockaddr_in *sin4; 219 int pasv, high, verbose; 220 int e, sd = -1; 221 socklen_t l; 222 char *s; 223 FILE *df; 224 225 /* check flags */ 226 pasv = (flags && strchr(flags, 'p')); 227 high = (flags && strchr(flags, 'h')); 228 verbose = (flags && strchr(flags, 'v')); 229 230 /* passive mode */ 231 if (!pasv && (s = getenv("FTP_PASSIVE_MODE")) != NULL) 232 pasv = (strncasecmp(s, "no", 2) != 0); 233 234 /* change directory */ 235 if (((s = strrchr(file, '/')) != NULL) && (s != file)) { 236 *s = 0; 237 if (verbose) 238 _fetch_info("changing directory to %s", file); 239 if ((e = _ftp_cmd(cd, "CWD %s", file)) != FTP_FILE_ACTION_OK) { 240 *s = '/'; 241 if (e != -1) 242 _ftp_seterr(e); 243 return NULL; 244 } 245 *s++ = '/'; 246 } else { 247 if (verbose) 248 _fetch_info("changing directory to /"); 249 if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) { 250 if (e != -1) 251 _ftp_seterr(e); 252 return NULL; 253 } 254 } 255 256 /* s now points to file name */ 257 258 /* find our own address, bind, and listen */ 259 l = sizeof sin; 260 if (getsockname(cd, (struct sockaddr *)&sin, &l) == -1) 261 goto sysouch; 262 if (sin.ss_family == AF_INET6) 263 unmappedaddr((struct sockaddr_in6 *)&sin); 264 265 /* open data socket */ 266 if ((sd = socket(sin.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { 267 _fetch_syserr(); 268 return NULL; 269 } 270 271 if (pasv) { 272 u_char addr[64]; 273 char *ln, *p; 274 int i; 275 int port; 276 277 /* send PASV command */ 278 if (verbose) 279 _fetch_info("setting passive mode"); 280 switch (sin.ss_family) { 281 case AF_INET: 282 if ((e = _ftp_cmd(cd, "PASV")) != FTP_PASSIVE_MODE) 283 goto ouch; 284 break; 285 case AF_INET6: 286 if ((e = _ftp_cmd(cd, "EPSV")) != FTP_EPASSIVE_MODE) { 287 if (e == -1) 288 goto ouch; 289 if ((e = _ftp_cmd(cd, "LPSV")) != FTP_LPASSIVE_MODE) 290 goto ouch; 291 } 292 break; 293 default: 294 e = 999; /* XXX: error code should be prepared */ 295 goto ouch; 296 } 297 298 /* 299 * Find address and port number. The reply to the PASV command 300 * is IMHO the one and only weak point in the FTP protocol. 301 */ 302 ln = last_reply; 303 for (p = ln + 3; *p && *p != '('; p++) 304 /* nothing */ ; 305 if (!*p) { 306 e = 999; 307 goto ouch; 308 } 309 p++; 310 switch (e) { 311 case FTP_PASSIVE_MODE: 312 case FTP_LPASSIVE_MODE: 313 l = (e == FTP_PASSIVE_MODE ? 6 : 21); 314 for (i = 0; *p && i < l; i++, p++) 315 addr[i] = strtol(p, &p, 10); 316 if (i < l) { 317 e = 999; 318 goto ouch; 319 } 320 break; 321 case FTP_EPASSIVE_MODE: 322 if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 323 &port, &addr[3]) != 5 || 324 addr[0] != addr[1] || 325 addr[0] != addr[2] || addr[0] != addr[3]) { 326 e = 999; 327 goto ouch; 328 } 329 break; 330 } 331 332 /* seek to required offset */ 333 if (offset) 334 if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 335 goto sysouch; 336 337 /* construct sockaddr for data socket */ 338 l = sizeof sin; 339 if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 340 goto sysouch; 341 if (sin.ss_family == AF_INET6) 342 unmappedaddr((struct sockaddr_in6 *)&sin); 343 switch (sin.ss_family) { 344 case AF_INET6: 345 sin6 = (struct sockaddr_in6 *)&sin; 346 if (e == FTP_EPASSIVE_MODE) 347 sin6->sin6_port = htons(port); 348 else { 349 bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 350 bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 351 } 352 break; 353 case AF_INET: 354 sin4 = (struct sockaddr_in *)&sin; 355 if (e == FTP_EPASSIVE_MODE) 356 sin4->sin_port = htons(port); 357 else { 358 bcopy(addr, (char *)&sin4->sin_addr, 4); 359 bcopy(addr + 4, (char *)&sin4->sin_port, 2); 360 } 361 break; 362 default: 363 e = 999; /* XXX: error code should be prepared */ 364 break; 365 } 366 367 /* connect to data port */ 368 if (verbose) 369 _fetch_info("opening data connection"); 370 if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 371 goto sysouch; 372 373 /* make the server initiate the transfer */ 374 if (verbose) 375 _fetch_info("initiating transfer"); 376 e = _ftp_cmd(cd, "%s %s", oper, s); 377 if (e != FTP_OPEN_DATA_CONNECTION) 378 goto ouch; 379 380 } else { 381 u_int32_t a; 382 u_short p; 383 int arg, d; 384 char *ap; 385 char hname[INET6_ADDRSTRLEN]; 386 387 switch (sin.ss_family) { 388 case AF_INET6: 389 ((struct sockaddr_in6 *)&sin)->sin6_port = 0; 390#ifdef IPV6_PORTRANGE 391 arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT; 392 if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 393 (char *)&arg, sizeof(arg)) == -1) 394 goto sysouch; 395#endif 396 break; 397 case AF_INET: 398 ((struct sockaddr_in *)&sin)->sin_port = 0; 399 arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; 400 if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 401 (char *)&arg, sizeof arg) == -1) 402 goto sysouch; 403 break; 404 } 405 if (verbose) 406 _fetch_info("binding data socket"); 407 if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 408 goto sysouch; 409 if (listen(sd, 1) == -1) 410 goto sysouch; 411 412 /* find what port we're on and tell the server */ 413 if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 414 goto sysouch; 415 switch (sin.ss_family) { 416 case AF_INET: 417 sin4 = (struct sockaddr_in *)&sin; 418 a = ntohl(sin4->sin_addr.s_addr); 419 p = ntohs(sin4->sin_port); 420 e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 421 (a >> 24) & 0xff, (a >> 16) & 0xff, 422 (a >> 8) & 0xff, a & 0xff, 423 (p >> 8) & 0xff, p & 0xff); 424 break; 425 case AF_INET6: 426#define UC(b) (((int)b)&0xff) 427 e = -1; 428 sin6 = (struct sockaddr_in6 *)&sin; 429 if (getnameinfo((struct sockaddr *)&sin, sin.ss_len, 430 hname, sizeof(hname), 431 NULL, 0, NI_NUMERICHOST) == 0) { 432 e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 433 htons(sin6->sin6_port)); 434 if (e == -1) 435 goto ouch; 436 } 437 if (e != FTP_OK) { 438 ap = (char *)&sin6->sin6_addr; 439 e = _ftp_cmd(cd, 440 "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 441 6, 16, 442 UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 443 UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 444 UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 445 UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 446 2, 447 (ntohs(sin6->sin6_port) >> 8) & 0xff, 448 ntohs(sin6->sin6_port) & 0xff); 449 } 450 break; 451 default: 452 e = 999; /* XXX: error code should be prepared */ 453 goto ouch; 454 } 455 if (e != FTP_OK) 456 goto ouch; 457 458 /* seek to required offset */ 459 if (offset) 460 if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 461 goto sysouch; 462 463 /* make the server initiate the transfer */ 464 if (verbose) 465 _fetch_info("initiating transfer"); 466 e = _ftp_cmd(cd, "%s %s", oper, s); 467 if (e != FTP_OPEN_DATA_CONNECTION) 468 goto ouch; 469 470 /* accept the incoming connection and go to town */ 471 if ((d = accept(sd, NULL, NULL)) == -1) 472 goto sysouch; 473 close(sd); 474 sd = d; 475 } 476 477 if ((df = fdopen(sd, mode)) == NULL) 478 goto sysouch; 479 return df; 480 481sysouch: 482 _fetch_syserr(); 483 if (sd >= 0) 484 close(sd); 485 return NULL; 486 487ouch: 488 if (e != -1) 489 _ftp_seterr(e); 490 if (sd >= 0) 491 close(sd); 492 return NULL; 493} 494 495/* 496 * Log on to FTP server 497 */ 498static int 499_ftp_connect(char *host, int port, char *user, char *pwd, char *flags) 500{ 501 int cd, e, pp = 0, direct, verbose; 502#ifdef INET6 503 int af = AF_UNSPEC; 504#else 505 int af = AF_INET; 506#endif 507 char *p, *q; 508 const char *logname; 509 char localhost[MAXHOSTNAMELEN]; 510 char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 511 512 direct = (flags && strchr(flags, 'd')); 513 verbose = (flags && strchr(flags, 'v')); 514 if ((flags && strchr(flags, '4'))) 515 af = AF_INET; 516 else if ((flags && strchr(flags, '6'))) 517 af = AF_INET6; 518 519 /* check for proxy */ 520 if (!direct && (p = getenv("FTP_PROXY")) != NULL) { 521 char c = 0; 522 523#ifdef INET6 524 if (*p != '[' || (q = strchr(p + 1, ']')) == NULL || 525 (*++q != '\0' && *q != ':')) 526#endif 527 q = strchr(p, ':'); 528 if (q != NULL && *q == ':') { 529 if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) { 530 /* XXX we should emit some kind of warning */ 531 } 532 pp = atoi(q+1); 533 if (pp < 1 || pp > 65535) { 534 /* XXX we should emit some kind of warning */ 535 } 536 } 537 if (!pp) { 538 struct servent *se; 539 540 if ((se = getservbyname("ftp", "tcp")) != NULL) 541 pp = ntohs(se->s_port); 542 else 543 pp = FTP_DEFAULT_PORT; 544 } 545 if (q) { 546#ifdef INET6 547 if (q > p && *p == '[' && *(q - 1) == ']') { 548 p++; 549 q--; 550 } 551#endif 552 c = *q; 553 *q = 0; 554 } 555 cd = _fetch_connect(p, pp, af, verbose); 556 if (q) 557 *q = c; 558 } else { 559 /* no proxy, go straight to target */ 560 cd = _fetch_connect(host, port, af, verbose); 561 p = NULL; 562 } 563 564 /* check connection */ 565 if (cd == -1) { 566 _fetch_syserr(); 567 return NULL; 568 } 569 570 /* expect welcome message */ 571 if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 572 goto fouch; 573 574 /* send user name and password */ 575 if (!user || !*user) 576 user = FTP_ANONYMOUS_USER; 577 e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port) 578 : _ftp_cmd(cd, "USER %s", user); 579 580 /* did the server request a password? */ 581 if (e == FTP_NEED_PASSWORD) { 582 if (!pwd || !*pwd) 583 pwd = getenv("FTP_PASSWORD"); 584 if (!pwd || !*pwd) { 585 if ((logname = getlogin()) == 0) 586 logname = FTP_ANONYMOUS_PASSWORD; 587 gethostname(localhost, sizeof localhost); 588 snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost); 589 pwd = pbuf; 590 } 591 e = _ftp_cmd(cd, "PASS %s", pwd); 592 } 593 594 /* did the server request an account? */ 595 if (e == FTP_NEED_ACCOUNT) 596 goto fouch; 597 598 /* we should be done by now */ 599 if (e != FTP_LOGGED_IN) 600 goto fouch; 601 602 /* might as well select mode and type at once */ 603#ifdef FTP_FORCE_STREAM_MODE 604 if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 605 goto fouch; 606#endif 607 if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 608 goto fouch; 609 610 /* done */ 611 return cd; 612 613fouch: 614 if (e != -1) 615 _ftp_seterr(e); 616 close(cd); 617 return NULL; 618} 619 620/* 621 * Disconnect from server 622 */ 623static void 624_ftp_disconnect(int cd) 625{ 626 (void)_ftp_cmd(cd, "QUIT"); 627 close(cd); 628} 629 630/* 631 * Check if we're already connected 632 */ 633static int 634_ftp_isconnected(struct url *url) 635{ 636 return (cached_socket 637 && (strcmp(url->host, cached_host.host) == 0) 638 && (strcmp(url->user, cached_host.user) == 0) 639 && (strcmp(url->pwd, cached_host.pwd) == 0) 640 && (url->port == cached_host.port)); 641} 642 643/* 644 * Check the cache, reconnect if no luck 645 */ 646static int 647_ftp_cached_connect(struct url *url, char *flags) 648{ 649 int e, cd; 650 651 cd = -1; 652 653 /* set default port */ 654 if (!url->port) { 655 struct servent *se; 656 657 if ((se = getservbyname("ftp", "tcp")) != NULL) 658 url->port = ntohs(se->s_port); 659 else 660 url->port = FTP_DEFAULT_PORT; 661 } 662 663 /* try to use previously cached connection */ 664 if (_ftp_isconnected(url)) { 665 e = _ftp_cmd(cached_socket, "NOOP"); 666 if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 667 cd = cached_socket; 668 } 669 670 /* connect to server */ 671 if (cd == -1) { 672 cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags); 673 if (cd == -1) 674 return -1; 675 if (cached_socket) 676 _ftp_disconnect(cached_socket); 677 cached_socket = cd; 678 memcpy(&cached_host, url, sizeof *url); 679 } 680 681 return cd; 682} 683 684/* 685 * Get file 686 */ 687FILE * 688fetchGetFTP(struct url *url, char *flags) 689{ 690 int cd; 691 692 /* connect to server */ 693 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 694 return NULL; 695 696 /* initiate the transfer */ 697 return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags); 698} 699 700/* 701 * Put file 702 */ 703FILE * 704fetchPutFTP(struct url *url, char *flags) 705{ 706 int cd; 707 708 /* connect to server */ 709 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 710 return NULL; 711 712 /* initiate the transfer */ 713 return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR", 714 url->doc, "w", url->offset, flags); 715} 716 717/* 718 * Get file stats 719 */ 720int 721fetchStatFTP(struct url *url, struct url_stat *us, char *flags) 722{ 723 char *ln, *s; 724 struct tm tm; 725 time_t t; 726 int e, cd; 727 728 us->size = -1; 729 us->atime = us->mtime = 0; 730 731 /* connect to server */ 732 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 733 return -1; 734 735 /* change directory */ 736 if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) { 737 *s = 0; 738 if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) { 739 *s = '/'; 740 goto ouch; 741 } 742 *s++ = '/'; 743 } else { 744 if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) 745 goto ouch; 746 } 747 748 /* s now points to file name */ 749 750 if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS) 751 goto ouch; 752 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 753 /* nothing */ ; 754 for (us->size = 0; *ln && isdigit(*ln); ln++) 755 us->size = us->size * 10 + *ln - '0'; 756 if (*ln && !isspace(*ln)) { 757 _ftp_seterr(999); 758 return -1; 759 } 760 DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size)); 761 762 if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) 763 goto ouch; 764 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 765 /* nothing */ ; 766 e = 999; 767 switch (strspn(ln, "0123456789")) { 768 case 14: 769 break; 770 case 15: 771 ln++; 772 ln[0] = '2'; 773 ln[1] = '0'; 774 break; 775 default: 776 goto ouch; 777 } 778 if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 779 &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 780 &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) 781 goto ouch; 782 tm.tm_mon--; 783 tm.tm_year -= 1900; 784 tm.tm_isdst = -1; 785 t = timegm(&tm); 786 if (t == (time_t)-1) 787 t = time(NULL); 788 us->mtime = t; 789 us->atime = t; 790 DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " 791 "%02d:%02d:%02d\033[m]\n", 792 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 793 tm.tm_hour, tm.tm_min, tm.tm_sec)); 794 return 0; 795 796ouch: 797 if (e != -1) 798 _ftp_seterr(e); 799 return -1; 800} 801 802/* 803 * List a directory 804 */ 805extern void warnx(char *, ...); 806struct url_ent * 807fetchListFTP(struct url *url, char *flags) 808{ 809 warnx("fetchListFTP(): not implemented"); 810 return NULL; 811} 812