From b19eda0e509579d877ec431c1da81808c47b8eb2 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 16 Jul 2013 17:02:46 +0200 Subject: [PATCH 1/1] Fix parsing of address[:port] strings for IPv6 addresses, bug #515 --- NEWS | 1 + doc/manpages/man5/afp.conf.5.xml | 2 + include/atalk/util.h | 1 + libatalk/dsi/dsi_tcp.c | 56 ++++++++++++++------- libatalk/util/socket.c | 86 ++++++++++++++++++++++++++++++++ man/man5/afp.conf.5.in | 2 + 6 files changed, 131 insertions(+), 17 deletions(-) diff --git a/NEWS b/NEWS index 101fc751..225c29d0 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ Changes in 3.0.5 ================ * FIX: Fix a crash when using pam_winbind. Fixes bug #516. * NEW: New global/volume option "ignored attributes" +* FIX: "afp listen" option failed to take IPv6 addresses. Bug #515. Changes in 3.0.4 ================ diff --git a/doc/manpages/man5/afp.conf.5.xml b/doc/manpages/man5/afp.conf.5.xml index 0115b897..e67d651c 100644 --- a/doc/manpages/man5/afp.conf.5.xml +++ b/doc/manpages/man5/afp.conf.5.xml @@ -575,6 +575,8 @@ any incoming request. The network address may be specified either in dotted-decimal format for IPv4 or in hexadecimal format for IPv6. + IPv6 address + port combination must use URL the format + using square brackets [IPv6]:port diff --git a/include/atalk/util.h b/include/atalk/util.h index facc7763..7000499b 100644 --- a/include/atalk/util.h +++ b/include/atalk/util.h @@ -147,6 +147,7 @@ extern const char *getip_string(const struct sockaddr *sa); extern unsigned int getip_port(const struct sockaddr *sa); extern void apply_ip_mask(struct sockaddr *ai, int maskbits); extern int compare_ip(const struct sockaddr *sa1, const struct sockaddr *sa2); +extern int tokenize_ip_port(const char *ipurl, char **address, char **port); /* Structures and functions dealing with dynamic pollfd arrays */ enum fdtype {IPC_FD, LISTEN_FD}; diff --git a/libatalk/dsi/dsi_tcp.c b/libatalk/dsi/dsi_tcp.c index 731252e6..ad8a3a23 100644 --- a/libatalk/dsi/dsi_tcp.c +++ b/libatalk/dsi/dsi_tcp.c @@ -304,25 +304,45 @@ iflist_done: #define AI_NUMERICSERV 0 #endif -/* this needs to accept passed in addresses */ +/*! + * Initialize DSI over TCP + * + * @param dsi (rw) DSI handle + * @param hostname (r) pointer to hostname string + * @param inaddress (r) Optional IPv4 or IPv6 address with an optional port, may be NULL + * @param inport (r) pointer to port string + * + * Creates listening AFP/DSI socket. If the parameter inaddress is NULL, then we listen + * on the wildcard address, ie on all interfaces. That should mean listening on the IPv6 + * address "::" on IPv4/IPv6 dual stack kernels, accepting both v4 and v6 requests. + * + * If the parameter inaddress is not NULL, then we only listen on the given address. + * The parameter may contain a port number using the URL format for address and port: + * + * IPv4, IPv4:port, IPv6, [IPv6], [IPv6]:port + * + * Parameter inport must be a valid pointer to a port string and is used if the inaddress + * parameter doesn't contain a port. + * + * @returns 0 on success, -1 on failure + */ int dsi_tcp_init(DSI *dsi, const char *hostname, const char *inaddress, const char *inport) { EC_INIT; int flag, err; - char *a = NULL, *b; - const char *address; - const char *port; + char *address = NULL, *port = NULL; struct addrinfo hints, *servinfo, *p; - /* Check whether address is of the from IP:PORT and split */ - address = inaddress; - port = inport; - if (address && strchr(address, ':')) { - EC_NULL_LOG( address = a = strdup(address) ); - b = strchr(a, ':'); - *b = 0; - port = b + 1; - } + /* inaddress may be NULL */ + AFP_ASSERT(dsi && hostname && inport); + + if (inaddress) + /* Check whether address is of the from IP:PORT and split */ + EC_ZERO( tokenize_ip_port(inaddress, &address, &port) ); + + if (port == NULL) + /* inport is supposed to always contain a valid port string */ + EC_NULL( port = strdup(inport) ); /* Prepare hint for getaddrinfo */ memset(&hints, 0, sizeof hints); @@ -343,8 +363,8 @@ int dsi_tcp_init(DSI *dsi, const char *hostname, const char *inaddress, const ch hints.ai_family = AF_UNSPEC; #endif } - if ((ret = getaddrinfo(address ? address : NULL, port, &hints, &servinfo)) != 0) { - LOG(log_error, logtype_dsi, "dsi_tcp_init: getaddrinfo: %s\n", gai_strerror(ret)); + if ((ret = getaddrinfo(address, port, &hints, &servinfo)) != 0) { + LOG(log_error, logtype_dsi, "dsi_tcp_init(%s): getaddrinfo: %s\n", address ? address : "*", gai_strerror(ret)); EC_FAIL; } @@ -447,8 +467,10 @@ interfaces: guess_interface(dsi, hostname, port ? port : "548"); EC_CLEANUP: - if (a) - free(a); + if (address) + free(address); + if (port) + free(port); EC_EXIT; } diff --git a/libatalk/util/socket.c b/libatalk/util/socket.c index acea8ad3..c7ef534a 100644 --- a/libatalk/util/socket.c +++ b/libatalk/util/socket.c @@ -39,6 +39,7 @@ #include #include +#include static char ipv4mapprefix[] = {0,0,0,0,0,0,0,0,0,0,0xff,0xff}; @@ -417,6 +418,91 @@ int compare_ip(const struct sockaddr *sa1, const struct sockaddr *sa2) return ret; } +/*! + * Tokenize IP(4/6) addresses with an optional port into address and port + * + * @param ipurl (r) IP URL string + * @param address (w) IP address + * @param port (w) IP port + * + * @returns 0 on success, -1 on failure + * + * Tokenize IPv4, IPv4:port, IPv6, [IPv6] or [IPv6:port] URL into address and + * port and return two allocated strings with the address and the port. + * + * If the function returns 0, then address point to a newly allocated + * valid address string, port may either be NULL or point to a newly + * allocated port number. + * + * If the function returns -1, then the contents of address and port are + * undefined. + */ +int tokenize_ip_port(const char *ipurl, char **address, char **port) +{ + EC_INIT; + char *p = NULL; + char *s; + + AFP_ASSERT(ipurl && address && port); + EC_NULL( p = strdup(ipurl)); + + /* Either ipv4, ipv4:port, ipv6, [ipv6] or [ipv6]:port */ + + if (!strchr(p, ':')) { + /* IPv4 address without port */ + *address = p; + p = NULL; /* prevent free() */ + *port = NULL; + EC_EXIT_STATUS(0); + } + + /* Either ipv4:port, ipv6, [ipv6] or [ipv6]:port */ + + if (strchr(p, '.')) { + /* ipv4:port */ + *address = p; + p = strchr(p, ':'); + *p = '\0'; + EC_NULL( *port = strdup(p + 1)); + p = NULL; /* prevent free() */ + EC_EXIT_STATUS(0); + } + + /* Either ipv6, [ipv6] or [ipv6]:port */ + + if (p[0] != '[') { + /* ipv6 */ + *address = p; + p = NULL; /* prevent free() */ + *port = NULL; + EC_EXIT_STATUS(0); + } + + /* [ipv6] or [ipv6]:port */ + + EC_NULL( *address = strdup(p + 1) ); + + if ((s = strchr(*address, ']')) == NULL) { + LOG(log_error, logtype_dsi, "tokenize_ip_port: malformed ipv6 address %s\n", ipurl); + EC_FAIL; + } + *s = '\0'; + /* address now points to the ipv6 address without [] */ + + if (s[1] == ':') { + /* [ipv6]:port */ + EC_NULL( *port = strdup(s + 2) ); + } else { + /* [ipv6] */ + *port = NULL; + } + +EC_CLEANUP: + if (p) + free(p); + EC_EXIT; +} + /*! * Add a fd to a dynamic pollfd array that is allocated and grown as needed * diff --git a/man/man5/afp.conf.5.in b/man/man5/afp.conf.5.in index d9a2304c..017a6fa4 100644 --- a/man/man5/afp.conf.5.in +++ b/man/man5/afp.conf.5.in @@ -424,6 +424,8 @@ afp listen = \fIip address[:port] [ip address[:port] \&.\&.\&.]\fR \fB(G)\fR Specifies the IP address that the server should advertise \fBand\fR listens to\&. The default is advertise the first IP address of the system, but to listen for any incoming request\&. The network address may be specified either in dotted\-decimal format for IPv4 or in hexadecimal format for IPv6\&. +.sp +IPv6 address + port combination must use URL the format using square brackets [IPv6]:port .RE .PP afp port = \fIport number\fR \fB(G)\fR -- 2.39.2