95f47c2
commit 883967347e817e1743d265d19a95a432cf875b5e
95f47c2
Author: Slava Zanko <slavazanko@gmail.com>
95f47c2
Date:   Wed Aug 5 09:44:39 2009 +0300
95f47c2
95f47c2
    Ticket #121 (support IPv6).
95f47c2
    
95f47c2
    121_support_IPv6 -> origin/121_support_IPv6
95f47c2
    
95f47c2
    Thanks to Dan Kopecek.
95f47c2
    
95f47c2
    Signed-off-by: Slava Zanko <slavazanko@gmail.com>
95f47c2
95f47c2
diff --git a/vfs/ftpfs.c b/vfs/ftpfs.c
95f47c2
index 9ac03bc..f2baaee 100644
95f47c2
--- a/vfs/ftpfs.c
95f47c2
+++ b/vfs/ftpfs.c
95f47c2
@@ -652,17 +652,17 @@ ftpfs_get_proxy_host_and_port (const char *proxy, char **host, int *port)
a176322
 static int
a176322
 ftpfs_open_socket (struct vfs_class *me, struct vfs_s_super *super)
a176322
 {
a176322
-    struct   sockaddr_in server_address;
a176322
-    struct   hostent *hp;
a176322
-    int      my_socket;
95f47c2
-    char     *host;
a176322
-    int      port = SUP.port;
95f47c2
-    int      free_host = 0;
95f47c2
+    struct addrinfo hints, *res, *curr_res;
95f47c2
+    int      my_socket = 0;
95f47c2
+    char     *host = NULL;
95f47c2
+    char     *port = NULL;
a176322
+    int      tmp_port;
a176322
+    int      e;
a176322
 
a176322
     (void) me;
a176322
     
95f47c2
     /* Use a proxy host? */
95f47c2
-    host = SUP.host;
95f47c2
+    host = g_strdup(SUP.host);
95f47c2
 
95f47c2
     if (!host || !*host){
95f47c2
 	print_vfs_message (_("ftpfs: Invalid host name."));
95f47c2
@@ -671,61 +671,86 @@ ftpfs_open_socket (struct vfs_class *me, struct vfs_s_super *super)
a176322
     }
a176322
 
a176322
     /* Hosts to connect to that start with a ! should use proxy */
a176322
+    tmp_port = SUP.port;
a176322
+
a176322
     if (SUP.proxy){
a176322
-	ftpfs_get_proxy_host_and_port (ftpfs_proxy_host, &host, &port);
95f47c2
-	free_host = 1;
95f47c2
+	char *orig_host = host;
95f47c2
+
a176322
+	ftpfs_get_proxy_host_and_port (ftpfs_proxy_host, &host, &tmp_port);
95f47c2
+
95f47c2
+	if (orig_host != host)
95f47c2
+	    g_free(orig_host);
95f47c2
+    }
95f47c2
+
95f47c2
+    port = g_strdup_printf("%hu", (unsigned short) tmp_port);
95f47c2
+    if (port == NULL) {
95f47c2
+	g_free (host);
95f47c2
+	ftpfs_errno = errno;
95f47c2
+	return -1;
a176322
     }
a176322
 
95f47c2
     enable_interrupt_key(); /* clear the interrupt flag */
a176322
-    
a176322
-    /* Get host address */
a176322
-    memset ((char *) &server_address, 0, sizeof (server_address));
a176322
-    server_address.sin_family = AF_INET;
a176322
-    server_address.sin_addr.s_addr = inet_addr (host);
a176322
-    if (server_address.sin_addr.s_addr == INADDR_NONE) {
a176322
-	hp = gethostbyname (host);
a176322
-	if (hp == NULL){
a176322
-	    disable_interrupt_key();
a176322
-	    print_vfs_message (_("ftpfs: Invalid host address."));
a176322
-	    ftpfs_errno = EINVAL;
a176322
-	    if (free_host)
a176322
-		g_free (host);
a176322
-	    return -1;
a176322
-	}
a176322
-	server_address.sin_family = hp->h_addrtype;
95f47c2
 
a176322
-	/* We copy only 4 bytes, we cannot trust hp->h_length, as it comes from the DNS */
a176322
-	memcpy ((char *) &server_address.sin_addr, (char *) hp->h_addr, 4);
95f47c2
-    }
95f47c2
+    memset (&hints, 0, sizeof (struct addrinfo));
95f47c2
+    hints.ai_socktype = SOCK_STREAM;
95f47c2
+    hints.ai_flags = AI_ADDRCONFIG;
a176322
 
a176322
-    server_address.sin_port = htons (port);
a176322
 
a176322
-    /* Connect */
a176322
-    if ((my_socket = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
95f47c2
+    e = getaddrinfo (host, port, &hints, &res;;
95f47c2
+    g_free (port);
95f47c2
+    port = NULL;
95f47c2
+
95f47c2
+    if ( e != 0 ) {
95f47c2
 	disable_interrupt_key();
a176322
-	ftpfs_errno = errno;
a176322
-        if (free_host)
a176322
-	    g_free (host);
95f47c2
+	print_vfs_message (_("ftpfs: %s"), gai_strerror (e));
95f47c2
+	g_free (host);
95f47c2
+	ftpfs_errno = EINVAL;
95f47c2
 	return -1;
95f47c2
     }
a176322
-    
a176322
-    print_vfs_message (_("ftpfs: making connection to %s"), host);
a176322
-    if (free_host)
a176322
+
95f47c2
+    for (curr_res = res; curr_res != NULL; curr_res = curr_res->ai_next) {
95f47c2
+
95f47c2
+	my_socket = socket (curr_res->ai_family, curr_res->ai_socktype, curr_res->ai_protocol);
95f47c2
+
95f47c2
+	if (my_socket < 0) {
95f47c2
+
95f47c2
+	    if (curr_res->ai_next != NULL)
95f47c2
+		continue;
95f47c2
+
95f47c2
+	    disable_interrupt_key();
95f47c2
+	    print_vfs_message (_("ftpfs: %s"), unix_error_string (errno));
95f47c2
+	    g_free (host);
95f47c2
+	    freeaddrinfo (res);
95f47c2
+	    ftpfs_errno = errno;
95f47c2
+	    return -1;
95f47c2
+	}
95f47c2
+
95f47c2
+	print_vfs_message (_("ftpfs: making connection to %s"), host);
a176322
 	g_free (host);
95f47c2
+	host = NULL;
95f47c2
+
95f47c2
+	if ( connect (my_socket, curr_res->ai_addr, curr_res->ai_addrlen) >= 0 )
95f47c2
+	    break;
95f47c2
 
a176322
-    if (connect (my_socket, (struct sockaddr *) &server_address,
a176322
-	     sizeof (server_address)) < 0){
95f47c2
 	ftpfs_errno = errno;
a176322
-	if (errno == EINTR && got_interrupt ())
95f47c2
+	close (my_socket);
a176322
+
95f47c2
+	if (errno == EINTR && got_interrupt ()) {
a176322
 	    print_vfs_message (_("ftpfs: connection interrupted by user"));
a176322
-	else
95f47c2
+	} else if (res->ai_next == NULL) {
a176322
 	    print_vfs_message (_("ftpfs: connection to server failed: %s"),
a176322
-				   unix_error_string(errno));
a176322
-	disable_interrupt_key();
a176322
-	close (my_socket);
95f47c2
+				unix_error_string (errno));
95f47c2
+	} else {
a176322
+	    continue;
95f47c2
+	}
95f47c2
+
95f47c2
+	freeaddrinfo (res);
95f47c2
+	disable_interrupt_key ();
95f47c2
 	return -1;
a176322
     }
a176322
-    disable_interrupt_key();
a176322
+
95f47c2
+    freeaddrinfo (res);
a176322
+    disable_interrupt_key ();
a176322
     return my_socket;
a176322
 }
a176322
 
95f47c2
@@ -874,84 +899,174 @@ ftpfs_get_current_directory (struct vfs_class *me, struct vfs_s_super *super)
a176322
     
a176322
 /* Setup Passive ftp connection, we use it for source routed connections */
a176322
 static int
a176322
-ftpfs_setup_passive (struct vfs_class *me, struct vfs_s_super *super, int my_socket, struct sockaddr_in *sa)
95f47c2
+ftpfs_setup_passive (struct vfs_class *me, struct vfs_s_super *super,
95f47c2
+	int my_socket, struct sockaddr_storage *sa, socklen_t *salen)
a176322
 {
95f47c2
-    int xa, xb, xc, xd, xe, xf;
95f47c2
-    char n [6];
95f47c2
     char *c;
95f47c2
-    
95f47c2
-    if (ftpfs_command (me, super, WAIT_REPLY | WANT_STRING, "PASV") != COMPLETE)
a176322
-	return 0;
95f47c2
 
95f47c2
-    /* Parse remote parameters */
95f47c2
-    for (c = reply_str + 4; (*c) && (!isdigit ((unsigned char) *c)); c++)
a176322
-	;
95f47c2
-    if (!*c)
a176322
-	return 0;
95f47c2
-    if (!isdigit ((unsigned char) *c))
a176322
-	return 0;
95f47c2
-    if (sscanf (c, "%d,%d,%d,%d,%d,%d", &xa, &xb, &xc, &xd, &xe, &xf) != 6)
95f47c2
+    if (ftpfs_command (me, super, WAIT_REPLY | WANT_STRING, "EPSV") == COMPLETE) {
95f47c2
+	int port;
95f47c2
+	/* (|||<port>|) */
95f47c2
+	c = strchr (reply_str, '|');
95f47c2
+	if (c == NULL)
95f47c2
+	    return 0;
95f47c2
+	if(strlen(c) > 3)
95f47c2
+	    c+=3;
95f47c2
+	else
95f47c2
+	    return 0;
95f47c2
+
95f47c2
+	port = atoi (c);
95f47c2
+	if (port < 0 || port > 65535)
95f47c2
+	    return 0;
95f47c2
+	port = htons (port);
95f47c2
+
95f47c2
+	switch (sa->ss_family) {
95f47c2
+	case AF_INET:
95f47c2
+	    ((struct sockaddr_in *)sa)->sin_port = port;
95f47c2
+	break;
95f47c2
+	case AF_INET6:
95f47c2
+	    ((struct sockaddr_in6 *)sa)->sin6_port = port;
95f47c2
+	break;
95f47c2
+	default:
95f47c2
+	    print_vfs_message (_("ftpfs: invalid address family"));
95f47c2
+	    ERRNOR (EINVAL, -1);
95f47c2
+	}
95f47c2
+    } else if (sa->ss_family == AF_INET) {
95f47c2
+	int xa, xb, xc, xd, xe, xf;
95f47c2
+	char n [6];
95f47c2
+
95f47c2
+	if (ftpfs_command (me, super, WAIT_REPLY | WANT_STRING, "PASV") != COMPLETE)
95f47c2
+	    return 0;
95f47c2
+
95f47c2
+	/* Parse remote parameters */
95f47c2
+	for (c = reply_str + 4; (*c) && (!isdigit ((unsigned char) *c)); c++);
95f47c2
+
95f47c2
+	if (!*c)
95f47c2
+	    return 0;
95f47c2
+	if (!isdigit ((unsigned char) *c))
95f47c2
+	    return 0;
95f47c2
+	if (sscanf (c, "%d,%d,%d,%d,%d,%d", &xa, &xb, &xc, &xd, &xe, &xf) != 6)
95f47c2
+	    return 0;
95f47c2
+
95f47c2
+	n [0] = (unsigned char) xa;
95f47c2
+	n [1] = (unsigned char) xb;
95f47c2
+	n [2] = (unsigned char) xc;
95f47c2
+	n [3] = (unsigned char) xd;
95f47c2
+	n [4] = (unsigned char) xe;
95f47c2
+	n [5] = (unsigned char) xf;
95f47c2
+
95f47c2
+	memcpy (&(((struct sockaddr_in *)sa)->sin_addr.s_addr), (void *)n, 4);
95f47c2
+	memcpy (&(((struct sockaddr_in *)sa)->sin_port), (void *)&n[4], 2);
95f47c2
+    } else
95f47c2
 	return 0;
95f47c2
-    n [0] = (unsigned char) xa;
95f47c2
-    n [1] = (unsigned char) xb;
95f47c2
-    n [2] = (unsigned char) xc;
95f47c2
-    n [3] = (unsigned char) xd;
95f47c2
-    n [4] = (unsigned char) xe;
95f47c2
-    n [5] = (unsigned char) xf;
95f47c2
-
a176322
-    memcpy (&(sa->sin_addr.s_addr), (void *)n, 4);
a176322
-    memcpy (&(sa->sin_port), (void *)&n[4], 2);
a176322
-    if (connect (my_socket, (struct sockaddr *) sa, sizeof (struct sockaddr_in)) < 0)
a176322
+
95f47c2
+    if (connect (my_socket, (struct sockaddr *) sa, *salen ) < 0)
95f47c2
 	return 0;
95f47c2
+
95f47c2
     return 1;
a176322
 }
a176322
 
a176322
 static int
a176322
 ftpfs_initconn (struct vfs_class *me, struct vfs_s_super *super)
a176322
 {
a176322
-    struct sockaddr_in data_addr;
a176322
-    int data;
a176322
-    socklen_t len = sizeof(data_addr);
a176322
-    struct protoent *pe;
95f47c2
+    struct sockaddr_storage data_addr;
95f47c2
+    socklen_t data_addrlen;
95f47c2
+    int data_sock;
a176322
 
a176322
-    pe = getprotobyname ("tcp");
a176322
-    if (pe == NULL)
a176322
-	ERRNOR (EIO, -1);
a176322
 again:
a176322
-    if (getsockname (SUP.sock, (struct sockaddr *) &data_addr, &len) == -1)
a176322
-	ERRNOR (EIO, -1);
a176322
-    data_addr.sin_port = 0;
a176322
-    
a176322
-    data = socket (AF_INET, SOCK_STREAM, pe->p_proto);
a176322
-    if (data < 0)
a176322
-	ERRNOR (EIO, -1);
95f47c2
+    memset (&data_addr, 0, sizeof (struct sockaddr_storage));
95f47c2
+    data_addrlen = sizeof (struct sockaddr_storage);
95f47c2
+
95f47c2
+    if (getpeername (SUP.sock, (struct sockaddr *) &data_addr, &data_addrlen) == -1)
95f47c2
+	return -1;
95f47c2
+
95f47c2
+    switch (data_addr.ss_family) {
95f47c2
+    case AF_INET:
95f47c2
+	((struct sockaddr_in *)&data_addr)->sin_port = 0;
95f47c2
+    break;
95f47c2
+    case AF_INET6:
95f47c2
+	((struct sockaddr_in6 *)&data_addr)->sin6_port = 0;
95f47c2
+    break;
95f47c2
+    default:
95f47c2
+	print_vfs_message (_("ftpfs: invalid address family"));
95f47c2
+	ERRNOR(EINVAL, -1);
95f47c2
+    }
95f47c2
+
95f47c2
+    data_sock = socket (data_addr.ss_family, SOCK_STREAM, IPPROTO_TCP);
95f47c2
+    if (data_sock < 0) {
95f47c2
+	if (SUP.use_passive_connection) {
95f47c2
+	    print_vfs_message (_("ftpfs: could not setup passive mode: %s"), unix_error_string (errno));
95f47c2
+	    SUP.use_passive_connection = 0;
95f47c2
+	    goto again;
95f47c2
+	}
95f47c2
+
95f47c2
+	print_vfs_message (_("ftpfs: could not create socket: %s"), unix_error_string (errno));
95f47c2
+	return -1;
95f47c2
+    }
a176322
 
95f47c2
     if (SUP.use_passive_connection) {
a176322
-	if (ftpfs_setup_passive (me, super, data, &data_addr))
a176322
-	    return data;
95f47c2
+
95f47c2
+	if (ftpfs_setup_passive (me, super, data_sock, &data_addr, &data_addrlen))
95f47c2
+	    return data_sock;
95f47c2
 
95f47c2
 	SUP.use_passive_connection = 0;
95f47c2
 	print_vfs_message (_("ftpfs: could not setup passive mode"));
a176322
 
a176322
-	/* data or data_addr may be damaged by ftpfs_setup_passive */
a176322
-	close (data);
95f47c2
+	close (data_sock);
95f47c2
 	goto again;
a176322
     }
a176322
 
a176322
     /* If passive setup fails, fallback to active connections */
a176322
     /* Active FTP connection */
a176322
-    if ((bind (data, (struct sockaddr *)&data_addr, len) == 0) &&
a176322
-	(getsockname (data, (struct sockaddr *) &data_addr, &len) == 0) && 
a176322
-	(listen (data, 1) == 0))
95f47c2
-    {
a176322
-	unsigned char *a = (unsigned char *)&data_addr.sin_addr;
a176322
-	unsigned char *p = (unsigned char *)&data_addr.sin_port;
95f47c2
+    if ((bind (data_sock, (struct sockaddr *)&data_addr, data_addrlen) == 0) &&
95f47c2
+      (getsockname (data_sock, (struct sockaddr *)&data_addr, &data_addrlen) == 0) && 
95f47c2
+      (listen (data_sock, 1) == 0)) {
95f47c2
+	unsigned short int port;
95f47c2
+	char *addr;
95f47c2
+	unsigned int af;
95f47c2
+
95f47c2
+	switch (data_addr.ss_family) {
95f47c2
+	case AF_INET: 
95f47c2
+	    af = FTP_INET;
95f47c2
+	    port = ((struct sockaddr_in *)&data_addr)->sin_port;
a176322
+	break;
95f47c2
+	case AF_INET6: 
95f47c2
+	    af = FTP_INET6;
95f47c2
+	    port = ((struct sockaddr_in6 *)&data_addr)->sin6_port;
a176322
+	break;
95f47c2
+	default:
95f47c2
+	    print_vfs_message (_("ftpfs: invalid address family"));
95f47c2
+	    ERRNOR (EINVAL, -1);
95f47c2
+	}
95f47c2
+
95f47c2
+	port = ntohs (port);
95f47c2
+
95f47c2
+	addr = malloc (NI_MAXHOST);
95f47c2
+	if (addr == NULL)
95f47c2
+	    ERRNOR (ENOMEM, -1);
a176322
+
95f47c2
+	if (getnameinfo ((struct sockaddr *)&data_addr, data_addrlen, addr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST) != 0) {
95f47c2
+	    g_free (addr);
95f47c2
+	    ERRNOR (EIO, -1);
95f47c2
+	}
95f47c2
+
95f47c2
+	if (ftpfs_command (me, super, WAIT_REPLY, "EPRT |%u|%s|%hu|", af, addr, port) == COMPLETE) {
95f47c2
+	    g_free (addr);
95f47c2
+	    return data_sock;
95f47c2
+	}
a176322
+	g_free (addr);
95f47c2
+
95f47c2
+	if (FTP_INET == af) {
95f47c2
+	    unsigned char *a = (unsigned char *)&((struct sockaddr_in *)&data_addr)->sin_addr;
95f47c2
+	    unsigned char *p = (unsigned char *)&por;;
a176322
 	
a176322
-	if (ftpfs_command (me, super, WAIT_REPLY, "PORT %d,%d,%d,%d,%d,%d", a[0], a[1], 
a176322
-		     a[2], a[3], p[0], p[1]) == COMPLETE)
a176322
-	    return data;
95f47c2
+	if (ftpfs_command (me, super, WAIT_REPLY, 
95f47c2
+			    "PORT %u,%u,%u,%u,%u,%u", a[0], a[1], a[2], a[3],
95f47c2
+			    p[0], p[1]) == COMPLETE)
a176322
+	  return data_sock;
95f47c2
+	}
a176322
     }
a176322
-    close (data);
95f47c2
+    close (data_sock);
95f47c2
     ftpfs_errno = EIO;
95f47c2
     return -1;
a176322
 }
95f47c2
@@ -960,7 +1075,7 @@ static int
a176322
 ftpfs_open_data_connection (struct vfs_class *me, struct vfs_s_super *super, const char *cmd,
a176322
 		      const char *remote, int isbinary, int reget)
a176322
 {
a176322
-    struct sockaddr_in from;
a176322
+    struct sockaddr_storage from;
a176322
     int s, j, data;
a176322
     socklen_t fromlen = sizeof(from);
a176322
     
95f47c2
diff --git a/vfs/ftpfs.h b/vfs/ftpfs.h
95f47c2
index b2003db..0f41b18 100644
95f47c2
--- a/vfs/ftpfs.h
95f47c2
+++ b/vfs/ftpfs.h
95f47c2
@@ -21,6 +21,9 @@ extern int ftpfs_first_cd_then_ls;
a176322
 void ftpfs_init_passwd (void);
a176322
 void init_ftpfs (void);
a176322
 
a176322
+#define FTP_INET  1
a176322
+#define FTP_INET6 2
a176322
+
a176322
 #define OPT_FLUSH        1
a176322
 #define OPT_IGNORE_ERROR 2
a176322
 
95f47c2
diff --git a/vfs/utilvfs.c b/vfs/utilvfs.c
95f47c2
index af05144..f53914a 100644
95f47c2
--- a/vfs/utilvfs.c
95f47c2
+++ b/vfs/utilvfs.c
95f47c2
@@ -123,7 +123,21 @@ vfs_split_url (const char *path, char **host, char **user, int *port,
49c5cab
     }
49c5cab
 
49c5cab
     /* Check if the host comes with a port spec, if so, chop it */
49c5cab
-    colon = strchr (rest, ':');
49c5cab
+    if ('[' == *rest) {
95f47c2
+	colon = strchr (++rest, ']');
95f47c2
+	if (colon) {
95f47c2
+	    colon[0] = '\0';
95f47c2
+	    colon[1] = '\0';
95f47c2
+	    colon++;
95f47c2
+	} else {
95f47c2
+	    g_free (pcopy);
95f47c2
+	    *host = NULL;
95f47c2
+	    *port = 0;
95f47c2
+	    return NULL;
95f47c2
+	}
49c5cab
+    } else
95f47c2
+	colon = strchr (rest, ':');
49c5cab
+
49c5cab
     if (colon) {
49c5cab
 	*colon = 0;
49c5cab
 	if (sscanf (colon + 1, "%d", port) == 1) {