| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| |
| /* |
| * Copyright 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| * |
| * lib/libnsl/nss/netdir_inet_sundry.c |
| * |
| * This file contains inet-specific implementations of netdir_options, |
| * uaddr2taddr, and taddr2uaddr. These implementations |
| * used to be in both tcpip.so and switch.so (identical copies). |
| * Since we got rid of those, and also it's a good idea to build-in |
| * inet-specific implementations in one place, we decided to put |
| * them in this file with a not-so glorious name. These are INET-SPECIFIC |
| * only, and will not be used for non-inet transports or by third-parties |
| * that decide to provide their own nametoaddr libs for inet transports |
| * (they are on their own for these as well => they get flexibility). |
| * |
| * Copied mostly from erstwhile lib/nametoaddr/tcpip/tcpip.c. |
| */ |
| |
| #include "mt.h" |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <thread.h> |
| #include <netconfig.h> |
| #include <netdir.h> |
| #include <nss_netdir.h> |
| #include <tiuser.h> |
| #include <sys/socket.h> |
| #include <net/if.h> |
| #include <sys/sockio.h> |
| #include <sys/fcntl.h> |
| #include <netinet/in.h> |
| #include <netinet/tcp.h> |
| #include <netinet/udp.h> |
| #include <arpa/inet.h> |
| #include <rpc/types.h> |
| #include <rpc/rpc_com.h> |
| #include <syslog.h> |
| #include <values.h> |
| #include <limits.h> |
| #include <nss_dbdefs.h> |
| #include "nss.h" |
| |
| #define MAXIFS 32 |
| |
| /* |
| * Extracted from socketvar.h |
| */ |
| #define SOV_DEFAULT 1 /* Select based on so_default_version */ |
| #define SOV_SOCKBSD 3 /* Socket with no streams operations */ |
| |
| extern int _so_socket(int, int, int, char *, int); |
| extern int _so_connect(int, struct sockaddr *, socklen_t, int); |
| extern int _so_getsockname(int, struct sockaddr *, socklen_t *, int); |
| |
| |
| static char *inet_netdir_mergeaddr(struct netconfig *, char *, char *); |
| static int bindresvport(struct netconfig *, int, struct netbuf *); |
| static int checkresvport(struct netbuf *); |
| static struct netbuf *ip_uaddr2taddr(char *); |
| static struct netbuf *ipv6_uaddr2taddr(char *); |
| |
| |
| extern char *inet_ntoa_r(struct in_addr, char *); |
| |
| int |
| __inet_netdir_options(struct netconfig *tp, int opts, int fd, char *par) |
| { |
| struct nd_mergearg *ma; |
| |
| switch (opts) { |
| case ND_SET_BROADCAST: |
| /* Every one is allowed to broadcast without asking */ |
| return (ND_OK); |
| case ND_SET_RESERVEDPORT: /* bind to a resered port */ |
| /* LINTED pointer cast */ |
| return (bindresvport(tp, fd, (struct netbuf *)par)); |
| case ND_CHECK_RESERVEDPORT: /* check if reserved prot */ |
| /* LINTED pointer cast */ |
| return (checkresvport((struct netbuf *)par)); |
| case ND_MERGEADDR: /* Merge two addresses */ |
| /* LINTED pointer cast */ |
| ma = (struct nd_mergearg *)(par); |
| ma->m_uaddr = inet_netdir_mergeaddr(tp, ma->c_uaddr, |
| ma->s_uaddr); |
| return (_nderror); |
| default: |
| return (ND_NOCTRL); |
| } |
| } |
| |
| |
| /* |
| * This routine will convert a TCP/IP internal format address |
| * into a "universal" format address. In our case it prints out the |
| * decimal dot equivalent. h1.h2.h3.h4.p1.p2 where h1-h4 are the host |
| * address and p1-p2 are the port number. |
| */ |
| char * |
| __inet_taddr2uaddr(struct netconfig *tp, struct netbuf *addr) |
| { |
| struct sockaddr_in *sa; /* our internal format */ |
| struct sockaddr_in6 *sa6; /* our internal format */ |
| char tmp[RPC_INET6_MAXUADDRSIZE]; |
| unsigned short myport; |
| |
| if (addr == NULL || tp == NULL || addr->buf == NULL) { |
| _nderror = ND_BADARG; |
| return (NULL); |
| } |
| if (strcmp(tp->nc_protofmly, NC_INET) == 0) { |
| /* LINTED pointer cast */ |
| sa = (struct sockaddr_in *)(addr->buf); |
| myport = ntohs(sa->sin_port); |
| (void) inet_ntoa_r(sa->sin_addr, tmp); |
| } else { |
| /* LINTED pointer cast */ |
| sa6 = (struct sockaddr_in6 *)(addr->buf); |
| myport = ntohs(sa6->sin6_port); |
| if (inet_ntop(AF_INET6, sa6->sin6_addr.s6_addr, tmp, |
| sizeof (tmp)) == NULL) { |
| _nderror = ND_BADARG; |
| return (NULL); |
| } |
| } |
| |
| (void) sprintf(tmp + strlen(tmp), ".%d.%d", myport >> 8, myport & 255); |
| return (strdup(tmp)); /* Doesn't return static data ! */ |
| } |
| |
| /* |
| * This internal routine will convert one of those "universal" addresses |
| * to the internal format used by the Sun TLI TCP/IP provider. |
| */ |
| struct netbuf * |
| __inet_uaddr2taddr(struct netconfig *tp, char *addr) |
| { |
| if (!addr || !tp) { |
| _nderror = ND_BADARG; |
| return (NULL); |
| } |
| if (strcmp(tp->nc_protofmly, NC_INET) == 0) |
| return (ip_uaddr2taddr(addr)); |
| else |
| return (ipv6_uaddr2taddr(addr)); |
| } |
| |
| static struct netbuf * |
| ip_uaddr2taddr(char *addr) |
| { |
| |
| struct sockaddr_in *sa; |
| uint32_t inaddr; |
| unsigned short inport; |
| int h1, h2, h3, h4, p1, p2; |
| struct netbuf *result; |
| |
| result = malloc(sizeof (struct netbuf)); |
| if (!result) { |
| _nderror = ND_NOMEM; |
| return (NULL); |
| } |
| |
| sa = calloc(1, sizeof (*sa)); |
| |
| if (!sa) { |
| free(result); |
| _nderror = ND_NOMEM; |
| return (NULL); |
| } |
| |
| result->buf = (char *)(sa); |
| result->maxlen = sizeof (struct sockaddr_in); |
| result->len = sizeof (struct sockaddr_in); |
| |
| /* XXX there is probably a better way to do this. */ |
| if (sscanf(addr, "%d.%d.%d.%d.%d.%d", &h1, &h2, &h3, &h4, |
| &p1, &p2) != 6) { |
| free(result); |
| _nderror = ND_NO_RECOVERY; |
| return (NULL); |
| } |
| |
| /* convert the host address first */ |
| inaddr = (h1 << 24) + (h2 << 16) + (h3 << 8) + h4; |
| sa->sin_addr.s_addr = htonl(inaddr); |
| |
| /* convert the port */ |
| inport = (p1 << 8) + p2; |
| sa->sin_port = htons(inport); |
| |
| sa->sin_family = AF_INET; |
| |
| return (result); |
| } |
| |
| static struct netbuf * |
| ipv6_uaddr2taddr(char *addr) |
| { |
| struct sockaddr_in6 *sa; |
| unsigned short inport; |
| int p1, p2; |
| struct netbuf *result; |
| char tmpaddr[RPC_INET6_MAXUADDRSIZE]; |
| char *dot; |
| |
| result = malloc(sizeof (struct netbuf)); |
| if (!result) { |
| _nderror = ND_NOMEM; |
| return (NULL); |
| } |
| |
| sa = calloc(1, sizeof (struct sockaddr_in6)); |
| if (!sa) { |
| free(result); |
| _nderror = ND_NOMEM; |
| return (NULL); |
| } |
| result->buf = (char *)(sa); |
| result->maxlen = sizeof (struct sockaddr_in6); |
| result->len = sizeof (struct sockaddr_in6); |
| |
| /* retrieve the ipv6 address and port info */ |
| |
| if (strlen(addr) > sizeof (tmpaddr) - 1) { |
| free(result); |
| _nderror = ND_NOMEM; |
| return (NULL); |
| } |
| |
| (void) strcpy(tmpaddr, addr); |
| |
| if ((dot = strrchr(tmpaddr, '.')) != 0) { |
| *dot = '\0'; |
| p2 = atoi(dot+1); |
| if ((dot = strrchr(tmpaddr, '.')) != 0) { |
| *dot = '\0'; |
| p1 = atoi(dot+1); |
| } |
| } |
| |
| if (dot == 0) { |
| free(result); |
| _nderror = ND_NOMEM; |
| return (NULL); |
| } |
| |
| if (inet_pton(AF_INET6, tmpaddr, sa->sin6_addr.s6_addr) == 0) { |
| free(result); |
| _nderror = ND_NOMEM; |
| return (NULL); |
| } |
| |
| /* convert the port */ |
| inport = (p1 << 8) + p2; |
| sa->sin6_port = htons(inport); |
| |
| sa->sin6_family = AF_INET6; |
| |
| return (result); |
| } |
| |
| /* |
| * Interface caching routines. The cache is refreshed every |
| * IF_CACHE_REFRESH_TIME seconds. A read-write lock is used to |
| * protect the cache. |
| */ |
| #define IF_CACHE_REFRESH_TIME 10 |
| |
| static int if_cache_refresh_time = IF_CACHE_REFRESH_TIME; |
| static rwlock_t iflock = DEFAULTRWLOCK; |
| static time_t last_updated = 0; /* protected by iflock */ |
| |
| /* |
| * Changing the data type of if_flags from uint_t to uint64_t to accomodate |
| * extra flags. Refer <net/if.h> for the extra flags. |
| */ |
| typedef struct if_info_s { |
| struct in_addr if_netmask; /* netmask in network order */ |
| struct in_addr if_address; /* address in network order */ |
| uint64_t if_flags; /* interface flags */ |
| } if_info_t; |
| |
| static if_info_t *if_info = NULL; /* if cache, protected by iflock */ |
| static int n_ifs = 0; /* number of cached interfaces */ |
| static int numifs_last = 0; /* number of interfaces last seen */ |
| |
| /* |
| * Builds the interface cache. Write lock on iflock is needed |
| * for calling this routine. It sets _nderror for error returns. |
| * Returns TRUE if successful, FALSE otherwise. |
| * Changing the structures ifreq and ifconf to lifreq and lifconf to |
| * have larger flag field. This is to accomodate the extra flags associated |
| * with the interface. Also introducing lifn which will contain the number |
| * of IPV4 interfaces present. |
| */ |
| static bool_t |
| get_if_info(void) |
| { |
| size_t needed; |
| struct lifreq *buf = NULL; |
| int numifs; |
| struct lifconf lifc; |
| struct lifreq *lifr; |
| struct lifnum lifn; |
| |
| lifn.lifn_family = AF_INET; |
| lifn.lifn_flags = 0; |
| getifnum: |
| if (nss_ioctl(AF_INET, SIOCGLIFNUM, &lifn) == -1) { |
| numifs = MAXIFS; |
| } else { |
| numifs = lifn.lifn_count; |
| } |
| /* |
| * Add a small fudge factor in case interfaces are plumbed |
| * between the SIOCGLIFNUM and SIOCGLIFCONF. |
| */ |
| needed = (numifs + 4) * sizeof (struct lifreq); |
| if (buf == NULL) |
| buf = malloc(needed); |
| else |
| buf = realloc(buf, needed); |
| if (buf == NULL) { |
| _nderror = ND_NOMEM; |
| return (FALSE); |
| } |
| |
| lifc.lifc_family = AF_INET; |
| lifc.lifc_flags = 0; |
| lifc.lifc_len = needed; |
| lifc.lifc_buf = (char *)buf; |
| if (nss_ioctl(AF_INET, SIOCGLIFCONF, &lifc) == -1) { |
| /* |
| * IP returns EINVAL if the buffer was too small to fit |
| * all of the entries. If that's the case, go back and |
| * try again. |
| */ |
| if (errno == EINVAL) |
| goto getifnum; |
| |
| free(buf); |
| free(if_info); |
| if_info = NULL; |
| _nderror = ND_SYSTEM; |
| return (FALSE); |
| } |
| numifs = lifc.lifc_len / (int)sizeof (struct lifreq); |
| |
| if (if_info == NULL || numifs > numifs_last) { |
| if (if_info == NULL) |
| if_info = malloc(numifs * sizeof (if_info_t)); |
| else |
| if_info = realloc(if_info, numifs * sizeof (if_info_t)); |
| if (if_info == NULL) { |
| free(buf); |
| _nderror = ND_NOMEM; |
| return (FALSE); |
| } |
| numifs_last = numifs; |
| } |
| |
| n_ifs = 0; |
| for (lifr = buf; lifr < (buf + numifs); lifr++) { |
| if (lifr->lifr_addr.ss_family != AF_INET) |
| continue; |
| |
| if_info[n_ifs].if_address = |
| ((struct sockaddr_in *)&lifr->lifr_addr)->sin_addr; |
| |
| if (nss_ioctl(AF_INET, SIOCGLIFFLAGS, lifr) < 0) |
| continue; |
| |
| if ((lifr->lifr_flags & IFF_UP) == 0) |
| continue; |
| if_info[n_ifs].if_flags = lifr->lifr_flags; |
| |
| if (nss_ioctl(AF_INET, SIOCGLIFNETMASK, lifr) < 0) |
| continue; |
| |
| if_info[n_ifs].if_netmask = |
| ((struct sockaddr_in *)&lifr->lifr_addr)->sin_addr; |
| n_ifs++; |
| } |
| free(buf); |
| return (TRUE); |
| } |
| |
| |
| /* |
| * Update the interface cache based on last update time. |
| */ |
| static bool_t |
| update_if_cache(void) |
| { |
| time_t curtime; |
| |
| (void) rw_wrlock(&iflock); |
| /* |
| * Check if some other thread has beaten this one to it. |
| */ |
| (void) time(&curtime); |
| if ((curtime - last_updated) >= if_cache_refresh_time) { |
| if (!get_if_info()) { |
| (void) rw_unlock(&iflock); |
| return (FALSE); |
| } |
| (void) time(&last_updated); |
| } |
| (void) rw_unlock(&iflock); |
| return (TRUE); |
| } |
| |
| |
| /* |
| * Given an IP address, check if this matches any of the interface |
| * addresses. If an error occurs, return FALSE so that the caller |
| * will not assume that this address belongs to this machine. |
| */ |
| static bool_t |
| is_my_address(struct in_addr addr) |
| { |
| time_t curtime; |
| if_info_t *ifn; |
| |
| (void) time(&curtime); |
| if ((curtime - last_updated) >= if_cache_refresh_time) { |
| /* |
| * Cache needs to be refreshed. |
| */ |
| if (!update_if_cache()) |
| return (FALSE); |
| } |
| (void) rw_rdlock(&iflock); |
| for (ifn = if_info; ifn < (if_info + n_ifs); ifn++) { |
| if (addr.s_addr == ifn->if_address.s_addr) { |
| (void) rw_unlock(&iflock); |
| return (TRUE); |
| } |
| } |
| (void) rw_unlock(&iflock); |
| return (FALSE); |
| } |
| |
| |
| /* |
| * Given a host name, check if it is this host. |
| */ |
| bool_t |
| __inet_netdir_is_my_host(const char *host) |
| { |
| int error; |
| char buf[NSS_BUFLEN_HOSTS]; |
| struct hostent res, *h; |
| char **c; |
| struct in_addr in; |
| |
| h = gethostbyname_r(host, (void *)&res, buf, sizeof (buf), &error); |
| if (h == NULL) |
| return (FALSE); |
| if (h->h_addrtype != AF_INET) |
| return (FALSE); |
| for (c = h->h_addr_list; *c != NULL; c++) { |
| (void) memcpy(&in.s_addr, *c, sizeof (in.s_addr)); |
| if (is_my_address(in)) |
| return (TRUE); |
| } |
| return (FALSE); |
| } |
| |
| |
| /* |
| * Given an IP address, find the interface address that has the best |
| * prefix match. Return the address in network order. |
| */ |
| static uint32_t |
| get_best_match(struct in_addr addr) |
| { |
| if_info_t *bestmatch, *ifn; |
| int bestcount, count, limit; |
| uint32_t mask, netmask, clnt_addr, if_addr; |
| bool_t found, subnet_match; |
| int subnet_count; |
| |
| bestmatch = NULL; /* no match yet */ |
| bestcount = BITSPERBYTE * sizeof (uint32_t); /* worst match */ |
| clnt_addr = ntohl(addr.s_addr); /* host order */ |
| |
| subnet_match = FALSE; /* subnet match not found yet */ |
| subnet_count = bestcount; /* worst subnet match */ |
| |
| for (ifn = if_info; ifn < (if_info + n_ifs); ifn++) { |
| netmask = ntohl(ifn->if_netmask.s_addr); /* host order */ |
| if_addr = ntohl(ifn->if_address.s_addr); /* host order */ |
| |
| /* |
| * set initial count to first bit set in netmask, with |
| * zero being the number of the least significant bit. |
| */ |
| count = 0; |
| for (mask = netmask; mask && ((mask & 1) == 0); mask >>= 1) |
| count++; |
| |
| /* |
| * Set limit so that we don't try to match prefixes shorter |
| * than the inherent netmask for the class (A, B, C, etc). |
| */ |
| if (IN_CLASSC(if_addr)) |
| limit = IN_CLASSC_NSHIFT; |
| else if (IN_CLASSB(if_addr)) |
| limit = IN_CLASSB_NSHIFT; |
| else if (IN_CLASSA(if_addr)) |
| limit = IN_CLASSA_NSHIFT; |
| else |
| limit = 0; |
| |
| /* |
| * We assume that the netmask consists of a contiguous |
| * sequence of 1-bits starting with the most significant bit. |
| * Prefix comparison starts at the subnet mask level. |
| * The prefix mask used for comparison is progressively |
| * reduced until it equals the inherent mask for the |
| * interface address class. The algorithm finds an |
| * interface in the following order of preference: |
| * |
| * (1) the longest subnet match |
| * (2) the best partial subnet match |
| * (3) the first non-loopback && non-PPP interface |
| * (4) the first non-loopback interface (PPP is OK) |
| */ |
| found = FALSE; |
| while (netmask && count < subnet_count) { |
| if ((netmask & clnt_addr) == (netmask & if_addr)) { |
| bestcount = count; |
| bestmatch = ifn; |
| found = TRUE; |
| break; |
| } |
| netmask <<= 1; |
| count++; |
| if (count >= bestcount || count > limit || subnet_match) |
| break; |
| } |
| /* |
| * If a subnet level match occurred, note this for |
| * comparison with future subnet matches. |
| */ |
| if (found && (netmask == ntohl(ifn->if_netmask.s_addr))) { |
| subnet_match = TRUE; |
| subnet_count = count; |
| } |
| } |
| |
| /* |
| * If we don't have a match, select the first interface that |
| * is not a loopback interface (and preferably not a PPP interface) |
| * as the best match. |
| */ |
| if (bestmatch == NULL) { |
| for (ifn = if_info; ifn < (if_info + n_ifs); ifn++) { |
| if ((ifn->if_flags & IFF_LOOPBACK) == 0) { |
| bestmatch = ifn; |
| |
| /* |
| * If this isn't a PPP interface, we're |
| * done. Otherwise, keep walking through |
| * the list in case we have a non-loopback |
| * iface that ISN'T a PPP further down our |
| * list... |
| */ |
| if ((ifn->if_flags & IFF_POINTOPOINT) == 0) { |
| break; |
| } |
| } |
| } |
| } |
| |
| if (bestmatch != NULL) |
| return (bestmatch->if_address.s_addr); |
| else |
| return (0); |
| } |
| |
| static int |
| is_myself(struct sockaddr_in6 *sa6) |
| { |
| struct sioc_addrreq areq; |
| int s; |
| |
| if ((s = open("/dev/udp6", O_RDONLY)) < 0) { |
| syslog(LOG_ERR, "is_myself: can't open /dev/udp6: %m"); |
| return (0); |
| } |
| |
| (void) memcpy(&areq.sa_addr, sa6, sizeof (struct sockaddr_storage)); |
| areq.sa_res = -1; |
| |
| if (ioctl(s, SIOCTMYADDR, (caddr_t)&areq) < 0) { |
| syslog(LOG_ERR, "is_myself:SIOCTMYADDR failed: %m"); |
| (void) close(s); |
| return (0); |
| } |
| |
| (void) close(s); |
| return (areq.sa_res); |
| |
| } |
| /* |
| * For a given destination address, determine a source address to use. |
| * Returns wildcard address if it cannot determine the source address. |
| * copied from ping.c. |
| */ |
| union any_in_addr { |
| struct in6_addr addr6; |
| struct in_addr addr; |
| }; |
| |
| static bool_t |
| select_server_addr(union any_in_addr *dst_addr, int family, |
| union any_in_addr *src_addr) |
| { |
| struct sockaddr *sock; |
| struct sockaddr_in *sin; |
| struct sockaddr_in6 *sin6; |
| int tmp_fd; |
| socklen_t sock_len; |
| |
| sock = calloc(1, sizeof (struct sockaddr_in6)); |
| if (sock == NULL) { |
| return (FALSE); |
| } |
| |
| if (family == AF_INET) { |
| /* LINTED pointer cast */ |
| sin = (struct sockaddr_in *)sock; |
| sin->sin_family = AF_INET; |
| sin->sin_port = 111; |
| sin->sin_addr = dst_addr->addr; |
| sock_len = sizeof (struct sockaddr_in); |
| } else { |
| /* LINTED pointer cast */ |
| sin6 = (struct sockaddr_in6 *)sock; |
| sin6->sin6_family = AF_INET6; |
| sin6->sin6_port = 111; |
| sin6->sin6_addr = dst_addr->addr6; |
| sock_len = sizeof (struct sockaddr_in6); |
| } |
| |
| /* open a UDP socket */ |
| tmp_fd = _so_socket(family, SOCK_DGRAM, 0, NULL, SOV_SOCKBSD); |
| if (tmp_fd < 0) { |
| syslog(LOG_ERR, "select_server_addr: connect failed\n"); |
| return (FALSE); |
| } |
| |
| /* connect it */ |
| if (_so_connect(tmp_fd, sock, sock_len, SOV_SOCKBSD) < 0) { |
| /* |
| * If there's no route to the destination, this connect() call |
| * fails. We just return all-zero (wildcard) as the source |
| * address, so that user can get to see "no route to dest" |
| * message, as it'll try to send the probe packet out and will |
| * receive ICMP unreachable. |
| */ |
| if (family == AF_INET) { |
| src_addr->addr.s_addr = INADDR_ANY; |
| } else { |
| /* |
| * Since in6addr_any is not in the scope |
| * use the following hack |
| */ |
| (void) memset(src_addr->addr6.s6_addr, |
| 0, sizeof (struct in6_addr)); |
| } |
| (void) close(tmp_fd); |
| free(sock); |
| return (FALSE); |
| } |
| |
| /* get the local sock info */ |
| if (_so_getsockname(tmp_fd, sock, &sock_len, SOV_DEFAULT) < 0) { |
| syslog(LOG_ERR, "select_server_addr: getsockname failed\n"); |
| (void) close(tmp_fd); |
| free(sock); |
| return (FALSE); |
| } |
| |
| if (family == AF_INET) { |
| /* LINTED pointer cast */ |
| sin = (struct sockaddr_in *)sock; |
| src_addr->addr = sin->sin_addr; |
| } else { |
| /* LINTED pointer cast */ |
| sin6 = (struct sockaddr_in6 *)sock; |
| src_addr->addr6 = sin6->sin6_addr; |
| } |
| |
| (void) close(tmp_fd); |
| free(sock); |
| return (TRUE); |
| } |
| |
| /* |
| * This internal routine will merge one of those "universal" addresses |
| * to the one which will make sense to the remote caller. |
| */ |
| static char * |
| inet_netdir_mergeaddr(struct netconfig *tp, char *ruaddr, char *uaddr) |
| { |
| char tmp[SYS_NMLN], *cp; |
| int j; |
| struct in_addr clientaddr, bestmatch; |
| time_t curtime; |
| int af; |
| |
| if (!uaddr || !ruaddr || !tp) { |
| _nderror = ND_BADARG; |
| return (NULL); |
| } |
| (void) bzero(tmp, SYS_NMLN); |
| |
| if (strcmp(tp->nc_protofmly, NC_INET) == 0) |
| af = AF_INET; |
| else |
| af = AF_INET6; |
| |
| if (af == AF_INET) { |
| if (strncmp(ruaddr, "0.0.0.0.", strlen("0.0.0.0.")) == 0) |
| /* thats me: return the way it is */ |
| return (strdup(uaddr)); |
| |
| /* |
| * Convert remote uaddr into an in_addr so that we can compare |
| * to it. Shave off last two dotted-decimal values. |
| */ |
| for (cp = ruaddr, j = 0; j < 4; j++, cp++) |
| if ((cp = strchr(cp, '.')) == NULL) |
| break; |
| |
| if (cp != NULL) |
| *--cp = '\0'; /* null out the dot after the IP addr */ |
| else { |
| _nderror = ND_NOHOST; |
| return (NULL); |
| } |
| |
| clientaddr.s_addr = inet_addr(ruaddr); |
| |
| /* We know cp is not NULL due to the check above */ |
| *cp = '.'; /* Put the dot back in the IP addr */ |
| |
| (void) time(&curtime); |
| if ((curtime - last_updated) >= if_cache_refresh_time) { |
| /* |
| * Cache needs to be refreshed. |
| */ |
| if (!update_if_cache()) |
| return (NULL); |
| } |
| |
| /* |
| * Find the best match now. |
| */ |
| (void) rw_rdlock(&iflock); |
| bestmatch.s_addr = get_best_match(clientaddr); |
| (void) rw_unlock(&iflock); |
| |
| if (bestmatch.s_addr) |
| _nderror = ND_OK; |
| else { |
| _nderror = ND_NOHOST; |
| return (NULL); |
| } |
| |
| /* prepare the reply */ |
| (void) memset(tmp, '\0', sizeof (tmp)); |
| |
| /* reply consists of the IP addr of the closest interface */ |
| (void) strcpy(tmp, inet_ntoa(bestmatch)); |
| |
| /* |
| * ... and the port number part (last two dotted-decimal values) |
| * of uaddr |
| */ |
| for (cp = uaddr, j = 0; j < 4; j++, cp++) |
| cp = strchr(cp, '.'); |
| (void) strcat(tmp, --cp); |
| |
| } else { |
| /* IPv6 */ |
| char *dot; |
| char *truaddr; |
| struct sockaddr_in6 sa; |
| struct sockaddr_in6 server_addr; |
| union any_in_addr in_addr, out_addr; |
| |
| if (strncmp(ruaddr, "::", strlen("::")) == 0) |
| if (*(ruaddr + strlen("::")) == '\0') |
| /* thats me: return the way it is */ |
| return (strdup(uaddr)); |
| |
| bzero(&sa, sizeof (sa)); |
| bzero(&server_addr, sizeof (server_addr)); |
| truaddr = &tmp[0]; |
| (void) strcpy(truaddr, ruaddr); |
| |
| /* |
| * now extract the server ip address from |
| * the address supplied by client. It can be |
| * client's own IP address. |
| */ |
| |
| if ((dot = strrchr(truaddr, '.')) != 0) { |
| *dot = '\0'; |
| if ((dot = strrchr(truaddr, '.')) != 0) |
| *dot = '\0'; |
| } |
| |
| if (dot == 0) { |
| _nderror = ND_NOHOST; |
| return (NULL); |
| } |
| |
| if (inet_pton(af, truaddr, sa.sin6_addr.s6_addr) |
| != 1) { |
| _nderror = ND_NOHOST; |
| return (NULL); |
| } |
| |
| in_addr.addr6 = sa.sin6_addr; |
| sa.sin6_family = AF_INET6; |
| |
| /* is it my IP address */ |
| if (!is_myself(&sa)) { |
| /* have the kernel select one for me */ |
| if (select_server_addr(&in_addr, af, &out_addr) == |
| FALSE) |
| return (NULL); |
| server_addr.sin6_addr = out_addr.addr6; |
| } else { |
| (void) memcpy(&server_addr, &sa, sizeof (server_addr)); |
| } |
| |
| if (inet_ntop(af, server_addr.sin6_addr.s6_addr, tmp, |
| sizeof (tmp)) == NULL) { |
| _nderror = ND_NOHOST; |
| return (NULL); |
| } |
| |
| /* now extract the port info */ |
| if ((dot = strrchr(uaddr, '.')) != 0) { |
| char *p = --dot; |
| |
| while (*p-- != '.') |
| ; |
| p++; |
| (void) strcat(tmp + strlen(tmp), p); |
| _nderror = ND_OK; |
| } else { |
| _nderror = ND_NOHOST; |
| return (NULL); |
| } |
| |
| } |
| return (strdup(tmp)); |
| } |
| |
| static int |
| bindresvport(struct netconfig *nconf, int fd, struct netbuf *addr) |
| { |
| int res; |
| struct sockaddr_in myaddr; |
| struct sockaddr_in6 myaddr6; |
| struct sockaddr_in *sin; |
| struct sockaddr_in6 *sin6; |
| int i; |
| struct t_bind tbindstr, *tres; |
| struct t_info tinfo; |
| struct t_optmgmt req, resp; |
| struct opthdr *opt; |
| int reqbuf[64/sizeof (int)]; |
| int *optval; |
| |
| union { |
| struct sockaddr_in *sin; |
| struct sockaddr_in6 *sin6; |
| char *buf; |
| } u; |
| |
| _nderror = ND_SYSTEM; |
| if (geteuid()) { |
| errno = EACCES; |
| return (-1); |
| } |
| if ((i = t_getstate(fd)) != T_UNBND) { |
| if (t_errno == TBADF) |
| errno = EBADF; |
| if (i != -1) |
| errno = EISCONN; |
| return (-1); |
| } |
| |
| if (strcmp(nconf->nc_protofmly, NC_INET) == 0) { |
| if (addr == NULL) { |
| sin = &myaddr; |
| (void) memset(sin, 0, sizeof (*sin)); |
| sin->sin_family = AF_INET; |
| u.buf = (char *)sin; |
| } else |
| u.buf = (char *)addr->buf; |
| } else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) { |
| if (addr == NULL) { |
| sin6 = &myaddr6; |
| (void) memset(sin6, 0, sizeof (*sin6)); |
| sin6->sin6_family = AF_INET6; |
| u.buf = (char *)sin6; |
| } else |
| u.buf = addr->buf; |
| |
| } else { |
| errno = EPFNOSUPPORT; |
| return (-1); |
| } |
| |
| /* Transform sockaddr_in to netbuf */ |
| if (t_getinfo(fd, &tinfo) == -1) |
| return (-1); |
| /* LINTED pointer cast */ |
| tres = (struct t_bind *)t_alloc(fd, T_BIND, T_ADDR); |
| if (tres == NULL) { |
| _nderror = ND_NOMEM; |
| return (-1); |
| } |
| |
| tbindstr.qlen = 0; /* Always 0; user should change if he wants to */ |
| tbindstr.addr.buf = (char *)u.buf; |
| tbindstr.addr.len = tbindstr.addr.maxlen = __rpc_get_a_size(tinfo.addr); |
| |
| /* |
| * Use *_ANONPRIVBIND to ask the kernel to pick a port in the |
| * priviledged range for us. |
| */ |
| opt = (struct opthdr *)reqbuf; |
| if (strcmp(nconf->nc_proto, NC_TCP) == 0) { |
| opt->level = IPPROTO_TCP; |
| opt->name = TCP_ANONPRIVBIND; |
| } else if (strcmp(nconf->nc_proto, NC_UDP) == 0) { |
| opt->level = IPPROTO_UDP; |
| opt->name = UDP_ANONPRIVBIND; |
| } else { |
| errno = EPROTONOSUPPORT; |
| (void) t_free((char *)tres, T_BIND); |
| return (-1); |
| } |
| |
| opt->len = sizeof (int); |
| req.flags = T_NEGOTIATE; |
| req.opt.len = sizeof (struct opthdr) + opt->len; |
| req.opt.buf = (char *)opt; |
| /* LINTED pointer cast */ |
| optval = (int *)((char *)reqbuf + sizeof (struct opthdr)); |
| *optval = 1; |
| resp.flags = 0; |
| resp.opt.buf = (char *)reqbuf; |
| resp.opt.maxlen = sizeof (reqbuf); |
| if (t_optmgmt(fd, &req, &resp) < 0 || resp.flags != T_SUCCESS) { |
| (void) t_free((char *)tres, T_BIND); |
| return (-1); |
| } |
| |
| if (u.sin->sin_family == AF_INET) |
| u.sin->sin_port = htons(0); |
| else |
| u.sin6->sin6_port = htons(0); |
| res = t_bind(fd, &tbindstr, tres); |
| if (res != 0) { |
| if (t_errno == TNOADDR) { |
| _nderror = ND_FAILCTRL; |
| res = 1; |
| } |
| } else { |
| _nderror = ND_OK; |
| } |
| |
| /* |
| * Always turn off the option when we are done. Note that by doing |
| * this, if the caller has set this option before calling |
| * bindresvport(), it will be unset. Better be safe... |
| */ |
| *optval = 0; |
| resp.flags = 0; |
| resp.opt.buf = (char *)reqbuf; |
| resp.opt.maxlen = sizeof (reqbuf); |
| if (t_optmgmt(fd, &req, &resp) < 0 || resp.flags != T_SUCCESS) { |
| (void) t_free((char *)tres, T_BIND); |
| if (res == 0) |
| (void) t_unbind(fd); |
| _nderror = ND_FAILCTRL; |
| return (-1); |
| } |
| |
| (void) t_free((char *)tres, T_BIND); |
| return (res); |
| } |
| |
| static int |
| checkresvport(struct netbuf *addr) |
| { |
| struct sockaddr_in *sin; |
| unsigned short port; |
| |
| if (addr == NULL) { |
| _nderror = ND_FAILCTRL; |
| return (-1); |
| } |
| /* |
| * Still works for IPv6 since the first two memebers of |
| * both address structure point to family and port # respectively |
| */ |
| /* LINTED pointer cast */ |
| sin = (struct sockaddr_in *)(addr->buf); |
| port = ntohs(sin->sin_port); |
| if (port < IPPORT_RESERVED) |
| return (0); |
| return (1); |
| } |