1 /* $NetBSD: ftp.c,v 1.30 2009/10/15 12:36:57 joerg Exp $ */
3 * Copyright (c) 1998-2004 Dag-Erling Coïdan Smørgrav
4 * Copyright (c) 2008, 2009 Joerg Sonnenberger <joerg@NetBSD.org>
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: ftp.c,v 1.101 2008/01/23 20:57:59 des Exp $
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 Coïdan 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 $
61 /* Keep this down to Linux, it can create surprises else where. */
72 #include <sys/types.h>
73 #include <sys/socket.h>
75 #include <netinet/in.h>
76 #include <arpa/inet.h>
81 #if defined(HAVE_INTTYPES_H) || defined(NETBSD)
86 #include <nbcompat/netdb.h>
87 #include <nbcompat/stdio.h>
101 #define FTP_ANONYMOUS_USER "anonymous"
103 #define FTP_CONNECTION_ALREADY_OPEN 125
104 #define FTP_OPEN_DATA_CONNECTION 150
106 #define FTP_FILE_STATUS 213
107 #define FTP_SERVICE_READY 220
108 #define FTP_TRANSFER_COMPLETE 226
109 #define FTP_PASSIVE_MODE 227
110 #define FTP_LPASSIVE_MODE 228
111 #define FTP_EPASSIVE_MODE 229
112 #define FTP_LOGGED_IN 230
113 #define FTP_FILE_ACTION_OK 250
114 #define FTP_DIRECTORY_CREATED 257 /* multiple meanings */
115 #define FTP_FILE_CREATED 257 /* multiple meanings */
116 #define FTP_WORKING_DIRECTORY 257 /* multiple meanings */
117 #define FTP_NEED_PASSWORD 331
118 #define FTP_NEED_ACCOUNT 332
119 #define FTP_FILE_OK 350
120 #define FTP_SYNTAX_ERROR 500
121 #define FTP_PROTOCOL_ERROR 999
123 static struct url cached_host
;
124 static conn_t
*cached_connection
;
126 #define isftpreply(foo) \
127 (isdigit((unsigned char)foo[0]) && \
128 isdigit((unsigned char)foo[1]) && \
129 isdigit((unsigned char)foo[2]) && \
130 (foo[3] == ' ' || foo[3] == '\0'))
131 #define isftpinfo(foo) \
132 (isdigit((unsigned char)foo[0]) && \
133 isdigit((unsigned char)foo[1]) && \
134 isdigit((unsigned char)foo[2]) && \
138 * Translate IPv4 mapped IPv6 address to IPv4 address
141 unmappedaddr(struct sockaddr_in6
*sin6
, socklen_t
*len
)
143 struct sockaddr_in
*sin4
;
147 if (sin6
->sin6_family
!= AF_INET6
||
148 !IN6_IS_ADDR_V4MAPPED(&sin6
->sin6_addr
))
150 sin4
= (struct sockaddr_in
*)sin6
;
151 addr
= *(uint32_t *)&sin6
->sin6_addr
.s6_addr
[12];
152 port
= sin6
->sin6_port
;
153 memset(sin4
, 0, sizeof(struct sockaddr_in
));
154 sin4
->sin_addr
.s_addr
= addr
;
155 sin4
->sin_port
= port
;
156 sin4
->sin_family
= AF_INET
;
157 *len
= sizeof(struct sockaddr_in
);
159 sin4
->sin_len
= sizeof(struct sockaddr_in
);
164 * Get server response
167 ftp_chkerr(conn_t
*conn
)
169 if (fetch_getln(conn
) == -1) {
173 if (isftpinfo(conn
->buf
)) {
174 while (conn
->buflen
&& !isftpreply(conn
->buf
)) {
175 if (fetch_getln(conn
) == -1) {
182 while (conn
->buflen
&&
183 isspace((unsigned char)conn
->buf
[conn
->buflen
- 1]))
185 conn
->buf
[conn
->buflen
] = '\0';
187 if (!isftpreply(conn
->buf
)) {
188 ftp_seterr(FTP_PROTOCOL_ERROR
);
192 conn
->err
= (conn
->buf
[0] - '0') * 100
193 + (conn
->buf
[1] - '0') * 10
194 + (conn
->buf
[2] - '0');
200 * Send a command and check reply
203 ftp_cmd(conn_t
*conn
, const char *fmt
, ...)
211 len
= vasprintf(&msg
, fmt
, ap
);
220 r
= fetch_putln(conn
, msg
, len
);
228 return (ftp_chkerr(conn
));
232 * Return a pointer to the filename part of a path
235 ftp_filename(const char *file
, int *len
, int *type
, int subdir
)
239 if ((s
= strrchr(file
, '/')) == NULL
|| subdir
)
244 if (*len
> 7 && strncmp(s
+ *len
- 7, ";type=", 6) == 0) {
254 * Get current working directory from the reply to a CWD, PWD or CDUP
258 ftp_pwd(conn_t
*conn
, char *pwd
, size_t pwdlen
)
260 char *src
, *dst
, *end
;
263 if (conn
->err
!= FTP_WORKING_DIRECTORY
&&
264 conn
->err
!= FTP_FILE_ACTION_OK
)
265 return (FTP_PROTOCOL_ERROR
);
266 end
= conn
->buf
+ conn
->buflen
;
268 if (src
>= end
|| *src
++ != '"')
269 return (FTP_PROTOCOL_ERROR
);
270 for (q
= 0, dst
= pwd
; src
< end
&& pwdlen
--; ++src
) {
271 if (!q
&& *src
== '"')
273 else if (q
&& *src
!= '"')
281 return (FTP_PROTOCOL_ERROR
);
287 * Change working directory to the directory that contains the specified
291 ftp_cwd(conn_t
*conn
, const char *file
, int subdir
)
293 const char *beg
, *end
;
297 /* If no slashes in name, no need to change dirs. */
299 end
= file
+ strlen(file
);
300 else if ((end
= strrchr(file
, '/')) == NULL
)
302 if ((e
= ftp_cmd(conn
, "PWD")) != FTP_WORKING_DIRECTORY
||
303 (e
= ftp_pwd(conn
, pwd
, sizeof(pwd
))) != FTP_OK
) {
310 /* Look for a common prefix between PWD and dir to fetch. */
311 for (i
= 0; i
<= len
&& i
<= end
- file
; ++i
)
312 if (pwd
[i
] != file
[i
])
314 /* Keep going up a dir until we have a matching prefix. */
315 if (strcmp(pwd
, "/") == 0)
317 if (pwd
[i
] == '\0' && (file
[i
- 1] == '/' || file
[i
] == '/'))
319 if ((e
= ftp_cmd(conn
, "CDUP")) != FTP_FILE_ACTION_OK
||
320 (e
= ftp_cmd(conn
, "PWD")) != FTP_WORKING_DIRECTORY
||
321 (e
= ftp_pwd(conn
, pwd
, sizeof(pwd
))) != FTP_OK
) {
327 #ifdef FTP_COMBINE_CWDS
328 /* Skip leading slashes, even "////". */
329 for (beg
= file
+ i
; beg
< end
&& *beg
== '/'; ++beg
, ++i
)
332 /* If there is no trailing dir, we're already there. */
336 /* Change to the directory all in one chunk (e.g., foo/bar/baz). */
337 e
= ftp_cmd(conn
, "CWD %.*s", (int)(end
- beg
), beg
);
338 if (e
== FTP_FILE_ACTION_OK
)
340 #endif /* FTP_COMBINE_CWDS */
342 /* That didn't work so go back to legacy behavior (multiple CWDs). */
343 for (beg
= file
+ i
; beg
< end
; beg
= file
+ i
+ 1) {
346 for (++i
; file
+ i
< end
&& file
[i
] != '/'; ++i
)
348 e
= ftp_cmd(conn
, "CWD %.*s", file
+ i
- beg
, beg
);
349 if (e
!= FTP_FILE_ACTION_OK
) {
358 * Set transfer mode and data type
361 ftp_mode_type(conn_t
*conn
, int mode
, int type
)
372 return (FTP_PROTOCOL_ERROR
);
374 if ((e
= ftp_cmd(conn
, "MODE %c", mode
)) != FTP_OK
) {
377 * Stream mode is supposed to be the default - so
378 * much so that some servers not only do not
379 * support any other mode, but do not support the
380 * MODE command at all.
382 * If "MODE S" fails, it is unlikely that we
383 * previously succeeded in setting a different
384 * mode. Therefore, we simply hope that the
385 * server is already in the correct mode, and
386 * silently ignore the failure.
406 /* can't handle yet */
408 return (FTP_PROTOCOL_ERROR
);
410 if ((e
= ftp_cmd(conn
, "TYPE %c", type
)) != FTP_OK
)
417 * Request and parse file stats
420 ftp_stat(conn_t
*conn
, const char *file
, struct url_stat
*us
)
423 const char *filename
;
424 int filenamelen
, type
;
430 us
->atime
= us
->mtime
= 0;
432 filename
= ftp_filename(file
, &filenamelen
, &type
, 0);
434 if ((e
= ftp_mode_type(conn
, 0, type
)) != FTP_OK
) {
439 e
= ftp_cmd(conn
, "SIZE %.*s", filenamelen
, filename
);
440 if (e
!= FTP_FILE_STATUS
) {
444 for (ln
= conn
->buf
+ 4; *ln
&& isspace((unsigned char)*ln
); ln
++)
446 for (us
->size
= 0; *ln
&& isdigit((unsigned char)*ln
); ln
++)
447 us
->size
= us
->size
* 10 + *ln
- '0';
448 if (*ln
&& !isspace((unsigned char)*ln
)) {
449 ftp_seterr(FTP_PROTOCOL_ERROR
);
456 e
= ftp_cmd(conn
, "MDTM %.*s", filenamelen
, filename
);
457 if (e
!= FTP_FILE_STATUS
) {
461 for (ln
= conn
->buf
+ 4; *ln
&& isspace((unsigned char)*ln
); ln
++)
463 switch (strspn(ln
, "0123456789")) {
472 ftp_seterr(FTP_PROTOCOL_ERROR
);
475 if (sscanf(ln
, "%04d%02d%02d%02d%02d%02d",
476 &tm
.tm_year
, &tm
.tm_mon
, &tm
.tm_mday
,
477 &tm
.tm_hour
, &tm
.tm_min
, &tm
.tm_sec
) != 6) {
478 ftp_seterr(FTP_PROTOCOL_ERROR
);
494 * I/O functions for FTP
497 conn_t
*cconn
; /* Control connection */
498 conn_t
*dconn
; /* Data connection */
499 int dir
; /* Direction */
500 int eof
; /* EOF reached */
501 int err
; /* Error code */
504 static ssize_t
ftp_readfn(void *, void *, size_t);
505 static ssize_t
ftp_writefn(void *, const void *, size_t);
506 static void ftp_closefn(void *);
509 ftp_readfn(void *v
, void *buf
, size_t len
)
514 io
= (struct ftpio
*)v
;
519 if (io
->cconn
== NULL
|| io
->dconn
== NULL
|| io
->dir
== O_WRONLY
) {
529 r
= fetch_read(io
->dconn
, buf
, len
);
542 ftp_writefn(void *v
, const void *buf
, size_t len
)
547 io
= (struct ftpio
*)v
;
552 if (io
->cconn
== NULL
|| io
->dconn
== NULL
|| io
->dir
== O_RDONLY
) {
560 w
= fetch_write(io
->dconn
, buf
, len
);
574 io
= (struct ftpio
*)v
;
581 if (io
->cconn
== NULL
|| io
->dconn
== NULL
) {
585 fetch_close(io
->dconn
);
587 io
->dconn
->is_active
= 0;
589 r
= ftp_chkerr(io
->cconn
);
590 if (io
->cconn
== cached_connection
&& io
->cconn
->ref
== 1) {
591 free(cached_host
.doc
);
592 cached_connection
= NULL
;
594 fetch_close(io
->cconn
);
600 ftp_setup(conn_t
*cconn
, conn_t
*dconn
, int mode
)
605 if (cconn
== NULL
|| dconn
== NULL
)
607 if ((io
= malloc(sizeof(*io
))) == NULL
)
612 io
->eof
= io
->err
= 0;
613 io
->cconn
->is_active
= 1;
614 f
= fetchIO_unopen(io
, ftp_readfn
, ftp_writefn
, ftp_closefn
);
624 ftp_transfer(conn_t
*conn
, const char *oper
, const char *file
, const char *op_arg
,
625 int mode
, off_t offset
, const char *flags
)
628 struct sockaddr_storage ss
;
630 struct sockaddr_in6 sin6
;
631 struct sockaddr_in sin4
;
633 const char *bindaddr
;
634 const char *filename
;
635 int filenamelen
, type
;
636 int low
, pasv
, verbose
;
643 low
= CHECK_FLAG('l');
644 pasv
= !CHECK_FLAG('a');
645 verbose
= CHECK_FLAG('v');
649 pasv
= ((s
= getenv("FTP_PASSIVE_MODE")) != NULL
&&
650 strncasecmp(s
, "no", 2) != 0);
652 /* isolate filename */
653 filename
= ftp_filename(file
, &filenamelen
, &type
, op_arg
!= NULL
);
655 /* set transfer mode and data type */
656 if ((e
= ftp_mode_type(conn
, 0, type
)) != FTP_OK
)
659 /* find our own address, bind, and listen */
661 if (getsockname(conn
->sd
, &u
.sa
, &l
) == -1)
663 if (u
.ss
.ss_family
== AF_INET6
)
664 unmappedaddr(&u
.sin6
, &l
);
668 /* open data socket */
669 if ((sd
= socket(u
.ss
.ss_family
, SOCK_STREAM
, IPPROTO_TCP
)) == -1) {
675 unsigned char addr
[64];
680 /* send PASV command */
682 fetch_info("setting passive mode");
683 switch (u
.ss
.ss_family
) {
685 if ((e
= ftp_cmd(conn
, "PASV")) != FTP_PASSIVE_MODE
)
689 if ((e
= ftp_cmd(conn
, "EPSV")) != FTP_EPASSIVE_MODE
) {
692 if ((e
= ftp_cmd(conn
, "LPSV")) !=
698 e
= FTP_PROTOCOL_ERROR
; /* XXX: error code should be prepared */
703 * Find address and port number. The reply to the PASV command
704 * is IMHO the one and only weak point in the FTP protocol.
708 case FTP_PASSIVE_MODE
:
709 case FTP_LPASSIVE_MODE
:
710 for (p
= ln
+ 3; *p
&& !isdigit((unsigned char)*p
); p
++)
713 e
= FTP_PROTOCOL_ERROR
;
716 l
= (e
== FTP_PASSIVE_MODE
? 6 : 21);
717 for (i
= 0; *p
&& i
< l
; i
++, p
++)
718 addr
[i
] = strtol(p
, &p
, 10);
720 e
= FTP_PROTOCOL_ERROR
;
724 case FTP_EPASSIVE_MODE
:
725 for (p
= ln
+ 3; *p
&& *p
!= '('; p
++)
728 e
= FTP_PROTOCOL_ERROR
;
732 if (sscanf(p
, "%c%c%c%d%c", &addr
[0], &addr
[1], &addr
[2],
733 &port
, &addr
[3]) != 5 ||
734 addr
[0] != addr
[1] ||
735 addr
[0] != addr
[2] || addr
[0] != addr
[3]) {
736 e
= FTP_PROTOCOL_ERROR
;
740 case FTP_SYNTAX_ERROR
:
742 fetch_info("passive mode failed");
743 /* Close socket and retry with passive mode. */
750 /* seek to required offset */
752 if (ftp_cmd(conn
, "REST %lu", (unsigned long)offset
) != FTP_FILE_OK
)
755 /* construct sockaddr for data socket */
757 if (getpeername(conn
->sd
, &u
.sa
, &l
) == -1)
759 if (u
.ss
.ss_family
== AF_INET6
)
760 unmappedaddr(&u
.sin6
, &l
);
761 switch (u
.ss
.ss_family
) {
763 if (e
== FTP_EPASSIVE_MODE
)
764 u
.sin6
.sin6_port
= htons(port
);
766 memcpy(&u
.sin6
.sin6_addr
, addr
+ 2, 16);
767 memcpy(&u
.sin6
.sin6_port
, addr
+ 19, 2);
771 if (e
== FTP_EPASSIVE_MODE
)
772 u
.sin4
.sin_port
= htons(port
);
774 memcpy(&u
.sin4
.sin_addr
, addr
, 4);
775 memcpy(&u
.sin4
.sin_port
, addr
+ 4, 2);
779 e
= FTP_PROTOCOL_ERROR
; /* XXX: error code should be prepared */
783 /* connect to data port */
785 fetch_info("opening data connection");
786 bindaddr
= getenv("FETCH_BIND_ADDRESS");
787 if (bindaddr
!= NULL
&& *bindaddr
!= '\0' &&
788 fetch_bind(sd
, u
.ss
.ss_family
, bindaddr
) != 0)
790 if (connect(sd
, &u
.sa
, l
) == -1)
793 /* make the server initiate the transfer */
795 fetch_info("initiating transfer");
797 e
= ftp_cmd(conn
, "%s%s%s", oper
, *op_arg
? " " : "", op_arg
);
799 e
= ftp_cmd(conn
, "%s %.*s", oper
,
800 filenamelen
, filename
);
801 if (e
!= FTP_CONNECTION_ALREADY_OPEN
&& e
!= FTP_OPEN_DATA_CONNECTION
)
807 #if defined(IPV6_PORTRANGE) || defined(IP_PORTRANGE)
812 char hname
[INET6_ADDRSTRLEN
];
814 switch (u
.ss
.ss_family
) {
816 u
.sin6
.sin6_port
= 0;
817 #ifdef IPV6_PORTRANGE
818 arg
= low
? IPV6_PORTRANGE_DEFAULT
: IPV6_PORTRANGE_HIGH
;
819 if (setsockopt(sd
, IPPROTO_IPV6
, IPV6_PORTRANGE
,
820 (char *)&arg
, sizeof(arg
)) == -1)
827 arg
= low
? IP_PORTRANGE_DEFAULT
: IP_PORTRANGE_HIGH
;
828 if (setsockopt(sd
, IPPROTO_IP
, IP_PORTRANGE
,
829 (char *)&arg
, sizeof(arg
)) == -1)
835 fetch_info("binding data socket");
836 if (bind(sd
, &u
.sa
, l
) == -1)
838 if (listen(sd
, 1) == -1)
841 /* find what port we're on and tell the server */
842 if (getsockname(sd
, &u
.sa
, &l
) == -1)
844 switch (u
.ss
.ss_family
) {
846 a
= ntohl(u
.sin4
.sin_addr
.s_addr
);
847 p
= ntohs(u
.sin4
.sin_port
);
848 e
= ftp_cmd(conn
, "PORT %d,%d,%d,%d,%d,%d",
849 (a
>> 24) & 0xff, (a
>> 16) & 0xff,
850 (a
>> 8) & 0xff, a
& 0xff,
851 (p
>> 8) & 0xff, p
& 0xff);
854 #define UC(b) (((int)b)&0xff)
856 u
.sin6
.sin6_scope_id
= 0;
857 if (getnameinfo(&u
.sa
, l
,
858 hname
, sizeof(hname
),
859 NULL
, 0, NI_NUMERICHOST
) == 0) {
860 e
= ftp_cmd(conn
, "EPRT |%d|%s|%d|", 2, hname
,
861 htons(u
.sin6
.sin6_port
));
866 ap
= (char *)&u
.sin6
.sin6_addr
;
868 "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
870 UC(ap
[0]), UC(ap
[1]), UC(ap
[2]), UC(ap
[3]),
871 UC(ap
[4]), UC(ap
[5]), UC(ap
[6]), UC(ap
[7]),
872 UC(ap
[8]), UC(ap
[9]), UC(ap
[10]), UC(ap
[11]),
873 UC(ap
[12]), UC(ap
[13]), UC(ap
[14]), UC(ap
[15]),
875 (ntohs(u
.sin6
.sin6_port
) >> 8) & 0xff,
876 ntohs(u
.sin6
.sin6_port
) & 0xff);
880 e
= FTP_PROTOCOL_ERROR
; /* XXX: error code should be prepared */
886 /* seek to required offset */
888 if (ftp_cmd(conn
, "REST %llu", (unsigned long long)offset
) != FTP_FILE_OK
)
891 /* make the server initiate the transfer */
893 fetch_info("initiating transfer");
895 e
= ftp_cmd(conn
, "%s%s%s", oper
, *op_arg
? " " : "", op_arg
);
897 e
= ftp_cmd(conn
, "%s %.*s", oper
,
898 filenamelen
, filename
);
899 if (e
!= FTP_CONNECTION_ALREADY_OPEN
&& e
!= FTP_OPEN_DATA_CONNECTION
)
902 /* accept the incoming connection and go to town */
903 if ((d
= accept(sd
, NULL
, NULL
)) == -1)
909 if ((df
= ftp_setup(conn
, fetch_reopen(sd
), mode
)) == NULL
)
931 ftp_authenticate(conn_t
*conn
, struct url
*url
, struct url
*purl
)
933 const char *user
, *pwd
, *login_name
;
934 char pbuf
[URL_USERLEN
+ 1 + URL_HOSTLEN
+ 1];
937 /* XXX FTP_AUTH, and maybe .netrc */
939 /* send user name and password */
940 if (url
->user
[0] == '\0')
941 fetch_netrc_auth(url
);
944 user
= getenv("FTP_LOGIN");
945 if (user
== NULL
|| *user
== '\0')
946 user
= FTP_ANONYMOUS_USER
;
947 if (purl
&& url
->port
== fetch_default_port(url
->scheme
))
948 e
= ftp_cmd(conn
, "USER %s@%s", user
, url
->host
);
950 e
= ftp_cmd(conn
, "USER %s@%s@%d", user
, url
->host
, url
->port
);
952 e
= ftp_cmd(conn
, "USER %s", user
);
954 /* did the server request a password? */
955 if (e
== FTP_NEED_PASSWORD
) {
958 pwd
= getenv("FTP_PASSWORD");
959 if (pwd
== NULL
|| *pwd
== '\0') {
960 if ((login_name
= getlogin()) == 0)
961 login_name
= FTP_ANONYMOUS_USER
;
962 if ((len
= snprintf(pbuf
, URL_USERLEN
+ 2, "%s@", login_name
)) < 0)
964 else if (len
> URL_USERLEN
+ 1)
965 len
= URL_USERLEN
+ 1;
966 gethostname(pbuf
+ len
, sizeof(pbuf
) - len
);
967 /* MAXHOSTNAMELEN can differ from URL_HOSTLEN + 1 */
968 pbuf
[sizeof(pbuf
) - 1] = '\0';
971 e
= ftp_cmd(conn
, "PASS %s", pwd
);
978 * Log on to FTP server
981 ftp_connect(struct url
*url
, struct url
*purl
, const char *flags
)
984 int e
, direct
, verbose
;
991 direct
= CHECK_FLAG('d');
992 verbose
= CHECK_FLAG('v');
995 else if (CHECK_FLAG('6'))
1001 /* check for proxy */
1003 /* XXX proxy authentication! */
1004 conn
= fetch_connect(purl
->host
, purl
->port
, af
, verbose
);
1006 /* no proxy, go straight to target */
1007 conn
= fetch_connect(url
->host
, url
->port
, af
, verbose
);
1011 /* check connection */
1013 /* fetch_connect() has already set an error code */
1016 /* expect welcome message */
1017 if ((e
= ftp_chkerr(conn
)) != FTP_SERVICE_READY
)
1021 if ((e
= ftp_authenticate(conn
, url
, purl
)) != FTP_LOGGED_IN
)
1024 /* TODO: Request extended features supported, if any (RFC 3659). */
1037 * Disconnect from server
1040 ftp_disconnect(conn_t
*conn
)
1042 (void)ftp_cmd(conn
, "QUIT");
1043 if (conn
== cached_connection
&& conn
->ref
== 1) {
1044 free(cached_host
.doc
);
1045 cached_host
.doc
= NULL
;
1046 cached_connection
= NULL
;
1052 * Check if we're already connected
1055 ftp_isconnected(struct url
*url
)
1057 return (cached_connection
1058 && (cached_connection
->is_active
== 0)
1059 && (strcmp(url
->host
, cached_host
.host
) == 0)
1060 && (strcmp(url
->user
, cached_host
.user
) == 0)
1061 && (strcmp(url
->pwd
, cached_host
.pwd
) == 0)
1062 && (url
->port
== cached_host
.port
));
1066 * Check the cache, reconnect if no luck
1069 ftp_cached_connect(struct url
*url
, struct url
*purl
, const char *flags
)
1075 /* set default port */
1077 url
->port
= fetch_default_port(url
->scheme
);
1079 /* try to use previously cached connection */
1080 if (ftp_isconnected(url
)) {
1081 e
= ftp_cmd(cached_connection
, "NOOP");
1082 if (e
== FTP_OK
|| e
== FTP_SYNTAX_ERROR
)
1083 return (fetch_ref(cached_connection
));
1086 /* connect to server */
1087 if ((conn
= ftp_connect(url
, purl
, flags
)) == NULL
)
1089 doc
= strdup(url
->doc
);
1091 if (cached_connection
)
1092 ftp_disconnect(cached_connection
);
1093 cached_connection
= fetch_ref(conn
);
1094 memcpy(&cached_host
, url
, sizeof(*url
));
1095 cached_host
.doc
= doc
;
1101 * Check the proxy settings
1104 ftp_get_proxy(struct url
* url
, const char *flags
)
1109 if (flags
!= NULL
&& strchr(flags
, 'd') != NULL
)
1111 if (fetch_no_proxy_match(url
->host
))
1113 if (((p
= getenv("FTP_PROXY")) || (p
= getenv("ftp_proxy")) ||
1114 (p
= getenv("HTTP_PROXY")) || (p
= getenv("http_proxy"))) &&
1115 *p
&& (purl
= fetchParseURL(p
)) != NULL
) {
1116 if (!*purl
->scheme
) {
1117 if (getenv("FTP_PROXY") || getenv("ftp_proxy"))
1118 strcpy(purl
->scheme
, SCHEME_FTP
);
1120 strcpy(purl
->scheme
, SCHEME_HTTP
);
1123 purl
->port
= fetch_default_proxy_port(purl
->scheme
);
1124 if (strcasecmp(purl
->scheme
, SCHEME_FTP
) == 0 ||
1125 strcasecmp(purl
->scheme
, SCHEME_HTTP
) == 0)
1133 * Process an FTP request
1136 ftp_request(struct url
*url
, const char *op
, const char *op_arg
,
1137 struct url_stat
*us
, struct url
*purl
, const char *flags
)
1142 int if_modified_since
, oflag
;
1143 struct url_stat local_us
;
1145 /* check if we should use HTTP instead */
1146 if (purl
&& strcasecmp(purl
->scheme
, SCHEME_HTTP
) == 0) {
1147 if (strcmp(op
, "STAT") == 0)
1148 return (http_request(url
, "HEAD", us
, purl
, flags
));
1149 else if (strcmp(op
, "RETR") == 0)
1150 return (http_request(url
, "GET", us
, purl
, flags
));
1152 * Our HTTP code doesn't support PUT requests yet, so try
1153 * a direct connection.
1157 /* connect to server */
1158 conn
= ftp_cached_connect(url
, purl
, flags
);
1164 if ((path
= fetchUnquotePath(url
)) == NULL
) {
1169 /* change directory */
1170 if (ftp_cwd(conn
, path
, op_arg
!= NULL
) == -1) {
1175 if_modified_since
= CHECK_FLAG('i');
1176 if (if_modified_since
&& us
== NULL
)
1180 if (us
&& ftp_stat(conn
, path
, us
) == -1
1181 && fetchLastErrCode
!= FETCH_PROTO
1182 && fetchLastErrCode
!= FETCH_UNAVAIL
) {
1187 if (if_modified_since
&& url
->last_modified
> 0 &&
1188 url
->last_modified
>= us
->mtime
) {
1190 fetchLastErrCode
= FETCH_UNCHANGED
;
1191 snprintf(fetchLastErrString
, MAXERRSTRING
, "Unchanged");
1196 if (strcmp(op
, "STAT") == 0) {
1198 return fetchIO_unopen(NULL
, NULL
, NULL
, NULL
);
1200 if (strcmp(op
, "STOR") == 0 || strcmp(op
, "APPE") == 0)
1205 /* initiate the transfer */
1206 f
= (ftp_transfer(conn
, op
, path
, op_arg
, oflag
, url
->offset
, flags
));
1215 fetchXGetFTP(struct url
*url
, struct url_stat
*us
, const char *flags
)
1217 return (ftp_request(url
, "RETR", NULL
, us
, ftp_get_proxy(url
, flags
), flags
));
1224 fetchGetFTP(struct url
*url
, const char *flags
)
1226 return (fetchXGetFTP(url
, NULL
, flags
));
1233 fetchPutFTP(struct url
*url
, const char *flags
)
1235 return (ftp_request(url
, CHECK_FLAG('a') ? "APPE" : "STOR", NULL
, NULL
,
1236 ftp_get_proxy(url
, flags
), flags
));
1243 fetchStatFTP(struct url
*url
, struct url_stat
*us
, const char *flags
)
1247 f
= ftp_request(url
, "STAT", NULL
, us
, ftp_get_proxy(url
, flags
), flags
);
1258 fetchListFTP(struct url_list
*ue
, struct url
*url
, const char *pattern
, const char *flags
)
1261 char buf
[2 * PATH_MAX
], *eol
, *eos
;
1266 /* XXX What about proxies? */
1267 if (pattern
== NULL
|| strcmp(pattern
, "*") == 0)
1269 f
= ftp_request(url
, "NLST", pattern
, NULL
, ftp_get_proxy(url
, flags
), flags
);
1276 while ((len
= fetchIO_read(f
, buf
+ cur_off
, sizeof(buf
) - cur_off
)) > 0) {
1278 while ((eol
= memchr(buf
, '\n', cur_off
)) != NULL
) {
1279 if (len
== eol
- buf
)
1282 if (eol
[-1] == '\r')
1287 ret
= fetch_add_entry(ue
, url
, buf
, 0);
1290 cur_off
-= eol
- buf
+ 1;
1291 memmove(buf
, eol
+ 1, cur_off
);
1297 if (cur_off
!= 0 || len
< 0) {
1298 /* Not RFC conform, bail out. */