2 * SPDX-License-Identifier: (BSD-3-Clause AND Beerware)
4 * Copyright (c) 1998-2011 Dag-Erling Smørgrav
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer
12 * in this position and unchanged.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. The name of the author may not be used to endorse or promote products
17 * derived from this software without specific prior written permission
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 * $FreeBSD: head/lib/libfetch/ftp.c 341013 2018-11-27 10:45:14Z des $
34 * Portions of this code were taken from or based on ftpio.c:
36 * ----------------------------------------------------------------------------
37 * "THE BEER-WARE LICENSE" (Revision 42):
38 * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
39 * can do whatever you want with this stuff. If we meet some day, and you think
40 * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
41 * ----------------------------------------------------------------------------
45 * Dag-Erling Smørgrav
48 * Incorporated into libfetch
53 * Turned inside out. Now returns xfers as new file ids, not as a special
56 * $ftpioId: ftpio.c,v 1.30 1998/04/11 07:28:53 phk Exp $
60 #include <sys/param.h>
61 #include <sys/socket.h>
62 #include <netinet/in.h>
81 #define FTP_ANONYMOUS_USER "anonymous"
83 #define FTP_CONNECTION_ALREADY_OPEN 125
84 #define FTP_OPEN_DATA_CONNECTION 150
86 #define FTP_FILE_STATUS 213
87 #define FTP_SERVICE_READY 220
88 #define FTP_TRANSFER_COMPLETE 226
89 #define FTP_PASSIVE_MODE 227
90 #define FTP_LPASSIVE_MODE 228
91 #define FTP_EPASSIVE_MODE 229
92 #define FTP_LOGGED_IN 230
93 #define FTP_FILE_ACTION_OK 250
94 #define FTP_DIRECTORY_CREATED 257 /* multiple meanings */
95 #define FTP_FILE_CREATED 257 /* multiple meanings */
96 #define FTP_WORKING_DIRECTORY 257 /* multiple meanings */
97 #define FTP_NEED_PASSWORD 331
98 #define FTP_NEED_ACCOUNT 332
99 #define FTP_FILE_OK 350
100 #define FTP_SYNTAX_ERROR 500
101 #define FTP_PROTOCOL_ERROR 999
103 static int ftp_cmd(conn_t
*, const char *, ...) __printflike(2, 3);
104 static struct url cached_host
;
105 static conn_t
*cached_connection
;
107 #define isftpreply(foo) \
108 (isdigit((unsigned char)foo[0]) && \
109 isdigit((unsigned char)foo[1]) && \
110 isdigit((unsigned char)foo[2]) && \
111 (foo[3] == ' ' || foo[3] == '\0'))
112 #define isftpinfo(foo) \
113 (isdigit((unsigned char)foo[0]) && \
114 isdigit((unsigned char)foo[1]) && \
115 isdigit((unsigned char)foo[2]) && \
119 * Translate IPv4 mapped IPv6 address to IPv4 address
122 unmappedaddr(struct sockaddr_in6
*sin6
)
124 struct sockaddr_in
*sin4
;
128 if (sin6
->sin6_family
!= AF_INET6
||
129 !IN6_IS_ADDR_V4MAPPED(&sin6
->sin6_addr
))
131 sin4
= (struct sockaddr_in
*)sin6
;
132 addr
= &sin6
->sin6_addr
.s6_addr
[12];
133 port
= sin6
->sin6_port
;
134 memset(sin4
, 0, sizeof(struct sockaddr_in
));
135 sin4
->sin_addr
.s_addr
= *(uint32_t *)addr
;
136 sin4
->sin_port
= port
;
137 sin4
->sin_family
= AF_INET
;
138 sin4
->sin_len
= sizeof(struct sockaddr_in
);
142 * Get server response
145 ftp_chkerr(conn_t
*conn
)
147 if (fetch_getln(conn
) == -1) {
151 if (isftpinfo(conn
->buf
)) {
152 while (conn
->buflen
&& !isftpreply(conn
->buf
)) {
153 if (fetch_getln(conn
) == -1) {
160 while (conn
->buflen
&&
161 isspace((unsigned char)conn
->buf
[conn
->buflen
- 1]))
163 conn
->buf
[conn
->buflen
] = '\0';
165 if (!isftpreply(conn
->buf
)) {
166 ftp_seterr(FTP_PROTOCOL_ERROR
);
170 conn
->err
= (conn
->buf
[0] - '0') * 100
171 + (conn
->buf
[1] - '0') * 10
172 + (conn
->buf
[2] - '0');
178 * Send a command and check reply
181 ftp_cmd(conn_t
*conn
, const char *fmt
, ...)
189 len
= vasprintf(&msg
, fmt
, ap
);
198 r
= fetch_putln(conn
, msg
, len
);
206 return (ftp_chkerr(conn
));
210 * Return a pointer to the filename part of a path
213 ftp_filename(const char *file
, int *len
, int *type
)
217 if ((s
= strrchr(file
, '/')) == NULL
)
222 if (*len
> 7 && strncmp(s
+ *len
- 7, ";type=", 6) == 0) {
232 * Get current working directory from the reply to a CWD, PWD or CDUP
236 ftp_pwd(conn_t
*conn
, char *pwd
, size_t pwdlen
)
238 char *src
, *dst
, *end
;
241 if (conn
->err
!= FTP_WORKING_DIRECTORY
&&
242 conn
->err
!= FTP_FILE_ACTION_OK
)
243 return (FTP_PROTOCOL_ERROR
);
244 end
= conn
->buf
+ conn
->buflen
;
246 if (src
>= end
|| *src
++ != '"')
247 return (FTP_PROTOCOL_ERROR
);
248 for (q
= 0, dst
= pwd
; src
< end
&& pwdlen
--; ++src
) {
249 if (!q
&& *src
== '"')
251 else if (q
&& *src
!= '"')
259 return (FTP_PROTOCOL_ERROR
);
262 DEBUGF("pwd: [%s]\n", pwd
);
268 * Change working directory to the directory that contains the specified
272 ftp_cwd(conn_t
*conn
, const char *file
)
274 const char *beg
, *end
;
278 /* If no slashes in name, no need to change dirs. */
279 if ((end
= strrchr(file
, '/')) == NULL
)
281 if ((e
= ftp_cmd(conn
, "PWD")) != FTP_WORKING_DIRECTORY
||
282 (e
= ftp_pwd(conn
, pwd
, sizeof(pwd
))) != FTP_OK
) {
289 /* Look for a common prefix between PWD and dir to fetch. */
290 for (i
= 0; i
<= len
&& i
<= end
- file
; ++i
)
291 if (pwd
[i
] != file
[i
])
294 DEBUGF("have: [%.*s|%s]\n", i
, pwd
, pwd
+ i
);
295 DEBUGF("want: [%.*s|%s]\n", i
, file
, file
+ i
);
297 /* Keep going up a dir until we have a matching prefix. */
298 if (pwd
[i
] == '\0' && (file
[i
- 1] == '/' || file
[i
] == '/'))
300 if ((e
= ftp_cmd(conn
, "CDUP")) != FTP_FILE_ACTION_OK
||
301 (e
= ftp_cmd(conn
, "PWD")) != FTP_WORKING_DIRECTORY
||
302 (e
= ftp_pwd(conn
, pwd
, sizeof(pwd
))) != FTP_OK
) {
308 #ifdef FTP_COMBINE_CWDS
309 /* Skip leading slashes, even "////". */
310 for (beg
= file
+ i
; beg
< end
&& *beg
== '/'; ++beg
, ++i
)
313 /* If there is no trailing dir, we're already there. */
317 /* Change to the directory all in one chunk (e.g., foo/bar/baz). */
318 e
= ftp_cmd(conn
, "CWD %.*s", (int)(end
- beg
), beg
);
319 if (e
== FTP_FILE_ACTION_OK
)
321 #endif /* FTP_COMBINE_CWDS */
323 /* That didn't work so go back to legacy behavior (multiple CWDs). */
324 for (beg
= file
+ i
; beg
< end
; beg
= file
+ i
+ 1) {
327 for (++i
; file
+ i
< end
&& file
[i
] != '/'; ++i
)
329 e
= ftp_cmd(conn
, "CWD %.*s", (int)(file
+ i
- beg
), beg
);
330 if (e
!= FTP_FILE_ACTION_OK
) {
339 * Set transfer mode and data type
342 ftp_mode_type(conn_t
*conn
, int mode
, int type
)
353 return (FTP_PROTOCOL_ERROR
);
355 if ((e
= ftp_cmd(conn
, "MODE %c", mode
)) != FTP_OK
) {
358 * Stream mode is supposed to be the default - so
359 * much so that some servers not only do not
360 * support any other mode, but do not support the
361 * MODE command at all.
363 * If "MODE S" fails, it is unlikely that we
364 * previously succeeded in setting a different
365 * mode. Therefore, we simply hope that the
366 * server is already in the correct mode, and
367 * silently ignore the failure.
388 /* can't handle yet */
390 return (FTP_PROTOCOL_ERROR
);
392 if ((e
= ftp_cmd(conn
, "TYPE %c", type
)) != FTP_OK
)
399 * Request and parse file stats
402 ftp_stat(conn_t
*conn
, const char *file
, struct url_stat
*us
)
405 const char *filename
;
406 int filenamelen
, type
;
412 us
->atime
= us
->mtime
= 0;
414 filename
= ftp_filename(file
, &filenamelen
, &type
);
416 if ((e
= ftp_mode_type(conn
, 0, type
)) != FTP_OK
) {
421 e
= ftp_cmd(conn
, "SIZE %.*s", filenamelen
, filename
);
422 if (e
!= FTP_FILE_STATUS
) {
426 for (ln
= conn
->buf
+ 4; *ln
&& isspace((unsigned char)*ln
); ln
++)
428 for (us
->size
= 0; *ln
&& isdigit((unsigned char)*ln
); ln
++)
429 us
->size
= us
->size
* 10 + *ln
- '0';
430 if (*ln
&& !isspace((unsigned char)*ln
)) {
431 ftp_seterr(FTP_PROTOCOL_ERROR
);
437 DEBUGF("size: [%lld]\n", (long long)us
->size
);
439 e
= ftp_cmd(conn
, "MDTM %.*s", filenamelen
, filename
);
440 if (e
!= FTP_FILE_STATUS
) {
444 for (ln
= conn
->buf
+ 4; *ln
&& isspace((unsigned char)*ln
); ln
++)
446 switch (strspn(ln
, "0123456789")) {
455 ftp_seterr(FTP_PROTOCOL_ERROR
);
458 if (sscanf(ln
, "%04d%02d%02d%02d%02d%02d",
459 &tm
.tm_year
, &tm
.tm_mon
, &tm
.tm_mday
,
460 &tm
.tm_hour
, &tm
.tm_min
, &tm
.tm_sec
) != 6) {
461 ftp_seterr(FTP_PROTOCOL_ERROR
);
472 DEBUGF("last modified: [%04d-%02d-%02d %02d:%02d:%02d]\n",
473 tm
.tm_year
+ 1900, tm
.tm_mon
+ 1, tm
.tm_mday
,
474 tm
.tm_hour
, tm
.tm_min
, tm
.tm_sec
);
479 * I/O functions for FTP
482 conn_t
*cconn
; /* Control connection */
483 conn_t
*dconn
; /* Data connection */
484 int dir
; /* Direction */
485 int eof
; /* EOF reached */
486 int err
; /* Error code */
489 static int ftp_readfn(void *, char *, int);
490 static int ftp_writefn(void *, const char *, int);
491 static fpos_t ftp_seekfn(void *, fpos_t, int);
492 static int ftp_closefn(void *);
495 ftp_readfn(void *v
, char *buf
, int len
)
500 io
= (struct ftpio
*)v
;
505 if (io
->cconn
== NULL
|| io
->dconn
== NULL
|| io
->dir
== O_WRONLY
) {
515 r
= fetch_read(io
->dconn
, buf
, len
);
528 ftp_writefn(void *v
, const char *buf
, int len
)
533 io
= (struct ftpio
*)v
;
538 if (io
->cconn
== NULL
|| io
->dconn
== NULL
|| io
->dir
== O_RDONLY
) {
546 w
= fetch_write(io
->dconn
, buf
, len
);
555 ftp_seekfn(void *v
, fpos_t pos __unused
, int whence __unused
)
559 io
= (struct ftpio
*)v
;
574 io
= (struct ftpio
*)v
;
581 if (io
->cconn
== NULL
|| io
->dconn
== NULL
) {
585 fetch_close(io
->dconn
);
588 DEBUGF("Waiting for final status\n");
589 r
= ftp_chkerr(io
->cconn
);
590 if (io
->cconn
== cached_connection
&& io
->cconn
->ref
== 1)
591 cached_connection
= NULL
;
592 fetch_close(io
->cconn
);
594 return (r
== FTP_TRANSFER_COMPLETE
) ? 0 : -1;
598 ftp_setup(conn_t
*cconn
, conn_t
*dconn
, int mode
)
603 if (cconn
== NULL
|| dconn
== NULL
)
605 if ((io
= malloc(sizeof(*io
))) == NULL
)
610 io
->eof
= io
->err
= 0;
611 f
= funopen(io
, ftp_readfn
, ftp_writefn
, ftp_seekfn
, ftp_closefn
);
621 ftp_transfer(conn_t
*conn
, const char *oper
, const char *file
,
622 int mode
, off_t offset
, const char *flags
)
624 struct sockaddr_storage sa
;
625 struct sockaddr_in6
*sin6
;
626 struct sockaddr_in
*sin4
;
627 const char *bindaddr
;
628 const char *filename
;
629 int filenamelen
, type
;
630 int low
, pasv
, verbose
;
637 low
= CHECK_FLAG('l');
638 pasv
= CHECK_FLAG('p') || !CHECK_FLAG('P');
639 verbose
= CHECK_FLAG('v');
642 if ((s
= getenv("FTP_PASSIVE_MODE")) != NULL
)
643 pasv
= (strncasecmp(s
, "no", 2) != 0);
645 /* isolate filename */
646 filename
= ftp_filename(file
, &filenamelen
, &type
);
648 /* set transfer mode and data type */
649 if ((e
= ftp_mode_type(conn
, 0, type
)) != FTP_OK
)
652 /* find our own address, bind, and listen */
654 if (getsockname(conn
->sd
, (struct sockaddr
*)&sa
, &l
) == -1)
656 if (sa
.ss_family
== AF_INET6
)
657 unmappedaddr((struct sockaddr_in6
*)&sa
);
659 /* open data socket */
660 if ((sd
= socket(sa
.ss_family
, SOCK_STREAM
, IPPROTO_TCP
)) == -1) {
671 /* send PASV command */
673 fetch_info("setting passive mode");
674 switch (sa
.ss_family
) {
676 if ((e
= ftp_cmd(conn
, "PASV")) != FTP_PASSIVE_MODE
)
680 if ((e
= ftp_cmd(conn
, "EPSV")) != FTP_EPASSIVE_MODE
) {
683 if ((e
= ftp_cmd(conn
, "LPSV")) !=
689 e
= FTP_PROTOCOL_ERROR
; /* XXX: error code should be prepared */
694 * Find address and port number. The reply to the PASV command
695 * is IMHO the one and only weak point in the FTP protocol.
699 case FTP_PASSIVE_MODE
:
700 case FTP_LPASSIVE_MODE
:
701 for (p
= ln
+ 3; *p
&& !isdigit((unsigned char)*p
); p
++)
704 e
= FTP_PROTOCOL_ERROR
;
707 l
= (e
== FTP_PASSIVE_MODE
? 6 : 21);
708 for (i
= 0; *p
&& i
< l
; i
++, p
++)
709 addr
[i
] = strtol(p
, &p
, 10);
711 e
= FTP_PROTOCOL_ERROR
;
715 case FTP_EPASSIVE_MODE
:
716 for (p
= ln
+ 3; *p
&& *p
!= '('; p
++)
719 e
= FTP_PROTOCOL_ERROR
;
723 if (sscanf(p
, "%c%c%c%d%c", &addr
[0], &addr
[1], &addr
[2],
724 &port
, &addr
[3]) != 5 ||
725 addr
[0] != addr
[1] ||
726 addr
[0] != addr
[2] || addr
[0] != addr
[3]) {
727 e
= FTP_PROTOCOL_ERROR
;
733 /* seek to required offset */
735 if (ftp_cmd(conn
, "REST %lu", (u_long
)offset
) != FTP_FILE_OK
)
738 /* construct sockaddr for data socket */
740 if (getpeername(conn
->sd
, (struct sockaddr
*)&sa
, &l
) == -1)
742 if (sa
.ss_family
== AF_INET6
)
743 unmappedaddr((struct sockaddr_in6
*)&sa
);
744 switch (sa
.ss_family
) {
746 sin6
= (struct sockaddr_in6
*)&sa
;
747 if (e
== FTP_EPASSIVE_MODE
)
748 sin6
->sin6_port
= htons(port
);
750 memcpy(&sin6
->sin6_addr
, addr
+ 2, 16);
751 memcpy(&sin6
->sin6_port
, addr
+ 19, 2);
755 sin4
= (struct sockaddr_in
*)&sa
;
756 if (e
== FTP_EPASSIVE_MODE
)
757 sin4
->sin_port
= htons(port
);
759 memcpy(&sin4
->sin_addr
, addr
, 4);
760 memcpy(&sin4
->sin_port
, addr
+ 4, 2);
764 e
= FTP_PROTOCOL_ERROR
; /* XXX: error code should be prepared */
768 /* connect to data port */
770 fetch_info("opening data connection");
771 bindaddr
= getenv("FETCH_BIND_ADDRESS");
772 if (bindaddr
!= NULL
&& *bindaddr
!= '\0' &&
773 (e
= fetch_bind(sd
, sa
.ss_family
, bindaddr
)) != 0)
775 if (connect(sd
, (struct sockaddr
*)&sa
, sa
.ss_len
) == -1)
778 /* make the server initiate the transfer */
780 fetch_info("initiating transfer");
781 e
= ftp_cmd(conn
, "%s %.*s", oper
, filenamelen
, filename
);
782 if (e
!= FTP_CONNECTION_ALREADY_OPEN
&& e
!= FTP_OPEN_DATA_CONNECTION
)
790 char hname
[INET6_ADDRSTRLEN
];
792 switch (sa
.ss_family
) {
794 ((struct sockaddr_in6
*)&sa
)->sin6_port
= 0;
795 #ifdef IPV6_PORTRANGE
796 arg
= low
? IPV6_PORTRANGE_DEFAULT
: IPV6_PORTRANGE_HIGH
;
797 if (setsockopt(sd
, IPPROTO_IPV6
, IPV6_PORTRANGE
,
798 (char *)&arg
, sizeof(arg
)) == -1)
803 ((struct sockaddr_in
*)&sa
)->sin_port
= 0;
804 arg
= low
? IP_PORTRANGE_DEFAULT
: IP_PORTRANGE_HIGH
;
805 if (setsockopt(sd
, IPPROTO_IP
, IP_PORTRANGE
,
806 (char *)&arg
, sizeof(arg
)) == -1)
811 fetch_info("binding data socket");
812 if (bind(sd
, (struct sockaddr
*)&sa
, sa
.ss_len
) == -1)
814 if (listen(sd
, 1) == -1)
817 /* find what port we're on and tell the server */
818 if (getsockname(sd
, (struct sockaddr
*)&sa
, &l
) == -1)
820 switch (sa
.ss_family
) {
822 sin4
= (struct sockaddr_in
*)&sa
;
823 a
= ntohl(sin4
->sin_addr
.s_addr
);
824 p
= ntohs(sin4
->sin_port
);
825 e
= ftp_cmd(conn
, "PORT %d,%d,%d,%d,%d,%d",
826 (a
>> 24) & 0xff, (a
>> 16) & 0xff,
827 (a
>> 8) & 0xff, a
& 0xff,
828 (p
>> 8) & 0xff, p
& 0xff);
831 #define UC(b) (((int)b)&0xff)
833 sin6
= (struct sockaddr_in6
*)&sa
;
834 sin6
->sin6_scope_id
= 0;
835 if (getnameinfo((struct sockaddr
*)&sa
, sa
.ss_len
,
836 hname
, sizeof(hname
),
837 NULL
, 0, NI_NUMERICHOST
) == 0) {
838 e
= ftp_cmd(conn
, "EPRT |%d|%s|%d|", 2, hname
,
839 htons(sin6
->sin6_port
));
844 ap
= (char *)&sin6
->sin6_addr
;
846 "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
848 UC(ap
[0]), UC(ap
[1]), UC(ap
[2]), UC(ap
[3]),
849 UC(ap
[4]), UC(ap
[5]), UC(ap
[6]), UC(ap
[7]),
850 UC(ap
[8]), UC(ap
[9]), UC(ap
[10]), UC(ap
[11]),
851 UC(ap
[12]), UC(ap
[13]), UC(ap
[14]), UC(ap
[15]),
853 (ntohs(sin6
->sin6_port
) >> 8) & 0xff,
854 ntohs(sin6
->sin6_port
) & 0xff);
858 e
= FTP_PROTOCOL_ERROR
; /* XXX: error code should be prepared */
864 /* seek to required offset */
866 if (ftp_cmd(conn
, "REST %ju", (uintmax_t)offset
) != FTP_FILE_OK
)
869 /* make the server initiate the transfer */
871 fetch_info("initiating transfer");
872 e
= ftp_cmd(conn
, "%s %.*s", oper
, filenamelen
, filename
);
873 if (e
!= FTP_CONNECTION_ALREADY_OPEN
&& e
!= FTP_OPEN_DATA_CONNECTION
)
876 /* accept the incoming connection and go to town */
877 if ((d
= accept(sd
, NULL
, NULL
)) == -1)
883 if ((df
= ftp_setup(conn
, fetch_reopen(sd
), mode
)) == NULL
)
905 ftp_authenticate(conn_t
*conn
, struct url
*url
, struct url
*purl
)
907 const char *user
, *pwd
, *logname
;
908 char pbuf
[MAXHOSTNAMELEN
+ MAXLOGNAME
+ 1];
911 /* XXX FTP_AUTH, and maybe .netrc */
913 /* send user name and password */
914 if (url
->user
[0] == '\0')
915 fetch_netrc_auth(url
);
918 if ((user
= getenv("FTP_LOGIN")) != NULL
)
919 DEBUGF("FTP_LOGIN=%s\n", user
);
920 if (user
== NULL
|| *user
== '\0')
921 user
= FTP_ANONYMOUS_USER
;
922 if (purl
&& url
->port
== fetch_default_port(url
->scheme
))
923 e
= ftp_cmd(conn
, "USER %s@%s", user
, url
->host
);
925 e
= ftp_cmd(conn
, "USER %s@%s@%d", user
, url
->host
, url
->port
);
927 e
= ftp_cmd(conn
, "USER %s", user
);
929 /* did the server request a password? */
930 if (e
== FTP_NEED_PASSWORD
) {
933 if ((pwd
= getenv("FTP_PASSWORD")) != NULL
)
934 DEBUGF("FTP_PASSWORD=%s\n", pwd
);
935 if (pwd
== NULL
|| *pwd
== '\0') {
936 if ((logname
= getlogin()) == NULL
)
937 logname
= FTP_ANONYMOUS_USER
;
938 if ((len
= snprintf(pbuf
, MAXLOGNAME
+ 1, "%s@", logname
)) < 0)
940 else if (len
> MAXLOGNAME
)
942 gethostname(pbuf
+ len
, sizeof(pbuf
) - len
);
945 e
= ftp_cmd(conn
, "PASS %s", pwd
);
952 * Log on to FTP server
955 ftp_connect(struct url
*url
, struct url
*purl
, const char *flags
)
958 int e
, direct
, verbose
;
965 direct
= CHECK_FLAG('d');
966 verbose
= CHECK_FLAG('v');
969 else if (CHECK_FLAG('6'))
975 /* check for proxy */
977 /* XXX proxy authentication! */
978 conn
= fetch_connect(purl
->host
, purl
->port
, af
, verbose
);
980 /* no proxy, go straight to target */
981 conn
= fetch_connect(url
->host
, url
->port
, af
, verbose
);
985 /* check connection */
987 /* fetch_connect() has already set an error code */
990 /* expect welcome message */
991 if ((e
= ftp_chkerr(conn
)) != FTP_SERVICE_READY
)
995 if ((e
= ftp_authenticate(conn
, url
, purl
)) != FTP_LOGGED_IN
)
998 /* TODO: Request extended features supported, if any (RFC 3659). */
1011 * Disconnect from server
1014 ftp_disconnect(conn_t
*conn
)
1016 (void)ftp_cmd(conn
, "QUIT");
1017 if (conn
== cached_connection
&& conn
->ref
== 1)
1018 cached_connection
= NULL
;
1023 * Check if we're already connected
1026 ftp_isconnected(struct url
*url
)
1028 return (cached_connection
1029 && (strcmp(url
->host
, cached_host
.host
) == 0)
1030 && (strcmp(url
->user
, cached_host
.user
) == 0)
1031 && (strcmp(url
->pwd
, cached_host
.pwd
) == 0)
1032 && (url
->port
== cached_host
.port
));
1036 * Check the cache, reconnect if no luck
1039 ftp_cached_connect(struct url
*url
, struct url
*purl
, const char *flags
)
1044 /* set default port */
1046 url
->port
= fetch_default_port(url
->scheme
);
1048 /* try to use previously cached connection */
1049 if (ftp_isconnected(url
)) {
1050 e
= ftp_cmd(cached_connection
, "NOOP");
1051 if (e
== FTP_OK
|| e
== FTP_SYNTAX_ERROR
)
1052 return (fetch_ref(cached_connection
));
1055 /* connect to server */
1056 if ((conn
= ftp_connect(url
, purl
, flags
)) == NULL
)
1058 if (cached_connection
)
1059 ftp_disconnect(cached_connection
);
1060 cached_connection
= fetch_ref(conn
);
1061 memcpy(&cached_host
, url
, sizeof(*url
));
1066 * Check the proxy settings
1069 ftp_get_proxy(struct url
* url
, const char *flags
)
1074 if (flags
!= NULL
&& strchr(flags
, 'd') != NULL
)
1076 if (fetch_no_proxy_match(url
->host
))
1078 if (((p
= getenv("FTP_PROXY")) || (p
= getenv("ftp_proxy")) ||
1079 (p
= getenv("HTTP_PROXY")) || (p
= getenv("http_proxy"))) &&
1080 *p
&& (purl
= fetchParseURL(p
)) != NULL
) {
1081 if (!*purl
->scheme
) {
1082 if (getenv("FTP_PROXY") || getenv("ftp_proxy"))
1083 strcpy(purl
->scheme
, SCHEME_FTP
);
1085 strcpy(purl
->scheme
, SCHEME_HTTP
);
1088 purl
->port
= fetch_default_proxy_port(purl
->scheme
);
1089 if (strcmp(purl
->scheme
, SCHEME_FTP
) == 0 ||
1090 strcmp(purl
->scheme
, SCHEME_HTTP
) == 0)
1098 * Process an FTP request
1101 ftp_request(struct url
*url
, const char *op
, struct url_stat
*us
,
1102 struct url
*purl
, const char *flags
)
1107 /* check if we should use HTTP instead */
1108 if (purl
&& (strcmp(purl
->scheme
, SCHEME_HTTP
) == 0 ||
1109 strcmp(purl
->scheme
, SCHEME_HTTPS
) == 0)) {
1110 if (strcmp(op
, "STAT") == 0)
1111 return (http_request(url
, "HEAD", us
, purl
, flags
));
1112 else if (strcmp(op
, "RETR") == 0)
1113 return (http_request(url
, "GET", us
, purl
, flags
));
1115 * Our HTTP code doesn't support PUT requests yet, so try
1116 * a direct connection.
1120 /* connect to server */
1121 conn
= ftp_cached_connect(url
, purl
, flags
);
1127 /* change directory */
1128 if (ftp_cwd(conn
, url
->doc
) == -1)
1132 if (us
&& ftp_stat(conn
, url
->doc
, us
) == -1
1133 && fetchLastErrCode
!= FETCH_PROTO
1134 && fetchLastErrCode
!= FETCH_UNAVAIL
)
1138 if (strcmp(op
, "STAT") == 0) {
1140 ftp_disconnect(conn
);
1141 return (FILE *)1; /* bogus return value */
1143 if (strcmp(op
, "STOR") == 0 || strcmp(op
, "APPE") == 0)
1148 /* initiate the transfer */
1149 return (ftp_transfer(conn
, op
, url
->doc
, oflag
, url
->offset
, flags
));
1152 ftp_disconnect(conn
);
1160 fetchXGetFTP(struct url
*url
, struct url_stat
*us
, const char *flags
)
1162 return (ftp_request(url
, "RETR", us
, ftp_get_proxy(url
, flags
), flags
));
1169 fetchGetFTP(struct url
*url
, const char *flags
)
1171 return (fetchXGetFTP(url
, NULL
, flags
));
1178 fetchPutFTP(struct url
*url
, const char *flags
)
1180 return (ftp_request(url
, CHECK_FLAG('a') ? "APPE" : "STOR", NULL
,
1181 ftp_get_proxy(url
, flags
), flags
));
1188 fetchStatFTP(struct url
*url
, struct url_stat
*us
, const char *flags
)
1192 f
= ftp_request(url
, "STAT", us
, ftp_get_proxy(url
, flags
), flags
);
1196 * When op is "STAT", ftp_request() will return either NULL or
1197 * (FILE *)1, never a valid FILE *, so we mustn't fclose(f) before
1198 * returning, as it would cause a segfault.
1207 fetchListFTP(struct url
*url __unused
, const char *flags __unused
)
1209 warnx("fetchListFTP(): not implemented");