2 * Copyright (c) 1998 Dag-Erling Coïdan Smørgrav
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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
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.
28 * $FreeBSD: src/lib/libfetch/ftp.c,v 1.16.2.31 2003/06/06 06:45:25 des Exp $
29 * $DragonFly: src/lib/libfetch/ftp.c,v 1.3 2003/12/01 22:58:44 drhodus Exp $
33 * Portions of this code were taken from or based on ftpio.c:
35 * ----------------------------------------------------------------------------
36 * "THE BEER-WARE LICENSE" (Revision 42):
37 * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
38 * can do whatever you want with this stuff. If we meet some day, and you think
39 * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
40 * ----------------------------------------------------------------------------
44 * Dag-Erling Coïdan Smørgrav
47 * Incorporated into libfetch
52 * Turned inside out. Now returns xfers as new file ids, not as a special
55 * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
59 #include <sys/param.h>
60 #include <sys/socket.h>
61 #include <netinet/in.h>
79 #define FTP_ANONYMOUS_USER "anonymous"
81 #define FTP_CONNECTION_ALREADY_OPEN 125
82 #define FTP_OPEN_DATA_CONNECTION 150
84 #define FTP_FILE_STATUS 213
85 #define FTP_SERVICE_READY 220
86 #define FTP_TRANSFER_COMPLETE 226
87 #define FTP_PASSIVE_MODE 227
88 #define FTP_LPASSIVE_MODE 228
89 #define FTP_EPASSIVE_MODE 229
90 #define FTP_LOGGED_IN 230
91 #define FTP_FILE_ACTION_OK 250
92 #define FTP_NEED_PASSWORD 331
93 #define FTP_NEED_ACCOUNT 332
94 #define FTP_FILE_OK 350
95 #define FTP_SYNTAX_ERROR 500
96 #define FTP_PROTOCOL_ERROR 999
98 static struct url cached_host
;
99 static conn_t
*cached_connection
;
101 #define isftpreply(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
103 && (foo[3] == ' ' || foo[3] == '\0'))
104 #define isftpinfo(foo) (isdigit(foo[0]) && isdigit(foo[1]) \
105 && isdigit(foo[2]) && foo[3] == '-')
108 * Translate IPv4 mapped IPv6 address to IPv4 address
111 unmappedaddr(struct sockaddr_in6
*sin6
)
113 struct sockaddr_in
*sin4
;
117 if (sin6
->sin6_family
!= AF_INET6
||
118 !IN6_IS_ADDR_V4MAPPED(&sin6
->sin6_addr
))
120 sin4
= (struct sockaddr_in
*)sin6
;
121 addr
= *(u_int32_t
*)&sin6
->sin6_addr
.s6_addr
[12];
122 port
= sin6
->sin6_port
;
123 memset(sin4
, 0, sizeof(struct sockaddr_in
));
124 sin4
->sin_addr
.s_addr
= addr
;
125 sin4
->sin_port
= port
;
126 sin4
->sin_family
= AF_INET
;
127 sin4
->sin_len
= sizeof(struct sockaddr_in
);
131 * Get server response
134 _ftp_chkerr(conn_t
*conn
)
136 if (_fetch_getln(conn
) == -1) {
140 if (isftpinfo(conn
->buf
)) {
141 while (conn
->buflen
&& !isftpreply(conn
->buf
)) {
142 if (_fetch_getln(conn
) == -1) {
149 while (conn
->buflen
&& isspace(conn
->buf
[conn
->buflen
- 1]))
151 conn
->buf
[conn
->buflen
] = '\0';
153 if (!isftpreply(conn
->buf
)) {
154 _ftp_seterr(FTP_PROTOCOL_ERROR
);
158 conn
->err
= (conn
->buf
[0] - '0') * 100
159 + (conn
->buf
[1] - '0') * 10
160 + (conn
->buf
[2] - '0');
166 * Send a command and check reply
169 _ftp_cmd(conn_t
*conn
, const char *fmt
, ...)
177 len
= vasprintf(&msg
, fmt
, ap
);
186 r
= _fetch_putln(conn
, msg
, len
);
194 return (_ftp_chkerr(conn
));
198 * Return a pointer to the filename part of a path
201 _ftp_filename(const char *file
)
205 if ((s
= strrchr(file
, '/')) == NULL
)
212 * Change working directory to the directory that contains the specified
216 _ftp_cwd(conn_t
*conn
, const char *file
)
221 if ((s
= strrchr(file
, '/')) == NULL
|| s
== file
) {
222 e
= _ftp_cmd(conn
, "CWD /");
224 e
= _ftp_cmd(conn
, "CWD %.*s", s
- file
, file
);
226 if (e
!= FTP_FILE_ACTION_OK
) {
234 * Request and parse file stats
237 _ftp_stat(conn_t
*conn
, const char *file
, struct url_stat
*us
)
246 us
->atime
= us
->mtime
= 0;
248 if ((s
= strrchr(file
, '/')) == NULL
)
253 if ((e
= _ftp_cmd(conn
, "SIZE %s", s
)) != FTP_FILE_STATUS
) {
257 for (ln
= conn
->buf
+ 4; *ln
&& isspace(*ln
); ln
++)
259 for (us
->size
= 0; *ln
&& isdigit(*ln
); ln
++)
260 us
->size
= us
->size
* 10 + *ln
- '0';
261 if (*ln
&& !isspace(*ln
)) {
262 _ftp_seterr(FTP_PROTOCOL_ERROR
);
268 DEBUG(fprintf(stderr
, "size: [%lld]\n", (long long)us
->size
));
270 if ((e
= _ftp_cmd(conn
, "MDTM %s", s
)) != FTP_FILE_STATUS
) {
274 for (ln
= conn
->buf
+ 4; *ln
&& isspace(*ln
); ln
++)
276 switch (strspn(ln
, "0123456789")) {
285 _ftp_seterr(FTP_PROTOCOL_ERROR
);
288 if (sscanf(ln
, "%04d%02d%02d%02d%02d%02d",
289 &tm
.tm_year
, &tm
.tm_mon
, &tm
.tm_mday
,
290 &tm
.tm_hour
, &tm
.tm_min
, &tm
.tm_sec
) != 6) {
291 _ftp_seterr(FTP_PROTOCOL_ERROR
);
302 DEBUG(fprintf(stderr
,
303 "last modified: [%04d-%02d-%02d %02d:%02d:%02d]\n",
304 tm
.tm_year
+ 1900, tm
.tm_mon
+ 1, tm
.tm_mday
,
305 tm
.tm_hour
, tm
.tm_min
, tm
.tm_sec
));
310 * I/O functions for FTP
313 conn_t
*cconn
; /* Control connection */
314 conn_t
*dconn
; /* Data connection */
315 int dir
; /* Direction */
316 int eof
; /* EOF reached */
317 int err
; /* Error code */
320 static int _ftp_readfn(void *, char *, int);
321 static int _ftp_writefn(void *, const char *, int);
322 static fpos_t _ftp_seekfn(void *, fpos_t, int);
323 static int _ftp_closefn(void *);
326 _ftp_readfn(void *v
, char *buf
, int len
)
331 io
= (struct ftpio
*)v
;
336 if (io
->cconn
== NULL
|| io
->dconn
== NULL
|| io
->dir
== O_WRONLY
) {
346 r
= _fetch_read(io
->dconn
, buf
, len
);
359 _ftp_writefn(void *v
, const char *buf
, int len
)
364 io
= (struct ftpio
*)v
;
369 if (io
->cconn
== NULL
|| io
->dconn
== NULL
|| io
->dir
== O_RDONLY
) {
377 w
= _fetch_write(io
->dconn
, buf
, len
);
386 _ftp_seekfn(void *v
, fpos_t pos __unused
, int whence __unused
)
390 io
= (struct ftpio
*)v
;
400 _ftp_closefn(void *v
)
405 io
= (struct ftpio
*)v
;
412 if (io
->cconn
== NULL
|| io
->dconn
== NULL
) {
416 _fetch_close(io
->dconn
);
419 DEBUG(fprintf(stderr
, "Waiting for final status\n"));
420 r
= _ftp_chkerr(io
->cconn
);
421 if (io
->cconn
== cached_connection
&& io
->cconn
->ref
== 1)
422 cached_connection
= NULL
;
423 _fetch_close(io
->cconn
);
425 return (r
== FTP_TRANSFER_COMPLETE
) ? 0 : -1;
429 _ftp_setup(conn_t
*cconn
, conn_t
*dconn
, int mode
)
434 if (cconn
== NULL
|| dconn
== NULL
)
436 if ((io
= malloc(sizeof(*io
))) == NULL
)
441 io
->eof
= io
->err
= 0;
442 f
= funopen(io
, _ftp_readfn
, _ftp_writefn
, _ftp_seekfn
, _ftp_closefn
);
452 _ftp_transfer(conn_t
*conn
, const char *oper
, const char *file
,
453 int mode
, off_t offset
, const char *flags
)
455 struct sockaddr_storage sa
;
456 struct sockaddr_in6
*sin6
;
457 struct sockaddr_in
*sin4
;
458 int low
, pasv
, verbose
;
465 low
= CHECK_FLAG('l');
466 pasv
= CHECK_FLAG('p');
467 verbose
= CHECK_FLAG('v');
471 pasv
= ((s
= getenv("FTP_PASSIVE_MODE")) != NULL
&&
472 strncasecmp(s
, "no", 2) != 0);
474 /* find our own address, bind, and listen */
476 if (getsockname(conn
->sd
, (struct sockaddr
*)&sa
, &l
) == -1)
478 if (sa
.ss_family
== AF_INET6
)
479 unmappedaddr((struct sockaddr_in6
*)&sa
);
481 /* open data socket */
482 if ((sd
= socket(sa
.ss_family
, SOCK_STREAM
, IPPROTO_TCP
)) == -1) {
493 /* send PASV command */
495 _fetch_info("setting passive mode");
496 switch (sa
.ss_family
) {
498 if ((e
= _ftp_cmd(conn
, "PASV")) != FTP_PASSIVE_MODE
)
502 if ((e
= _ftp_cmd(conn
, "EPSV")) != FTP_EPASSIVE_MODE
) {
505 if ((e
= _ftp_cmd(conn
, "LPSV")) !=
511 e
= FTP_PROTOCOL_ERROR
; /* XXX: error code should be prepared */
516 * Find address and port number. The reply to the PASV command
517 * is IMHO the one and only weak point in the FTP protocol.
521 case FTP_PASSIVE_MODE
:
522 case FTP_LPASSIVE_MODE
:
523 for (p
= ln
+ 3; *p
&& !isdigit(*p
); p
++)
526 e
= FTP_PROTOCOL_ERROR
;
529 l
= (e
== FTP_PASSIVE_MODE
? 6 : 21);
530 for (i
= 0; *p
&& i
< l
; i
++, p
++)
531 addr
[i
] = strtol(p
, &p
, 10);
533 e
= FTP_PROTOCOL_ERROR
;
537 case FTP_EPASSIVE_MODE
:
538 for (p
= ln
+ 3; *p
&& *p
!= '('; p
++)
541 e
= FTP_PROTOCOL_ERROR
;
545 if (sscanf(p
, "%c%c%c%d%c", &addr
[0], &addr
[1], &addr
[2],
546 &port
, &addr
[3]) != 5 ||
547 addr
[0] != addr
[1] ||
548 addr
[0] != addr
[2] || addr
[0] != addr
[3]) {
549 e
= FTP_PROTOCOL_ERROR
;
555 /* seek to required offset */
557 if (_ftp_cmd(conn
, "REST %lu", (u_long
)offset
) != FTP_FILE_OK
)
560 /* construct sockaddr for data socket */
562 if (getpeername(conn
->sd
, (struct sockaddr
*)&sa
, &l
) == -1)
564 if (sa
.ss_family
== AF_INET6
)
565 unmappedaddr((struct sockaddr_in6
*)&sa
);
566 switch (sa
.ss_family
) {
568 sin6
= (struct sockaddr_in6
*)&sa
;
569 if (e
== FTP_EPASSIVE_MODE
)
570 sin6
->sin6_port
= htons(port
);
572 bcopy(addr
+ 2, (char *)&sin6
->sin6_addr
, 16);
573 bcopy(addr
+ 19, (char *)&sin6
->sin6_port
, 2);
577 sin4
= (struct sockaddr_in
*)&sa
;
578 if (e
== FTP_EPASSIVE_MODE
)
579 sin4
->sin_port
= htons(port
);
581 bcopy(addr
, (char *)&sin4
->sin_addr
, 4);
582 bcopy(addr
+ 4, (char *)&sin4
->sin_port
, 2);
586 e
= FTP_PROTOCOL_ERROR
; /* XXX: error code should be prepared */
590 /* connect to data port */
592 _fetch_info("opening data connection");
593 if (connect(sd
, (struct sockaddr
*)&sa
, sa
.ss_len
) == -1)
596 /* make the server initiate the transfer */
598 _fetch_info("initiating transfer");
599 e
= _ftp_cmd(conn
, "%s %s", oper
, _ftp_filename(file
));
600 if (e
!= FTP_CONNECTION_ALREADY_OPEN
&& e
!= FTP_OPEN_DATA_CONNECTION
)
608 char hname
[INET6_ADDRSTRLEN
];
610 switch (sa
.ss_family
) {
612 ((struct sockaddr_in6
*)&sa
)->sin6_port
= 0;
613 #ifdef IPV6_PORTRANGE
614 arg
= low
? IPV6_PORTRANGE_DEFAULT
: IPV6_PORTRANGE_HIGH
;
615 if (setsockopt(sd
, IPPROTO_IPV6
, IPV6_PORTRANGE
,
616 (char *)&arg
, sizeof(arg
)) == -1)
621 ((struct sockaddr_in
*)&sa
)->sin_port
= 0;
622 arg
= low
? IP_PORTRANGE_DEFAULT
: IP_PORTRANGE_HIGH
;
623 if (setsockopt(sd
, IPPROTO_IP
, IP_PORTRANGE
,
624 (char *)&arg
, sizeof(arg
)) == -1)
629 _fetch_info("binding data socket");
630 if (bind(sd
, (struct sockaddr
*)&sa
, sa
.ss_len
) == -1)
632 if (listen(sd
, 1) == -1)
635 /* find what port we're on and tell the server */
636 if (getsockname(sd
, (struct sockaddr
*)&sa
, &l
) == -1)
638 switch (sa
.ss_family
) {
640 sin4
= (struct sockaddr_in
*)&sa
;
641 a
= ntohl(sin4
->sin_addr
.s_addr
);
642 p
= ntohs(sin4
->sin_port
);
643 e
= _ftp_cmd(conn
, "PORT %d,%d,%d,%d,%d,%d",
644 (a
>> 24) & 0xff, (a
>> 16) & 0xff,
645 (a
>> 8) & 0xff, a
& 0xff,
646 (p
>> 8) & 0xff, p
& 0xff);
649 #define UC(b) (((int)b)&0xff)
651 sin6
= (struct sockaddr_in6
*)&sa
;
652 sin6
->sin6_scope_id
= 0;
653 if (getnameinfo((struct sockaddr
*)&sa
, sa
.ss_len
,
654 hname
, sizeof(hname
),
655 NULL
, 0, NI_NUMERICHOST
) == 0) {
656 e
= _ftp_cmd(conn
, "EPRT |%d|%s|%d|", 2, hname
,
657 htons(sin6
->sin6_port
));
662 ap
= (char *)&sin6
->sin6_addr
;
664 "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
666 UC(ap
[0]), UC(ap
[1]), UC(ap
[2]), UC(ap
[3]),
667 UC(ap
[4]), UC(ap
[5]), UC(ap
[6]), UC(ap
[7]),
668 UC(ap
[8]), UC(ap
[9]), UC(ap
[10]), UC(ap
[11]),
669 UC(ap
[12]), UC(ap
[13]), UC(ap
[14]), UC(ap
[15]),
671 (ntohs(sin6
->sin6_port
) >> 8) & 0xff,
672 ntohs(sin6
->sin6_port
) & 0xff);
676 e
= FTP_PROTOCOL_ERROR
; /* XXX: error code should be prepared */
682 /* seek to required offset */
684 if (_ftp_cmd(conn
, "REST %llu",
685 (unsigned long long)offset
) != FTP_FILE_OK
)
688 /* make the server initiate the transfer */
690 _fetch_info("initiating transfer");
691 e
= _ftp_cmd(conn
, "%s %s", oper
, _ftp_filename(file
));
692 if (e
!= FTP_OPEN_DATA_CONNECTION
)
695 /* accept the incoming connection and go to town */
696 if ((d
= accept(sd
, NULL
, NULL
)) == -1)
702 if ((df
= _ftp_setup(conn
, _fetch_reopen(sd
), mode
)) == NULL
)
724 _ftp_authenticate(conn_t
*conn
, struct url
*url
, struct url
*purl
)
726 const char *user
, *pwd
, *logname
;
727 char pbuf
[MAXHOSTNAMELEN
+ MAXLOGNAME
+ 1];
730 /* XXX FTP_AUTH, and maybe .netrc */
732 /* send user name and password */
733 if (url
->user
[0] == '\0')
734 _fetch_netrc_auth(url
);
737 user
= getenv("FTP_LOGIN");
738 if (user
== NULL
|| *user
== '\0')
739 user
= FTP_ANONYMOUS_USER
;
740 if (purl
&& url
->port
== _fetch_default_port(url
->scheme
))
741 e
= _ftp_cmd(conn
, "USER %s@%s", user
, url
->host
);
743 e
= _ftp_cmd(conn
, "USER %s@%s@%d", user
, url
->host
, url
->port
);
745 e
= _ftp_cmd(conn
, "USER %s", user
);
747 /* did the server request a password? */
748 if (e
== FTP_NEED_PASSWORD
) {
751 pwd
= getenv("FTP_PASSWORD");
752 if (pwd
== NULL
|| *pwd
== '\0') {
753 if ((logname
= getlogin()) == 0)
754 logname
= FTP_ANONYMOUS_USER
;
755 if ((len
= snprintf(pbuf
, MAXLOGNAME
+ 1, "%s@", logname
)) < 0)
757 else if (len
> MAXLOGNAME
)
759 gethostname(pbuf
+ len
, sizeof(pbuf
) - len
);
762 e
= _ftp_cmd(conn
, "PASS %s", pwd
);
769 * Log on to FTP server
772 _ftp_connect(struct url
*url
, struct url
*purl
, const char *flags
)
775 int e
, direct
, verbose
;
782 direct
= CHECK_FLAG('d');
783 verbose
= CHECK_FLAG('v');
786 else if (CHECK_FLAG('6'))
792 /* check for proxy */
794 /* XXX proxy authentication! */
795 conn
= _fetch_connect(purl
->host
, purl
->port
, af
, verbose
);
797 /* no proxy, go straight to target */
798 conn
= _fetch_connect(url
->host
, url
->port
, af
, verbose
);
802 /* check connection */
804 /* _fetch_connect() has already set an error code */
807 /* expect welcome message */
808 if ((e
= _ftp_chkerr(conn
)) != FTP_SERVICE_READY
)
812 if ((e
= _ftp_authenticate(conn
, url
, purl
)) != FTP_LOGGED_IN
)
815 /* might as well select mode and type at once */
816 #ifdef FTP_FORCE_STREAM_MODE
817 if ((e
= _ftp_cmd(conn
, "MODE S")) != FTP_OK
) /* default is S */
820 if ((e
= _ftp_cmd(conn
, "TYPE I")) != FTP_OK
) /* default is A */
834 * Disconnect from server
837 _ftp_disconnect(conn_t
*conn
)
839 (void)_ftp_cmd(conn
, "QUIT");
840 if (conn
== cached_connection
&& conn
->ref
== 1)
841 cached_connection
= NULL
;
846 * Check if we're already connected
849 _ftp_isconnected(struct url
*url
)
851 return (cached_connection
852 && (strcmp(url
->host
, cached_host
.host
) == 0)
853 && (strcmp(url
->user
, cached_host
.user
) == 0)
854 && (strcmp(url
->pwd
, cached_host
.pwd
) == 0)
855 && (url
->port
== cached_host
.port
));
859 * Check the cache, reconnect if no luck
862 _ftp_cached_connect(struct url
*url
, struct url
*purl
, const char *flags
)
867 /* set default port */
869 url
->port
= _fetch_default_port(url
->scheme
);
871 /* try to use previously cached connection */
872 if (_ftp_isconnected(url
)) {
873 e
= _ftp_cmd(cached_connection
, "NOOP");
874 if (e
== FTP_OK
|| e
== FTP_SYNTAX_ERROR
)
875 return (_fetch_ref(cached_connection
));
878 /* connect to server */
879 if ((conn
= _ftp_connect(url
, purl
, flags
)) == NULL
)
881 if (cached_connection
)
882 _ftp_disconnect(cached_connection
);
883 cached_connection
= _fetch_ref(conn
);
884 memcpy(&cached_host
, url
, sizeof(*url
));
889 * Check the proxy settings
892 _ftp_get_proxy(const char *flags
)
897 if (flags
!= NULL
&& strchr(flags
, 'd') != NULL
)
899 if (((p
= getenv("FTP_PROXY")) || (p
= getenv("ftp_proxy")) ||
900 (p
= getenv("HTTP_PROXY")) || (p
= getenv("http_proxy"))) &&
901 *p
&& (purl
= fetchParseURL(p
)) != NULL
) {
902 if (!*purl
->scheme
) {
903 if (getenv("FTP_PROXY") || getenv("ftp_proxy"))
904 strcpy(purl
->scheme
, SCHEME_FTP
);
906 strcpy(purl
->scheme
, SCHEME_HTTP
);
909 purl
->port
= _fetch_default_proxy_port(purl
->scheme
);
910 if (strcasecmp(purl
->scheme
, SCHEME_FTP
) == 0 ||
911 strcasecmp(purl
->scheme
, SCHEME_HTTP
) == 0)
919 * Process an FTP request
922 _ftp_request(struct url
*url
, const char *op
, struct url_stat
*us
,
923 struct url
*purl
, const char *flags
)
928 /* check if we should use HTTP instead */
929 if (purl
&& strcasecmp(purl
->scheme
, SCHEME_HTTP
) == 0) {
930 if (strcmp(op
, "STAT") == 0)
931 return (_http_request(url
, "HEAD", us
, purl
, flags
));
932 else if (strcmp(op
, "RETR") == 0)
933 return (_http_request(url
, "GET", us
, purl
, flags
));
935 * Our HTTP code doesn't support PUT requests yet, so try
936 * a direct connection.
940 /* connect to server */
941 conn
= _ftp_cached_connect(url
, purl
, flags
);
947 /* change directory */
948 if (_ftp_cwd(conn
, url
->doc
) == -1)
952 if (us
&& _ftp_stat(conn
, url
->doc
, us
) == -1
953 && fetchLastErrCode
!= FETCH_PROTO
954 && fetchLastErrCode
!= FETCH_UNAVAIL
)
958 if (strcmp(op
, "STAT") == 0)
959 return (FILE *)1; /* bogus return value */
960 if (strcmp(op
, "STOR") == 0 || strcmp(op
, "APPE") == 0)
965 /* initiate the transfer */
966 return (_ftp_transfer(conn
, op
, url
->doc
, oflag
, url
->offset
, flags
));
973 fetchXGetFTP(struct url
*url
, struct url_stat
*us
, const char *flags
)
975 return (_ftp_request(url
, "RETR", us
, _ftp_get_proxy(flags
), flags
));
982 fetchGetFTP(struct url
*url
, const char *flags
)
984 return (fetchXGetFTP(url
, NULL
, flags
));
991 fetchPutFTP(struct url
*url
, const char *flags
)
994 return (_ftp_request(url
, CHECK_FLAG('a') ? "APPE" : "STOR", NULL
,
995 _ftp_get_proxy(flags
), flags
));
1002 fetchStatFTP(struct url
*url
, struct url_stat
*us
, const char *flags
)
1006 f
= _ftp_request(url
, "STAT", us
, _ftp_get_proxy(flags
), flags
);
1016 fetchListFTP(struct url
*url __unused
, const char *flags __unused
)
1018 warnx("fetchListFTP(): not implemented");