autofs-5.0.8 - fix ipv6 libtirpc getport From: Ian Kent The method that was being used to obtain a service port number when using libtirpc was wrong. --- CHANGELOG | 1 lib/rpc_subs.c | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 267 insertions(+), 17 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 68db340..9c87373 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ - fix task manager not getting signaled. - allow --with-systemd to take a path arg. - fix WITH_LIBTIRPC function name. +- fix ipv6 libtirpc getport. 17/10/2013 autofs-5.0.8 ======================= diff --git a/lib/rpc_subs.c b/lib/rpc_subs.c index 46b3e8d..2365b6e 100644 --- a/lib/rpc_subs.c +++ b/lib/rpc_subs.c @@ -234,6 +234,28 @@ static int rpc_do_create_client(struct sockaddr *addr, struct conn_info *info, i return 0; } +static int rpc_getport(struct conn_info *info, + struct pmap *parms, CLIENT *client) +{ + enum clnt_stat status; + + /* + * Check to see if server is up otherwise a getport will take + * forever to timeout. + */ + status = clnt_call(client, PMAPPROC_NULL, + (xdrproc_t) xdr_void, 0, (xdrproc_t) xdr_void, 0, + info->timeout); + + if (status == RPC_SUCCESS) { + status = clnt_call(client, PMAPPROC_GETPORT, + (xdrproc_t) xdr_pmap, (caddr_t) parms, + (xdrproc_t) xdr_u_short, (caddr_t) port, + info->timeout); + } + + return status; +} #else static int rpc_do_create_client(struct sockaddr *addr, struct conn_info *info, int *fd, CLIENT **client) { @@ -267,9 +289,6 @@ static int rpc_do_create_client(struct sockaddr *addr, struct conn_info *info, i laddr = (struct sockaddr *) &in4_laddr; in4_raddr->sin_port = htons(info->port); slen = sizeof(struct sockaddr_in); - /* Use rpcbind v2 for AF_INET */ - if (info->program == rpcb_prog) - info->version = PMAPVERS; } else if (addr->sa_family == AF_INET6) { struct sockaddr_in6 *in6_raddr = (struct sockaddr_in6 *) addr; in6_laddr.sin6_family = AF_INET6; @@ -324,6 +343,244 @@ static int rpc_do_create_client(struct sockaddr *addr, struct conn_info *info, i return 0; } + +/* + * Thankfully nfs-utils had already dealt with this. + * Thanks to Chuck Lever for his nfs-utils patch series, much of + * which is used here. + */ +static pthread_mutex_t proto_mutex = PTHREAD_MUTEX_INITIALIZER; + +static enum clnt_stat rpc_get_netid(const sa_family_t family, + const int protocol, char **netid) +{ + char *nc_protofmly, *nc_proto, *nc_netid; + struct netconfig *nconf; + struct protoent *proto; + void *handle; + + switch (family) { + case AF_LOCAL: + case AF_INET: + nc_protofmly = NC_INET; + break; + case AF_INET6: + nc_protofmly = NC_INET6; + break; + default: + return RPC_UNKNOWNPROTO; + } + + pthread_mutex_lock(&proto_mutex); + proto = getprotobynumber(protocol); + if (!proto) { + pthread_mutex_unlock(&proto_mutex); + return RPC_UNKNOWNPROTO; + } + nc_proto = strdup(proto->p_name); + pthread_mutex_unlock(&proto_mutex); + if (!nc_proto) + return RPC_SYSTEMERROR; + + handle = setnetconfig(); + while ((nconf = getnetconfig(handle)) != NULL) { + if (nconf->nc_protofmly != NULL && + strcmp(nconf->nc_protofmly, nc_protofmly) != 0) + continue; + if (nconf->nc_proto != NULL && + strcmp(nconf->nc_proto, nc_proto) != 0) + continue; + + nc_netid = strdup(nconf->nc_netid); + if (!nc_netid) { + free(nc_proto); + return RPC_SYSTEMERROR; + } + + *netid = nc_netid; + } + endnetconfig(handle); + free(nc_proto); + + return RPC_SUCCESS; +} + +static char *rpc_sockaddr2universal(const struct sockaddr *addr) +{ + const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *) addr; + const struct sockaddr_un *sun = (const struct sockaddr_un *) addr; + const struct sockaddr_in *sin = (const struct sockaddr_in *) addr; + char buf[INET6_ADDRSTRLEN + 8 /* for port information */]; + uint16_t port; + size_t count; + char *result; + int len; + + switch (addr->sa_family) { + case AF_LOCAL: + return strndup(sun->sun_path, sizeof(sun->sun_path)); + case AF_INET: + if (inet_ntop(AF_INET, (const void *)&sin->sin_addr.s_addr, + buf, (socklen_t)sizeof(buf)) == NULL) + goto out_err; + port = ntohs(sin->sin_port); + break; + case AF_INET6: + if (inet_ntop(AF_INET6, (const void *)&sin6->sin6_addr, + buf, (socklen_t)sizeof(buf)) == NULL) + goto out_err; + port = ntohs(sin6->sin6_port); + break; + default: + goto out_err; + } + + count = sizeof(buf) - strlen(buf); + len = snprintf(buf + strlen(buf), count, ".%u.%u", + (unsigned)(port >> 8), (unsigned)(port & 0xff)); + /* before glibc 2.0.6, snprintf(3) could return -1 */ + if (len < 0 || (size_t)len > count) + goto out_err; + + result = strdup(buf); + return result; + +out_err: + return NULL; +} + +static int rpc_universal2port(const char *uaddr) +{ + char *addrstr; + char *p, *endptr; + unsigned long portlo, porthi; + int port = -1; + + addrstr = strdup(uaddr); + if (!addrstr) + return -1; + + p = strrchr(addrstr, '.'); + if (!p) + goto out; + + portlo = strtoul(p + 1, &endptr, 10); + if (*endptr != '\0' || portlo > 255) + goto out; + *p = '\0'; + + p = strrchr(addrstr, '.'); + if (!p) + goto out; + + porthi = strtoul(p + 1, &endptr, 10); + if (*endptr != '\0' || porthi > 255) + goto out; + *p = '\0'; + + port = (porthi << 8) | portlo; + +out: + free(addrstr); + return port; +} + +static enum clnt_stat rpc_rpcb_getport(CLIENT *client, + struct rpcb *parms, + struct timeval timeout, + unsigned short *port) +{ + rpcvers_t rpcb_version; + struct rpc_err rpcerr; + int s_port = 0; + + for (rpcb_version = RPCBVERS_4; + rpcb_version >= RPCBVERS_3; + rpcb_version--) { + enum clnt_stat status; + char *uaddr = NULL; + + CLNT_CONTROL(client, CLSET_VERS, (void *) &rpcb_version); + status = CLNT_CALL(client, (rpcproc_t) RPCBPROC_GETADDR, + (xdrproc_t) xdr_rpcb, (void *) parms, + (xdrproc_t) xdr_wrapstring, (void *) &uaddr, + timeout); + + switch (status) { + case RPC_SUCCESS: + if ((uaddr == NULL) || (uaddr[0] == '\0')) + return RPC_PROGNOTREGISTERED; + + s_port = rpc_universal2port(uaddr); + xdr_free((xdrproc_t) xdr_wrapstring, (char *) &uaddr); + if (s_port == -1) { + return RPC_N2AXLATEFAILURE; + } + *port = s_port; + return RPC_SUCCESS; + + case RPC_PROGVERSMISMATCH: + clnt_geterr(client, &rpcerr); + if (rpcerr.re_vers.low > RPCBVERS4) + return status; + continue; + case RPC_PROCUNAVAIL: + case RPC_PROGUNAVAIL: + continue; + default: + /* Most likely RPC_TIMEDOUT or RPC_CANTRECV */ + return status; + } + } + + if (s_port == 0) + return RPC_PROGNOTREGISTERED; + + return RPC_PROCUNAVAIL; +} + +static enum clnt_stat rpc_getport(struct conn_info *info, + struct pmap *parms, CLIENT *client, + unsigned short *port) +{ + enum clnt_stat status; + struct sockaddr *paddr, addr; + struct rpcb rpcb_parms; + char *netid, *raddr; + + if (info->addr) + paddr = info->addr; + else { + if (!clnt_control(client, CLGET_SERVER_ADDR, (char *) &addr)) + return RPC_UNKNOWNADDR; + paddr = &addr; + } + + netid = NULL; + status = rpc_get_netid(paddr->sa_family, info->proto, &netid); + if (status != RPC_SUCCESS) + return status; + + raddr = rpc_sockaddr2universal(paddr); + if (!raddr) { + free(netid); + return RPC_UNKNOWNADDR; + } + + memset(&rpcb_parms, 0, sizeof(rpcb_parms)); + rpcb_parms.r_prog = parms->pm_prog; + rpcb_parms.r_vers = parms->pm_vers; + rpcb_parms.r_netid = netid; + rpcb_parms.r_addr = raddr; + rpcb_parms.r_owner = ""; + + status = rpc_rpcb_getport(client, &rpcb_parms, info->timeout, port); + + free(netid); + free(raddr); + + return status; +} #endif #if defined(HAVE_GETRPCBYNAME) || defined(HAVE_GETSERVBYNAME) @@ -647,20 +904,7 @@ int rpc_portmap_getport(struct conn_info *info, return ret; } - /* - * Check to see if server is up otherwise a getport will take - * forever to timeout. - */ - status = clnt_call(client, PMAPPROC_NULL, - (xdrproc_t) xdr_void, 0, (xdrproc_t) xdr_void, 0, - pmap_info.timeout); - - if (status == RPC_SUCCESS) { - status = clnt_call(client, PMAPPROC_GETPORT, - (xdrproc_t) xdr_pmap, (caddr_t) parms, - (xdrproc_t) xdr_u_short, (caddr_t) port, - pmap_info.timeout); - } + status = rpc_getport(&pmap_info, parms, client, port); if (!info->client) { /* @@ -867,6 +1111,11 @@ static int rpc_get_exports_proto(struct conn_info *info, exports *exp) clnt_control(client, CLSET_RETRY_TIMEOUT, (char *) &info->timeout); client->cl_auth = authunix_create_default(); + if (client->cl_auth == NULL) { + error(LOGOPT_ANY, "auth create failed"); + clnt_destroy(client); + return 0; + } vers_entry = 0; while (1) {