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$ 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 95 static char ENDL[2] = "\r\n"; 96 97 static struct url cached_host; 98 static int cached_socket; 99 100 static char *last_reply; 101 static size_t lr_size, lr_length; 102 static 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 */ 111 static void 112 unmappedaddr(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 */ 134 static 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 */ 175 static 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 */ 212 static 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 switch (e) { 304 case FTP_PASSIVE_MODE: 305 case FTP_LPASSIVE_MODE: 306 for (p = ln + 3; *p && !isdigit(*p); p++) 307 /* nothing */ ; 308 if (!*p) { 309 e = 999; 310 goto ouch; 311 } 312 l = (e == FTP_PASSIVE_MODE ? 6 : 21); 313 for (i = 0; *p && i < l; i++, p++) 314 addr[i] = strtol(p, &p, 10); 315 if (i < l) { 316 e = 999; 317 goto ouch; 318 } 319 break; 320 case FTP_EPASSIVE_MODE: 321 for (p = ln + 3; *p && *p != '('; p++) 322 /* nothing */ ; 323 if (!*p) { 324 e = 999; 325 goto ouch; 326 } 327 ++p; 328 if (sscanf(p, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2], 329 &port, &addr[3]) != 5 || 330 addr[0] != addr[1] || 331 addr[0] != addr[2] || addr[0] != addr[3]) { 332 e = 999; 333 goto ouch; 334 } 335 break; 336 } 337 338 /* seek to required offset */ 339 if (offset) 340 if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 341 goto sysouch; 342 343 /* construct sockaddr for data socket */ 344 l = sizeof sin; 345 if (getpeername(cd, (struct sockaddr *)&sin, &l) == -1) 346 goto sysouch; 347 if (sin.ss_family == AF_INET6) 348 unmappedaddr((struct sockaddr_in6 *)&sin); 349 switch (sin.ss_family) { 350 case AF_INET6: 351 sin6 = (struct sockaddr_in6 *)&sin; 352 if (e == FTP_EPASSIVE_MODE) 353 sin6->sin6_port = htons(port); 354 else { 355 bcopy(addr + 2, (char *)&sin6->sin6_addr, 16); 356 bcopy(addr + 19, (char *)&sin6->sin6_port, 2); 357 } 358 break; 359 case AF_INET: 360 sin4 = (struct sockaddr_in *)&sin; 361 if (e == FTP_EPASSIVE_MODE) 362 sin4->sin_port = htons(port); 363 else { 364 bcopy(addr, (char *)&sin4->sin_addr, 4); 365 bcopy(addr + 4, (char *)&sin4->sin_port, 2); 366 } 367 break; 368 default: 369 e = 999; /* XXX: error code should be prepared */ 370 break; 371 } 372 373 /* connect to data port */ 374 if (verbose) 375 _fetch_info("opening data connection"); 376 if (connect(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 377 goto sysouch; 378 379 /* make the server initiate the transfer */ 380 if (verbose) 381 _fetch_info("initiating transfer"); 382 e = _ftp_cmd(cd, "%s %s", oper, s); 383 if (e != FTP_OPEN_DATA_CONNECTION) 384 goto ouch; 385 386 } else { 387 u_int32_t a; 388 u_short p; 389 int arg, d; 390 char *ap; 391 char hname[INET6_ADDRSTRLEN]; 392 393 switch (sin.ss_family) { 394 case AF_INET6: 395 ((struct sockaddr_in6 *)&sin)->sin6_port = 0; 396 #ifdef IPV6_PORTRANGE 397 arg = high ? IPV6_PORTRANGE_HIGH : IPV6_PORTRANGE_DEFAULT; 398 if (setsockopt(sd, IPPROTO_IPV6, IPV6_PORTRANGE, 399 (char *)&arg, sizeof(arg)) == -1) 400 goto sysouch; 401 #endif 402 break; 403 case AF_INET: 404 ((struct sockaddr_in *)&sin)->sin_port = 0; 405 arg = high ? IP_PORTRANGE_HIGH : IP_PORTRANGE_DEFAULT; 406 if (setsockopt(sd, IPPROTO_IP, IP_PORTRANGE, 407 (char *)&arg, sizeof arg) == -1) 408 goto sysouch; 409 break; 410 } 411 if (verbose) 412 _fetch_info("binding data socket"); 413 if (bind(sd, (struct sockaddr *)&sin, sin.ss_len) == -1) 414 goto sysouch; 415 if (listen(sd, 1) == -1) 416 goto sysouch; 417 418 /* find what port we're on and tell the server */ 419 if (getsockname(sd, (struct sockaddr *)&sin, &l) == -1) 420 goto sysouch; 421 switch (sin.ss_family) { 422 case AF_INET: 423 sin4 = (struct sockaddr_in *)&sin; 424 a = ntohl(sin4->sin_addr.s_addr); 425 p = ntohs(sin4->sin_port); 426 e = _ftp_cmd(cd, "PORT %d,%d,%d,%d,%d,%d", 427 (a >> 24) & 0xff, (a >> 16) & 0xff, 428 (a >> 8) & 0xff, a & 0xff, 429 (p >> 8) & 0xff, p & 0xff); 430 break; 431 case AF_INET6: 432 #define UC(b) (((int)b)&0xff) 433 e = -1; 434 sin6 = (struct sockaddr_in6 *)&sin; 435 if (getnameinfo((struct sockaddr *)&sin, sin.ss_len, 436 hname, sizeof(hname), 437 NULL, 0, NI_NUMERICHOST) == 0) { 438 e = _ftp_cmd(cd, "EPRT |%d|%s|%d|", 2, hname, 439 htons(sin6->sin6_port)); 440 if (e == -1) 441 goto ouch; 442 } 443 if (e != FTP_OK) { 444 ap = (char *)&sin6->sin6_addr; 445 e = _ftp_cmd(cd, 446 "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 447 6, 16, 448 UC(ap[0]), UC(ap[1]), UC(ap[2]), UC(ap[3]), 449 UC(ap[4]), UC(ap[5]), UC(ap[6]), UC(ap[7]), 450 UC(ap[8]), UC(ap[9]), UC(ap[10]), UC(ap[11]), 451 UC(ap[12]), UC(ap[13]), UC(ap[14]), UC(ap[15]), 452 2, 453 (ntohs(sin6->sin6_port) >> 8) & 0xff, 454 ntohs(sin6->sin6_port) & 0xff); 455 } 456 break; 457 default: 458 e = 999; /* XXX: error code should be prepared */ 459 goto ouch; 460 } 461 if (e != FTP_OK) 462 goto ouch; 463 464 /* seek to required offset */ 465 if (offset) 466 if (_ftp_cmd(cd, "REST %lu", (u_long)offset) != FTP_FILE_OK) 467 goto sysouch; 468 469 /* make the server initiate the transfer */ 470 if (verbose) 471 _fetch_info("initiating transfer"); 472 e = _ftp_cmd(cd, "%s %s", oper, s); 473 if (e != FTP_OPEN_DATA_CONNECTION) 474 goto ouch; 475 476 /* accept the incoming connection and go to town */ 477 if ((d = accept(sd, NULL, NULL)) == -1) 478 goto sysouch; 479 close(sd); 480 sd = d; 481 } 482 483 if ((df = fdopen(sd, mode)) == NULL) 484 goto sysouch; 485 return df; 486 487 sysouch: 488 _fetch_syserr(); 489 if (sd >= 0) 490 close(sd); 491 return NULL; 492 493 ouch: 494 if (e != -1) 495 _ftp_seterr(e); 496 if (sd >= 0) 497 close(sd); 498 return NULL; 499 } 500 501 /* 502 * Log on to FTP server 503 */ 504 static int 505 _ftp_connect(char *host, int port, char *user, char *pwd, char *flags) 506 { 507 int cd, e, pp = 0, direct, verbose; 508 #ifdef INET6 509 int af = AF_UNSPEC; 510 #else 511 int af = AF_INET; 512 #endif 513 char *p, *q; 514 const char *logname; 515 char localhost[MAXHOSTNAMELEN]; 516 char pbuf[MAXHOSTNAMELEN + MAXLOGNAME + 1]; 517 518 direct = (flags && strchr(flags, 'd')); 519 verbose = (flags && strchr(flags, 'v')); 520 if ((flags && strchr(flags, '4'))) 521 af = AF_INET; 522 else if ((flags && strchr(flags, '6'))) 523 af = AF_INET6; 524 525 /* check for proxy */ 526 if (!direct && (p = getenv("FTP_PROXY")) != NULL) { 527 char c = 0; 528 529 #ifdef INET6 530 if (*p != '[' || (q = strchr(p + 1, ']')) == NULL || 531 (*++q != '\0' && *q != ':')) 532 #endif 533 q = strchr(p, ':'); 534 if (q != NULL && *q == ':') { 535 if (strspn(q+1, "0123456789") != strlen(q+1) || strlen(q+1) > 5) { 536 /* XXX we should emit some kind of warning */ 537 } 538 pp = atoi(q+1); 539 if (pp < 1 || pp > 65535) { 540 /* XXX we should emit some kind of warning */ 541 } 542 } 543 if (!pp) { 544 struct servent *se; 545 546 if ((se = getservbyname("ftp", "tcp")) != NULL) 547 pp = ntohs(se->s_port); 548 else 549 pp = FTP_DEFAULT_PORT; 550 } 551 if (q) { 552 #ifdef INET6 553 if (q > p && *p == '[' && *(q - 1) == ']') { 554 p++; 555 q--; 556 } 557 #endif 558 c = *q; 559 *q = 0; 560 } 561 cd = _fetch_connect(p, pp, af, verbose); 562 if (q) 563 *q = c; 564 } else { 565 /* no proxy, go straight to target */ 566 cd = _fetch_connect(host, port, af, verbose); 567 p = NULL; 568 } 569 570 /* check connection */ 571 if (cd == -1) { 572 _fetch_syserr(); 573 return NULL; 574 } 575 576 /* expect welcome message */ 577 if ((e = _ftp_chkerr(cd)) != FTP_SERVICE_READY) 578 goto fouch; 579 580 /* send user name and password */ 581 if (!user || !*user) 582 user = FTP_ANONYMOUS_USER; 583 e = p ? _ftp_cmd(cd, "USER %s@%s@%d", user, host, port) 584 : _ftp_cmd(cd, "USER %s", user); 585 586 /* did the server request a password? */ 587 if (e == FTP_NEED_PASSWORD) { 588 if (!pwd || !*pwd) 589 pwd = getenv("FTP_PASSWORD"); 590 if (!pwd || !*pwd) { 591 if ((logname = getlogin()) == 0) 592 logname = FTP_ANONYMOUS_PASSWORD; 593 gethostname(localhost, sizeof localhost); 594 snprintf(pbuf, sizeof pbuf, "%s@%s", logname, localhost); 595 pwd = pbuf; 596 } 597 e = _ftp_cmd(cd, "PASS %s", pwd); 598 } 599 600 /* did the server request an account? */ 601 if (e == FTP_NEED_ACCOUNT) 602 goto fouch; 603 604 /* we should be done by now */ 605 if (e != FTP_LOGGED_IN) 606 goto fouch; 607 608 /* might as well select mode and type at once */ 609 #ifdef FTP_FORCE_STREAM_MODE 610 if ((e = _ftp_cmd(cd, "MODE S")) != FTP_OK) /* default is S */ 611 goto fouch; 612 #endif 613 if ((e = _ftp_cmd(cd, "TYPE I")) != FTP_OK) /* default is A */ 614 goto fouch; 615 616 /* done */ 617 return cd; 618 619 fouch: 620 if (e != -1) 621 _ftp_seterr(e); 622 close(cd); 623 return NULL; 624 } 625 626 /* 627 * Disconnect from server 628 */ 629 static void 630 _ftp_disconnect(int cd) 631 { 632 (void)_ftp_cmd(cd, "QUIT"); 633 close(cd); 634 } 635 636 /* 637 * Check if we're already connected 638 */ 639 static int 640 _ftp_isconnected(struct url *url) 641 { 642 return (cached_socket 643 && (strcmp(url->host, cached_host.host) == 0) 644 && (strcmp(url->user, cached_host.user) == 0) 645 && (strcmp(url->pwd, cached_host.pwd) == 0) 646 && (url->port == cached_host.port)); 647 } 648 649 /* 650 * Check the cache, reconnect if no luck 651 */ 652 static int 653 _ftp_cached_connect(struct url *url, char *flags) 654 { 655 int e, cd; 656 657 cd = -1; 658 659 /* set default port */ 660 if (!url->port) { 661 struct servent *se; 662 663 if ((se = getservbyname("ftp", "tcp")) != NULL) 664 url->port = ntohs(se->s_port); 665 else 666 url->port = FTP_DEFAULT_PORT; 667 } 668 669 /* try to use previously cached connection */ 670 if (_ftp_isconnected(url)) { 671 e = _ftp_cmd(cached_socket, "NOOP"); 672 if (e == FTP_OK || e == FTP_SYNTAX_ERROR) 673 cd = cached_socket; 674 } 675 676 /* connect to server */ 677 if (cd == -1) { 678 cd = _ftp_connect(url->host, url->port, url->user, url->pwd, flags); 679 if (cd == -1) 680 return -1; 681 if (cached_socket) 682 _ftp_disconnect(cached_socket); 683 cached_socket = cd; 684 memcpy(&cached_host, url, sizeof *url); 685 } 686 687 return cd; 688 } 689 690 /* 691 * Get file 692 */ 693 FILE * 694 fetchGetFTP(struct url *url, char *flags) 695 { 696 int cd; 697 698 /* connect to server */ 699 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 700 return NULL; 701 702 /* initiate the transfer */ 703 return _ftp_transfer(cd, "RETR", url->doc, "r", url->offset, flags); 704 } 705 706 /* 707 * Put file 708 */ 709 FILE * 710 fetchPutFTP(struct url *url, char *flags) 711 { 712 int cd; 713 714 /* connect to server */ 715 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 716 return NULL; 717 718 /* initiate the transfer */ 719 return _ftp_transfer(cd, (flags && strchr(flags, 'a')) ? "APPE" : "STOR", 720 url->doc, "w", url->offset, flags); 721 } 722 723 /* 724 * Get file stats 725 */ 726 int 727 fetchStatFTP(struct url *url, struct url_stat *us, char *flags) 728 { 729 char *ln, *s; 730 struct tm tm; 731 time_t t; 732 int e, cd; 733 734 us->size = -1; 735 us->atime = us->mtime = 0; 736 737 /* connect to server */ 738 if ((cd = _ftp_cached_connect(url, flags)) == NULL) 739 return -1; 740 741 /* change directory */ 742 if (((s = strrchr(url->doc, '/')) != NULL) && (s != url->doc)) { 743 *s = 0; 744 if ((e = _ftp_cmd(cd, "CWD %s", url->doc)) != FTP_FILE_ACTION_OK) { 745 *s = '/'; 746 goto ouch; 747 } 748 *s++ = '/'; 749 } else { 750 if ((e = _ftp_cmd(cd, "CWD /")) != FTP_FILE_ACTION_OK) 751 goto ouch; 752 } 753 754 /* s now points to file name */ 755 756 if (_ftp_cmd(cd, "SIZE %s", s) != FTP_FILE_STATUS) 757 goto ouch; 758 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 759 /* nothing */ ; 760 for (us->size = 0; *ln && isdigit(*ln); ln++) 761 us->size = us->size * 10 + *ln - '0'; 762 if (*ln && !isspace(*ln)) { 763 _ftp_seterr(999); 764 return -1; 765 } 766 DEBUG(fprintf(stderr, "size: [\033[1m%lld\033[m]\n", us->size)); 767 768 if ((e = _ftp_cmd(cd, "MDTM %s", s)) != FTP_FILE_STATUS) 769 goto ouch; 770 for (ln = last_reply + 4; *ln && isspace(*ln); ln++) 771 /* nothing */ ; 772 e = 999; 773 switch (strspn(ln, "0123456789")) { 774 case 14: 775 break; 776 case 15: 777 ln++; 778 ln[0] = '2'; 779 ln[1] = '0'; 780 break; 781 default: 782 goto ouch; 783 } 784 if (sscanf(ln, "%04d%02d%02d%02d%02d%02d", 785 &tm.tm_year, &tm.tm_mon, &tm.tm_mday, 786 &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) 787 goto ouch; 788 tm.tm_mon--; 789 tm.tm_year -= 1900; 790 tm.tm_isdst = -1; 791 t = timegm(&tm); 792 if (t == (time_t)-1) 793 t = time(NULL); 794 us->mtime = t; 795 us->atime = t; 796 DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d " 797 "%02d:%02d:%02d\033[m]\n", 798 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 799 tm.tm_hour, tm.tm_min, tm.tm_sec)); 800 return 0; 801 802 ouch: 803 if (e != -1) 804 _ftp_seterr(e); 805 return -1; 806 } 807 808 /* 809 * List a directory 810 */ 811 extern void warnx(char *, ...); 812 struct url_ent * 813 fetchListFTP(struct url *url, char *flags) 814 { 815 warnx("fetchListFTP(): not implemented"); 816 return NULL; 817 } 818