diff options
Diffstat (limited to 'src/lib/krb5/os/localaddr.c')
| -rw-r--r-- | src/lib/krb5/os/localaddr.c | 1560 |
1 files changed, 1560 insertions, 0 deletions
diff --git a/src/lib/krb5/os/localaddr.c b/src/lib/krb5/os/localaddr.c new file mode 100644 index 0000000000000..9f7765254467e --- /dev/null +++ b/src/lib/krb5/os/localaddr.c @@ -0,0 +1,1560 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/os/localaddr.c */ +/* + * Copyright 1990,1991,2000,2001,2002,2004,2007,2008 by the Massachusetts + * Institute of Technology. All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +/* + * Return the protocol addresses supported by this host. + * Exports from this file: + * krb5int_foreach_localaddr (does callbacks) + * krb5_os_localaddr (doesn't include krb5.conf extra_addresses) + * + * XNS support is untested, but "Should just work". (Hah!) + */ + +#include "k5-int.h" +#include "os-proto.h" + +#if !defined(_WIN32) + +/* needed for solaris, harmless elsewhere... */ +#define BSD_COMP +#include <sys/ioctl.h> +#include <sys/time.h> +#include <errno.h> +#include <stddef.h> +#include <ctype.h> + +#if defined(TEST) || defined(DEBUG) +# include "fake-addrinfo.h" +#endif + +#include "foreachaddr.h" + +/* Note: foreach_localaddr is exported from the library through + krb5int_accessor, for the KDC to use. + + This function iterates over all the addresses it can find for the + local system, in one or two passes. In each pass, and between the + two, it can invoke callback functions supplied by the caller. The + two passes should operate on the same information, though not + necessarily in the same order each time. Duplicate and local + addresses should be eliminated. Storage passed to callback + functions should not be assumed to be valid after foreach_localaddr + returns. + + The int return value is an errno value (XXX or krb5_error_code + returned for a socket error) if something internal to + foreach_localaddr fails. If one of the callback functions wants to + indicate an error, it should store something via the 'data' handle. + If any callback function returns a non-zero value, + foreach_localaddr will clean up and return immediately. + + Multiple definitions are provided below, dependent on various + system facilities for extracting the necessary information. */ + +/* Now, on to the implementations, and heaps of debugging code. */ + +#ifdef TEST +# define Tprintf(X) printf X +# define Tperror(X) perror(X) +#else +# define Tprintf(X) (void) X +# define Tperror(X) (void)(X) +#endif + +/* + * The SIOCGIF* ioctls require a socket. + * It doesn't matter *what* kind of socket they use, but it has to be + * a socket. + * + * Of course, you can't just ask the kernel for a socket of arbitrary + * type; you have to ask for one with a valid type. + * + */ +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#ifndef USE_AF +#define USE_AF AF_INET +#define USE_TYPE SOCK_DGRAM +#define USE_PROTO 0 +#endif +#endif + +#ifdef KRB5_USE_NS +#include <netns/ns.h> +#ifndef USE_AF +#define USE_AF AF_NS +#define USE_TYPE SOCK_DGRAM +#define USE_PROTO 0 /* guess */ +#endif +#endif +/* + * Add more address families here. + */ + + +#if defined(__linux__) && !defined(HAVE_IFADDRS_H) +#define LINUX_IPV6_HACK +#endif + +#include <errno.h> + +/* + * Return all the protocol addresses of this host. + * + * We could kludge up something to return all addresses, assuming that + * they're valid kerberos protocol addresses, but we wouldn't know the + * real size of the sockaddr or know which part of it was actually the + * host part. + * + * This uses the SIOCGIFCONF, SIOCGIFFLAGS, and SIOCGIFADDR ioctl's. + */ + +/* + * BSD 4.4 defines the size of an ifreq to be + * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len + * However, under earlier systems, sa_len isn't present, so the size is + * just sizeof(struct ifreq). + */ +#ifdef HAVE_SA_LEN +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif +#define ifreq_size(i) max(sizeof(struct ifreq), \ + sizeof((i).ifr_name)+(i).ifr_addr.sa_len) +#else +#define ifreq_size(i) sizeof(struct ifreq) +#endif /* HAVE_SA_LEN*/ + +#if defined(DEBUG) || defined(TEST) +#include <netinet/in.h> +#include <net/if.h> + +#include "socket-utils.h" +#include "fake-addrinfo.h" + +void printaddr (struct sockaddr *); + +void +printaddr(struct sockaddr *sa) +/*@modifies fileSystem@*/ +{ + char buf[NI_MAXHOST]; + int err; + + printf ("%p ", (void *) sa); + err = getnameinfo (sa, sa_socklen (sa), buf, sizeof (buf), 0, 0, + NI_NUMERICHOST); + if (err) + printf ("<getnameinfo error %d: %s> family=%d", + err, gai_strerror (err), + sa->sa_family); + else + printf ("%s", buf); +} +#endif + +static int +is_loopback_address(struct sockaddr *sa) +{ + switch (sa->sa_family) { + case AF_INET: { + struct sockaddr_in *s4 = (struct sockaddr_in *)sa; + return s4->sin_addr.s_addr == htonl(INADDR_LOOPBACK); + } + case AF_INET6: { + struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)sa; + return IN6_IS_ADDR_LOOPBACK(&s6->sin6_addr); + } + default: + return 0; + } +} + +#ifdef HAVE_IFADDRS_H +#include <ifaddrs.h> + +#ifdef DEBUG +void +printifaddr(struct ifaddrs *ifp) +{ + printf ("%p={\n", ifp); +/* printf ("\tnext=%p\n", ifp->ifa_next); */ + printf ("\tname=%s\n", ifp->ifa_name); + printf ("\tflags="); + { + int ch, flags = ifp->ifa_flags; + printf ("%x", flags); + ch = '<'; +#define X(F) if (flags & IFF_##F) { printf ("%c%s", ch, #F); flags &= ~IFF_##F; ch = ','; } + X (UP); X (BROADCAST); X (DEBUG); X (LOOPBACK); X (POINTOPOINT); + X (NOTRAILERS); X (RUNNING); X (NOARP); X (PROMISC); X (ALLMULTI); +#ifdef IFF_OACTIVE + X (OACTIVE); +#endif +#ifdef IFF_SIMPLE + X (SIMPLEX); +#endif + X (MULTICAST); + printf (">"); +#undef X + } + if (ifp->ifa_addr) + printf ("\n\taddr="), printaddr (ifp->ifa_addr); + if (ifp->ifa_netmask) + printf ("\n\tnetmask="), printaddr (ifp->ifa_netmask); + if (ifp->ifa_broadaddr) + printf ("\n\tbroadaddr="), printaddr (ifp->ifa_broadaddr); + if (ifp->ifa_dstaddr) + printf ("\n\tdstaddr="), printaddr (ifp->ifa_dstaddr); + if (ifp->ifa_data) + printf ("\n\tdata=%p", ifp->ifa_data); + printf ("\n}\n"); +} +#endif /* DEBUG */ + +#include <string.h> +#include <stdlib.h> + +static int +addr_eq (const struct sockaddr *s1, const struct sockaddr *s2) +{ + if (s1->sa_family != s2->sa_family) + return 0; +#define CMPTYPE(T,F) (!memcmp(&((const T*)s1)->F,&((const T*)s2)->F,sizeof(((const T*)s1)->F))) + switch (s1->sa_family) { + case AF_INET: + return CMPTYPE (struct sockaddr_in, sin_addr); + case AF_INET6: + return CMPTYPE (struct sockaddr_in6, sin6_addr); + default: + /* Err on side of duplicate listings. */ + return 0; + } +} +#endif + +#ifndef HAVE_IFADDRS_H +/*@-usereleased@*/ /* lclint doesn't understand realloc */ +static /*@null@*/ void * +grow_or_free (/*@only@*/ void *ptr, size_t newsize) +/*@*/ +{ + void *newptr; + newptr = realloc (ptr, newsize); + if (newptr == NULL && newsize != 0) { + free (ptr); /* lclint complains but this is right */ + return NULL; + } + return newptr; +} +/*@=usereleased@*/ + +static int +get_ifconf (int s, size_t *lenp, /*@out@*/ char *buf) +/*@modifies *buf,*lenp@*/ +{ + int ret; + struct ifconf ifc; + + /*@+matchanyintegral@*/ + ifc.ifc_len = *lenp; + /*@=matchanyintegral@*/ + ifc.ifc_buf = buf; + memset(buf, 0, *lenp); + /*@-moduncon@*/ + ret = ioctl (s, SIOCGIFCONF, (char *)&ifc); + /*@=moduncon@*/ + /*@+matchanyintegral@*/ + *lenp = ifc.ifc_len; + /*@=matchanyintegral@*/ + return ret; +} + +/* Solaris uses SIOCGLIFCONF to return struct lifconf which is just + an extended version of struct ifconf. + + HP-UX 11 also appears to have SIOCGLIFCONF, but uses struct + if_laddrconf, and struct if_laddrreq to be used with + SIOCGLIFADDR. */ +#if defined(SIOCGLIFCONF) && defined(HAVE_STRUCT_LIFCONF) +static int +get_lifconf (int af, int s, size_t *lenp, /*@out@*/ char *buf) +/*@modifies *buf,*lenp@*/ +{ + int ret; + struct lifconf lifc; + + lifc.lifc_family = af; + lifc.lifc_flags = 0; + /*@+matchanyintegral@*/ + lifc.lifc_len = *lenp; + /*@=matchanyintegral@*/ + lifc.lifc_buf = buf; + memset(buf, 0, *lenp); + /*@-moduncon@*/ + ret = ioctl (s, SIOCGLIFCONF, (char *)&lifc); + if (ret) + Tperror ("SIOCGLIFCONF"); + /*@=moduncon@*/ + /*@+matchanyintegral@*/ + *lenp = lifc.lifc_len; + /*@=matchanyintegral@*/ + return ret; +} +#endif +#if defined(SIOCGLIFCONF) && defined(HAVE_STRUCT_IF_LADDRCONF) && 0 +/* I'm not sure if this is needed or if net/if.h will pull it in. */ +/* #include <net/if6.h> */ +static int +get_if_laddrconf (int af, int s, size_t *lenp, /*@out@*/ char *buf) +/*@modifies *buf,*lenp@*/ +{ + int ret; + struct if_laddrconf iflc; + + /*@+matchanyintegral@*/ + iflc.iflc_len = *lenp; + /*@=matchanyintegral@*/ + iflc.iflc_buf = buf; + memset(buf, 0, *lenp); + /*@-moduncon@*/ + ret = ioctl (s, SIOCGLIFCONF, (char *)&iflc); + if (ret) + Tperror ("SIOCGLIFCONF"); + /*@=moduncon@*/ + /*@+matchanyintegral@*/ + *lenp = iflc.iflc_len; + /*@=matchanyintegral@*/ + return ret; +} +#endif +#endif /* ! HAVE_IFADDRS_H */ + +#ifdef LINUX_IPV6_HACK +#include <stdio.h> +/* Read IPv6 addresses out of /proc/net/if_inet6, since there isn't + (currently) any ioctl to return them. */ +struct linux_ipv6_addr_list { + struct sockaddr_in6 addr; + struct linux_ipv6_addr_list *next; +}; +static struct linux_ipv6_addr_list * +get_linux_ipv6_addrs () +{ + struct linux_ipv6_addr_list *lst = 0; + FILE *f; + + /* _PATH_PROCNET_IFINET6 */ + f = fopen("/proc/net/if_inet6", "r"); + if (f) { + char ifname[21]; + unsigned int idx, pfxlen, scope, dadstat; + struct in6_addr a6; + struct linux_ipv6_addr_list *nw; + int i; + unsigned int addrbyte[16]; + + set_cloexec_file(f); + while (fscanf(f, + "%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x" + " %2x %2x %2x %2x %20s\n", + &addrbyte[0], &addrbyte[1], &addrbyte[2], &addrbyte[3], + &addrbyte[4], &addrbyte[5], &addrbyte[6], &addrbyte[7], + &addrbyte[8], &addrbyte[9], &addrbyte[10], &addrbyte[11], + &addrbyte[12], &addrbyte[13], &addrbyte[14], + &addrbyte[15], + &idx, &pfxlen, &scope, &dadstat, ifname) != EOF) { + for (i = 0; i < 16; i++) + a6.s6_addr[i] = addrbyte[i]; + if (scope != 0) + continue; +#if 0 /* These symbol names are as used by ifconfig, but none of the + system header files export them. Dig up the kernel versions + someday and see if they're exported. */ + switch (scope) { + case 0: + default: + break; + case IPV6_ADDR_LINKLOCAL: + case IPV6_ADDR_SITELOCAL: + case IPV6_ADDR_COMPATv4: + case IPV6_ADDR_LOOPBACK: + continue; + } +#endif + nw = calloc (1, sizeof (struct linux_ipv6_addr_list)); + if (nw == 0) + continue; + nw->addr.sin6_addr = a6; + nw->addr.sin6_family = AF_INET6; + /* Ignore other fields, we don't actually use them here. */ + nw->next = lst; + lst = nw; + } + fclose (f); + } + return lst; +} +#endif + +/* Return value is errno if internal stuff failed, otherwise zero, + even in the case where a called function terminated the iteration. + + If one of the callback functions wants to pass back an error + indication, it should do it via some field pointed to by the DATA + argument. */ + +#ifdef HAVE_IFADDRS_H + +int +foreach_localaddr (/*@null@*/ void *data, + int (*pass1fn) (/*@null@*/ void *, struct sockaddr *) /*@*/, + /*@null@*/ int (*betweenfn) (/*@null@*/ void *) /*@*/, + /*@null@*/ int (*pass2fn) (/*@null@*/ void *, + struct sockaddr *) /*@*/) +#if defined(DEBUG) || defined(TEST) +/*@modifies fileSystem@*/ +#endif +{ + struct ifaddrs *ifp_head, *ifp, *ifp2; + int match; + + if (getifaddrs (&ifp_head) < 0) + return errno; + for (ifp = ifp_head; ifp; ifp = ifp->ifa_next) { +#ifdef DEBUG + printifaddr (ifp); +#endif + if ((ifp->ifa_flags & IFF_UP) == 0) + continue; + if (ifp->ifa_addr == NULL) { + /* Can't use an interface without an address. Linux + apparently does this sometimes. [RT ticket 1770 from + Maurice Massar, also Debian bug 206851, shows the + problem with a PPP link on a newer kernel than I'm + running.] + + Pretend it's not up, so the second pass will skip + it. */ + ifp->ifa_flags &= ~IFF_UP; + continue; + } + if (is_loopback_address(ifp->ifa_addr)) { + /* Pretend it's not up, so the second pass will skip + it. */ + ifp->ifa_flags &= ~IFF_UP; + continue; + } + /* If this address is a duplicate, punt. */ + match = 0; + for (ifp2 = ifp_head; ifp2 && ifp2 != ifp; ifp2 = ifp2->ifa_next) { + if ((ifp2->ifa_flags & IFF_UP) == 0) + continue; + if (addr_eq (ifp->ifa_addr, ifp2->ifa_addr)) { + match = 1; + ifp->ifa_flags &= ~IFF_UP; + break; + } + } + if (match) + continue; + if ((*pass1fn) (data, ifp->ifa_addr)) + goto punt; + } + if (betweenfn && (*betweenfn)(data)) + goto punt; + if (pass2fn) + for (ifp = ifp_head; ifp; ifp = ifp->ifa_next) { + if (ifp->ifa_flags & IFF_UP) + if ((*pass2fn) (data, ifp->ifa_addr)) + goto punt; + } +punt: + freeifaddrs (ifp_head); + return 0; +} + +#elif defined (SIOCGLIFNUM) && defined(HAVE_STRUCT_LIFCONF) /* Solaris 8 and later; Sol 7? */ + +int +foreach_localaddr (/*@null@*/ void *data, + int (*pass1fn) (/*@null@*/ void *, struct sockaddr *) /*@*/, + /*@null@*/ int (*betweenfn) (/*@null@*/ void *) /*@*/, + /*@null@*/ int (*pass2fn) (/*@null@*/ void *, + struct sockaddr *) /*@*/) +#if defined(DEBUG) || defined(TEST) +/*@modifies fileSystem@*/ +#endif +{ + /* Okay, this is kind of odd. We have to use each of the address + families we care about, because with an AF_INET socket, extra + interfaces like hme0:1 that have only AF_INET6 addresses will + cause errors. Similarly, if hme0 has more AF_INET addresses + than AF_INET6 addresses, we won't be able to retrieve all of + the AF_INET addresses if we use an AF_INET6 socket. Since + neither family is guaranteed to have the greater number of + addresses, we should use both. + + If it weren't for this little quirk, we could use one socket of + any type, and ask for addresses of all types. At least, it + seems to work that way. */ + + static const int afs[] = { AF_INET, AF_NS, AF_INET6 }; +#define N_AFS (sizeof (afs) / sizeof (afs[0])) + struct { + int af; + int sock; + void *buf; + size_t buf_size; + struct lifnum lifnum; + } afp[N_AFS]; + int code, i, j; + int retval = 0, afidx; + krb5_error_code sock_err = 0; + struct lifreq *lifr, lifreq, *lifr2; + +#define FOREACH_AF() for (afidx = 0; afidx < N_AFS; afidx++) +#define P (afp[afidx]) + + /* init */ + FOREACH_AF () { + P.af = afs[afidx]; + P.sock = -1; + P.buf = 0; + } + + /* first pass: get raw data, discard uninteresting addresses, callback */ + FOREACH_AF () { + Tprintf (("trying af %d...\n", P.af)); + P.sock = socket (P.af, USE_TYPE, USE_PROTO); + if (P.sock < 0) { + sock_err = SOCKET_ERROR; + Tperror ("socket"); + continue; + } + set_cloexec_fd(P.sock); + + P.lifnum.lifn_family = P.af; + P.lifnum.lifn_flags = 0; + P.lifnum.lifn_count = 0; + code = ioctl (P.sock, SIOCGLIFNUM, &P.lifnum); + if (code) { + Tperror ("ioctl(SIOCGLIFNUM)"); + retval = errno; + goto punt; + } + + P.buf_size = P.lifnum.lifn_count * sizeof (struct lifreq) * 2; + P.buf = malloc (P.buf_size); + if (P.buf == NULL) { + retval = ENOMEM; + goto punt; + } + + code = get_lifconf (P.af, P.sock, &P.buf_size, P.buf); + if (code < 0) { + retval = errno; + goto punt; + } + + for (i = 0; i + sizeof(*lifr) <= P.buf_size; i+= sizeof (*lifr)) { + lifr = (struct lifreq *)((caddr_t) P.buf+i); + + strncpy(lifreq.lifr_name, lifr->lifr_name, + sizeof (lifreq.lifr_name)); + Tprintf (("interface %s\n", lifreq.lifr_name)); + /*@-moduncon@*/ /* ioctl unknown to lclint */ + if (ioctl (P.sock, SIOCGLIFFLAGS, (char *)&lifreq) < 0) { + Tperror ("ioctl(SIOCGLIFFLAGS)"); + skip: + /* mark for next pass */ + lifr->lifr_name[0] = '\0'; + continue; + } + /*@=moduncon@*/ + + /* None of the current callers want loopback addresses. */ + if (is_loopback_address((struct sockaddr *)&lifr->lifr_addr)) { + Tprintf ((" loopback\n")); + goto skip; + } + /* Ignore interfaces that are down. */ + if ((lifreq.lifr_flags & IFF_UP) == 0) { + Tprintf ((" down\n")); + goto skip; + } + + /* Make sure we didn't process this address already. */ + for (j = 0; j < i; j += sizeof (*lifr2)) { + lifr2 = (struct lifreq *)((caddr_t) P.buf+j); + if (lifr2->lifr_name[0] == '\0') + continue; + if (lifr2->lifr_addr.ss_family == lifr->lifr_addr.ss_family + /* Compare address info. If this isn't good enough -- + i.e., if random padding bytes turn out to differ + when the addresses are the same -- then we'll have + to do it on a per address family basis. */ + && !memcmp (&lifr2->lifr_addr, &lifr->lifr_addr, + sizeof (*lifr))) { + Tprintf ((" duplicate addr\n")); + goto skip; + } + } + + /*@-moduncon@*/ + if ((*pass1fn) (data, ss2sa (&lifr->lifr_addr))) + goto punt; + /*@=moduncon@*/ + } + } + + /* Did we actually get any working sockets? */ + FOREACH_AF () + if (P.sock != -1) + goto have_working_socket; + retval = sock_err; + goto punt; +have_working_socket: + + /*@-moduncon@*/ + if (betweenfn != NULL && (*betweenfn)(data)) + goto punt; + /*@=moduncon@*/ + + if (pass2fn) + FOREACH_AF () + if (P.sock >= 0) { + for (i = 0; i + sizeof (*lifr) <= P.buf_size; i+= sizeof (*lifr)) { + lifr = (struct lifreq *)((caddr_t) P.buf+i); + + if (lifr->lifr_name[0] == '\0') + /* Marked in first pass to be ignored. */ + continue; + + /*@-moduncon@*/ + if ((*pass2fn) (data, ss2sa (&lifr->lifr_addr))) + goto punt; + /*@=moduncon@*/ + } + } +punt: + FOREACH_AF () { + /*@-moduncon@*/ + closesocket(P.sock); + /*@=moduncon@*/ + free (P.buf); + } + + return retval; +} + +#elif defined (SIOCGLIFNUM) && defined(HAVE_STRUCT_IF_LADDRCONF) && 0 /* HP-UX 11 support being debugged */ + +int +foreach_localaddr (/*@null@*/ void *data, + int (*pass1fn) (/*@null@*/ void *, struct sockaddr *) /*@*/, + /*@null@*/ int (*betweenfn) (/*@null@*/ void *) /*@*/, + /*@null@*/ int (*pass2fn) (/*@null@*/ void *, + struct sockaddr *) /*@*/) +#if defined(DEBUG) || defined(TEST) +/*@modifies fileSystem@*/ +#endif +{ + /* Okay, this is kind of odd. We have to use each of the address + families we care about, because with an AF_INET socket, extra + interfaces like hme0:1 that have only AF_INET6 addresses will + cause errors. Similarly, if hme0 has more AF_INET addresses + than AF_INET6 addresses, we won't be able to retrieve all of + the AF_INET addresses if we use an AF_INET6 socket. Since + neither family is guaranteed to have the greater number of + addresses, we should use both. + + If it weren't for this little quirk, we could use one socket of + any type, and ask for addresses of all types. At least, it + seems to work that way. */ + + static const int afs[] = { AF_INET, AF_NS, AF_INET6 }; +#define N_AFS (sizeof (afs) / sizeof (afs[0])) + struct { + int af; + int sock; + void *buf; + size_t buf_size; + int if_num; + } afp[N_AFS]; + int code, i, j; + int retval = 0, afidx; + krb5_error_code sock_err = 0; + struct if_laddrreq *lifr, lifreq, *lifr2; + +#define FOREACH_AF() for (afidx = 0; afidx < N_AFS; afidx++) +#define P (afp[afidx]) + + /* init */ + FOREACH_AF () { + P.af = afs[afidx]; + P.sock = -1; + P.buf = 0; + } + + /* first pass: get raw data, discard uninteresting addresses, callback */ + FOREACH_AF () { + Tprintf (("trying af %d...\n", P.af)); + P.sock = socket (P.af, USE_TYPE, USE_PROTO); + if (P.sock < 0) { + sock_err = SOCKET_ERROR; + Tperror ("socket"); + continue; + } + set_cloexec_fd(P.sock); + + code = ioctl (P.sock, SIOCGLIFNUM, &P.if_num); + if (code) { + Tperror ("ioctl(SIOCGLIFNUM)"); + retval = errno; + goto punt; + } + + P.buf_size = P.if_num * sizeof (struct if_laddrreq) * 2; + P.buf = malloc (P.buf_size); + if (P.buf == NULL) { + retval = ENOMEM; + goto punt; + } + + code = get_if_laddrconf (P.af, P.sock, &P.buf_size, P.buf); + if (code < 0) { + retval = errno; + goto punt; + } + + for (i = 0; i + sizeof(*lifr) <= P.buf_size; i+= sizeof (*lifr)) { + lifr = (struct if_laddrreq *)((caddr_t) P.buf+i); + + strncpy(lifreq.iflr_name, lifr->iflr_name, + sizeof (lifreq.iflr_name)); + Tprintf (("interface %s\n", lifreq.iflr_name)); + /*@-moduncon@*/ /* ioctl unknown to lclint */ + if (ioctl (P.sock, SIOCGLIFFLAGS, (char *)&lifreq) < 0) { + Tperror ("ioctl(SIOCGLIFFLAGS)"); + skip: + /* mark for next pass */ + lifr->iflr_name[0] = '\0'; + continue; + } + /*@=moduncon@*/ + + /* None of the current callers want loopback addresses. */ + if (is_loopback_address(&lifr->iflr_addr)) { + Tprintf ((" loopback\n")); + goto skip; + } + /* Ignore interfaces that are down. */ + if ((lifreq.iflr_flags & IFF_UP) == 0) { + Tprintf ((" down\n")); + goto skip; + } + + /* Make sure we didn't process this address already. */ + for (j = 0; j < i; j += sizeof (*lifr2)) { + lifr2 = (struct if_laddrreq *)((caddr_t) P.buf+j); + if (lifr2->iflr_name[0] == '\0') + continue; + if (lifr2->iflr_addr.sa_family == lifr->iflr_addr.sa_family + /* Compare address info. If this isn't good enough -- + i.e., if random padding bytes turn out to differ + when the addresses are the same -- then we'll have + to do it on a per address family basis. */ + && !memcmp (&lifr2->iflr_addr, &lifr->iflr_addr, + sizeof (*lifr))) { + Tprintf ((" duplicate addr\n")); + goto skip; + } + } + + /*@-moduncon@*/ + if ((*pass1fn) (data, ss2sa (&lifr->iflr_addr))) + goto punt; + /*@=moduncon@*/ + } + } + + /* Did we actually get any working sockets? */ + FOREACH_AF () + if (P.sock != -1) + goto have_working_socket; + retval = sock_err; + goto punt; +have_working_socket: + + /*@-moduncon@*/ + if (betweenfn != NULL && (*betweenfn)(data)) + goto punt; + /*@=moduncon@*/ + + if (pass2fn) + FOREACH_AF () + if (P.sock >= 0) { + for (i = 0; i + sizeof(*lifr) <= P.buf_size; i+= sizeof (*lifr)) { + lifr = (struct if_laddrreq *)((caddr_t) P.buf+i); + + if (lifr->iflr_name[0] == '\0') + /* Marked in first pass to be ignored. */ + continue; + + /*@-moduncon@*/ + if ((*pass2fn) (data, ss2sa (&lifr->iflr_addr))) + goto punt; + /*@=moduncon@*/ + } + } +punt: + FOREACH_AF () { + /*@-moduncon@*/ + closesocket(P.sock); + /*@=moduncon@*/ + free (P.buf); + } + + return retval; +} + +#else /* not defined (SIOCGLIFNUM) */ + +#define SLOP (sizeof (struct ifreq) + 128) + +static int +get_ifreq_array(char **bufp, size_t *np, int s) +{ + int code; + int est_if_count = 8; + size_t est_ifreq_size; + char *buf = 0; + size_t current_buf_size = 0, size, n; +#ifdef SIOCGSIZIFCONF + int ifconfsize = -1; +#endif +#ifdef SIOCGIFNUM + int numifs = -1; +#endif + + /* At least on NetBSD, an ifreq can hold an IPv4 address, but + isn't big enough for an IPv6 or ethernet address. So add a + little more space. */ + est_ifreq_size = sizeof (struct ifreq) + 8; +#ifdef SIOCGSIZIFCONF + code = ioctl (s, SIOCGSIZIFCONF, &ifconfsize); + if (!code) { + current_buf_size = ifconfsize; + est_if_count = ifconfsize / est_ifreq_size; + } +#elif defined (SIOCGIFNUM) + code = ioctl (s, SIOCGIFNUM, &numifs); + if (!code && numifs > 0) + est_if_count = numifs; +#endif + if (current_buf_size == 0) + current_buf_size = est_ifreq_size * est_if_count + SLOP; + buf = malloc (current_buf_size); + if (buf == NULL) + return ENOMEM; + +ask_again: + size = current_buf_size; + code = get_ifconf (s, &size, buf); + if (code < 0) { + code = errno; + free (buf); + return code; + } + /* Test that the buffer was big enough that another ifreq could've + fit easily, if the OS wanted to provide one. That seems to be + the only indication we get, complicated by the fact that the + associated address may make the required storage a little + bigger than the size of an ifreq. */ + if (current_buf_size - size < SLOP +#ifdef SIOCGSIZIFCONF + /* Unless we hear SIOCGSIZIFCONF is broken somewhere, let's + trust the value it returns. */ + && ifconfsize <= 0 +#elif defined (SIOCGIFNUM) + && numifs <= 0 +#endif + /* And we need *some* sort of bounds. */ + && current_buf_size <= 100000 + ) { + size_t new_size; + + est_if_count *= 2; + new_size = est_ifreq_size * est_if_count + SLOP; + buf = grow_or_free (buf, new_size); + if (buf == 0) + return ENOMEM; + current_buf_size = new_size; + goto ask_again; + } + + n = size; + if (n > current_buf_size) + n = current_buf_size; + + *bufp = buf; + *np = n; + return 0; +} + +int +foreach_localaddr (/*@null@*/ void *data, + int (*pass1fn) (/*@null@*/ void *, struct sockaddr *) /*@*/, + /*@null@*/ int (*betweenfn) (/*@null@*/ void *) /*@*/, + /*@null@*/ int (*pass2fn) (/*@null@*/ void *, + struct sockaddr *) /*@*/) +#if defined(DEBUG) || defined(TEST) +/*@modifies fileSystem@*/ +#endif +{ + struct ifreq *ifr, ifreq, *ifr2; + int s, code; + char *buf = 0; + size_t size, n, i, j; + int retval = 0; +#ifdef LINUX_IPV6_HACK + struct linux_ipv6_addr_list *linux_ipv6_addrs = get_linux_ipv6_addrs (); + struct linux_ipv6_addr_list *lx_v6; +#endif + + s = socket (USE_AF, USE_TYPE, USE_PROTO); + if (s < 0) + return SOCKET_ERRNO; + set_cloexec_fd(s); + + retval = get_ifreq_array(&buf, &n, s); + if (retval) { + /*@-moduncon@*/ /* close() unknown to lclint */ + closesocket(s); + /*@=moduncon@*/ + return retval; + } + + /* Note: Apparently some systems put the size (used or wanted?) + into the start of the buffer, just none that I'm actually + using. Fix this when there's such a test system available. + The Samba mailing list archives mention that NTP looks for the + size on these systems: *-fujitsu-uxp* *-ncr-sysv4* + *-univel-sysv*. */ + for (i = 0; i + sizeof(struct ifreq) <= n; i+= ifreq_size(*ifr) ) { + ifr = (struct ifreq *)((caddr_t) buf+i); + /* In case ifreq_size is more than sizeof(). */ + if (i + ifreq_size(*ifr) > n) + break; + + strncpy(ifreq.ifr_name, ifr->ifr_name, sizeof (ifreq.ifr_name)); + Tprintf (("interface %s\n", ifreq.ifr_name)); + /*@-moduncon@*/ /* ioctl unknown to lclint */ + if (ioctl (s, SIOCGIFFLAGS, (char *)&ifreq) < 0) { + skip: + /* mark for next pass */ + ifr->ifr_name[0] = '\0'; + continue; + } + /*@=moduncon@*/ + + /* None of the current callers want loopback addresses. */ + if (is_loopback_address(&ifreq.ifr_addr)) { + Tprintf ((" loopback\n")); + goto skip; + } + /* Ignore interfaces that are down. */ + if ((ifreq.ifr_flags & IFF_UP) == 0) { + Tprintf ((" down\n")); + goto skip; + } + + /* Make sure we didn't process this address already. */ + for (j = 0; j < i; j += ifreq_size(*ifr2)) { + ifr2 = (struct ifreq *)((caddr_t) buf+j); + if (ifr2->ifr_name[0] == '\0') + continue; + if (ifr2->ifr_addr.sa_family == ifr->ifr_addr.sa_family + && ifreq_size (*ifr) == ifreq_size (*ifr2) + /* Compare address info. If this isn't good enough -- + i.e., if random padding bytes turn out to differ + when the addresses are the same -- then we'll have + to do it on a per address family basis. */ + && !memcmp (&ifr2->ifr_addr.sa_data, &ifr->ifr_addr.sa_data, + (ifreq_size (*ifr) + - offsetof (struct ifreq, ifr_addr.sa_data)))) { + Tprintf ((" duplicate addr\n")); + goto skip; + } + } + + /*@-moduncon@*/ + if ((*pass1fn) (data, &ifr->ifr_addr)) + goto punt; + /*@=moduncon@*/ + } + +#ifdef LINUX_IPV6_HACK + for (lx_v6 = linux_ipv6_addrs; lx_v6; lx_v6 = lx_v6->next) + if ((*pass1fn) (data, (struct sockaddr *) &lx_v6->addr)) + goto punt; +#endif + + /*@-moduncon@*/ + if (betweenfn != NULL && (*betweenfn)(data)) + goto punt; + /*@=moduncon@*/ + + if (pass2fn) { + for (i = 0; i + sizeof(struct ifreq) <= n; i+= ifreq_size(*ifr) ) { + ifr = (struct ifreq *)((caddr_t) buf+i); + + if (ifr->ifr_name[0] == '\0') + /* Marked in first pass to be ignored. */ + continue; + + /*@-moduncon@*/ + if ((*pass2fn) (data, &ifr->ifr_addr)) + goto punt; + /*@=moduncon@*/ + } +#ifdef LINUX_IPV6_HACK + for (lx_v6 = linux_ipv6_addrs; lx_v6; lx_v6 = lx_v6->next) + if ((*pass2fn) (data, (struct sockaddr *) &lx_v6->addr)) + goto punt; +#endif + } +punt: + /*@-moduncon@*/ + closesocket(s); + /*@=moduncon@*/ + free (buf); +#ifdef LINUX_IPV6_HACK + while (linux_ipv6_addrs) { + lx_v6 = linux_ipv6_addrs->next; + free (linux_ipv6_addrs); + linux_ipv6_addrs = lx_v6; + } +#endif + + return retval; +} + +#endif /* not HAVE_IFADDRS_H and not SIOCGLIFNUM */ + +static krb5_error_code +get_localaddrs (krb5_context context, krb5_address ***addr, int use_profile); + +#ifdef TEST + +static int print_addr (/*@unused@*/ void *dataptr, struct sockaddr *sa) +/*@modifies fileSystem@*/ +{ + char hostbuf[NI_MAXHOST]; + int err; + socklen_t len; + + printf (" --> family %2d ", sa->sa_family); + len = sa_socklen (sa); + err = getnameinfo (sa, len, hostbuf, (socklen_t) sizeof (hostbuf), + (char *) NULL, 0, NI_NUMERICHOST); + if (err) { + int e = errno; + printf ("<getnameinfo error %d: %s>\n", err, gai_strerror (err)); + if (err == EAI_SYSTEM) + printf ("\t\t<errno is %d: %s>\n", e, strerror(e)); + } else + printf ("addr %s\n", hostbuf); + return 0; +} + +int main () +{ + int r; + + (void) setvbuf (stdout, (char *)NULL, _IONBF, 0); + r = foreach_localaddr (0, print_addr, NULL, NULL); + printf ("return value = %d\n", r); + return 0; +} + +#else /* not TESTing */ + +struct localaddr_data { + int count, mem_err, cur_idx, cur_size; + krb5_address **addr_temp; +}; + +static int +count_addrs (void *P_data, struct sockaddr *a) +/*@*/ +{ + struct localaddr_data *data = P_data; + switch (a->sa_family) { + case AF_INET: + case AF_INET6: +#ifdef KRB5_USE_NS + case AF_XNS: +#endif + data->count++; + break; + default: + break; + } + return 0; +} + +static int +allocate (void *P_data) +/*@*/ +{ + struct localaddr_data *data = P_data; + int i; + void *n; + + n = realloc (data->addr_temp, + (1 + data->count + data->cur_idx) * sizeof (krb5_address *)); + if (n == 0) { + data->mem_err++; + return 1; + } + data->addr_temp = n; + data->cur_size = 1 + data->count + data->cur_idx; + for (i = data->cur_idx; i <= data->count + data->cur_idx; i++) + data->addr_temp[i] = 0; + return 0; +} + +static /*@null@*/ krb5_address * +make_addr (int type, size_t length, const void *contents) +/*@*/ +{ + krb5_address *a; + void *data; + + data = malloc (length); + if (data == NULL) + return NULL; + a = malloc (sizeof (krb5_address)); + if (a == NULL) { + free (data); + return NULL; + } + memcpy (data, contents, length); + a->magic = KV5M_ADDRESS; + a->addrtype = type; + a->length = length; + a->contents = data; + return a; +} + +static int +add_addr (void *P_data, struct sockaddr *a) +/*@modifies *P_data@*/ +{ + struct localaddr_data *data = P_data; + /*@null@*/ krb5_address *address = 0; + + switch (a->sa_family) { +#ifdef HAVE_NETINET_IN_H + case AF_INET: + address = make_addr (ADDRTYPE_INET, sizeof (struct in_addr), + &((const struct sockaddr_in *) a)->sin_addr); + if (address == NULL) + data->mem_err++; + break; + + case AF_INET6: + { + const struct sockaddr_in6 *in = (const struct sockaddr_in6 *) a; + + if (IN6_IS_ADDR_LINKLOCAL (&in->sin6_addr)) + break; + + address = make_addr (ADDRTYPE_INET6, sizeof (struct in6_addr), + &in->sin6_addr); + if (address == NULL) + data->mem_err++; + break; + } +#endif /* netinet/in.h */ + +#ifdef KRB5_USE_NS + case AF_XNS: + address = make_addr (ADDRTYPE_XNS, sizeof (struct ns_addr), + &((const struct sockaddr_ns *)a)->sns_addr); + if (address == NULL) + data->mem_err++; + break; +#endif + +#ifdef AF_LINK + /* Some BSD-based systems (e.g. NetBSD 1.5) and AIX will + include the ethernet address, but we don't want that, at + least for now. */ + case AF_LINK: + break; +#endif + /* + * Add more address families here.. + */ + default: + break; + } +#ifdef __LCLINT__ + /* Redundant but unconditional store un-confuses lclint. */ + data->addr_temp[data->cur_idx] = address; +#endif + if (address) { + data->addr_temp[data->cur_idx++] = address; + } + + return data->mem_err; +} + +static krb5_error_code +krb5_os_localaddr_profile (krb5_context context, struct localaddr_data *datap) +{ + krb5_error_code err; + static const char *const profile_name[] = { + KRB5_CONF_LIBDEFAULTS, KRB5_CONF_EXTRA_ADDRESSES, 0 + }; + char **values; + char **iter; + krb5_address **newaddrs; + +#ifdef DEBUG + fprintf (stderr, "looking up extra_addresses foo\n"); +#endif + + err = profile_get_values (context->profile, profile_name, &values); + /* Ignore all errors for now? */ + if (err) + return 0; + + for (iter = values; *iter; iter++) { + char *cp = *iter, *next, *current; + int i, count; + +#ifdef DEBUG + fprintf (stderr, " found line: '%s'\n", cp); +#endif + + for (cp = *iter, next = 0; *cp; cp = next) { + while (isspace ((int) *cp) || *cp == ',') + cp++; + if (*cp == 0) + break; + /* Start of an address. */ +#ifdef DEBUG + fprintf (stderr, " addr found in '%s'\n", cp); +#endif + current = cp; + while (*cp != 0 && !isspace((int) *cp) && *cp != ',') + cp++; + if (*cp != 0) { + next = cp + 1; + *cp = 0; + } else + next = cp; + /* Got a single address, process it. */ +#ifdef DEBUG + fprintf (stderr, " processing '%s'\n", current); +#endif + newaddrs = 0; + err = k5_os_hostaddr (context, current, &newaddrs); + if (err) + continue; + for (i = 0; newaddrs[i]; i++) { +#ifdef DEBUG + fprintf (stderr, " %d: family %d", i, + newaddrs[i]->addrtype); + fprintf (stderr, "\n"); +#endif + } + count = i; +#ifdef DEBUG + fprintf (stderr, " %d addresses\n", count); +#endif + if (datap->cur_idx + count >= datap->cur_size) { + krb5_address **bigger; + bigger = realloc (datap->addr_temp, + sizeof (krb5_address *) * (datap->cur_idx + count)); + if (bigger) { + datap->addr_temp = bigger; + datap->cur_size = datap->cur_idx + count; + } + } + for (i = 0; i < count; i++) { + if (datap->cur_idx < datap->cur_size) + datap->addr_temp[datap->cur_idx++] = newaddrs[i]; + else + free (newaddrs[i]->contents), free (newaddrs[i]); + } + free (newaddrs); + } + } + return 0; +} + +krb5_error_code KRB5_CALLCONV +krb5_os_localaddr(krb5_context context, krb5_address ***addr) +{ + return get_localaddrs(context, addr, 1); +} + +#if 0 /* not actually used anywhere currently */ +krb5_error_code +krb5int_local_addresses(krb5_context context, krb5_address ***addr) +{ + return get_localaddrs(context, addr, 0); +} +#endif + +static krb5_error_code +get_localaddrs (krb5_context context, krb5_address ***addr, int use_profile) +{ + struct localaddr_data data = { 0 }; + int r; + + /* Ignore errors for now. */ + if (use_profile) + (void)krb5_os_localaddr_profile (context, &data); + + r = foreach_localaddr (&data, count_addrs, allocate, add_addr); + if (r != 0) { + int i; + if (data.addr_temp) { + for (i = 0; i < data.count; i++) + free (data.addr_temp[i]); + free (data.addr_temp); + } + if (data.mem_err) + return ENOMEM; + else + return r; + } + + data.cur_idx++; /* null termination */ + if (data.mem_err) + return ENOMEM; + else if (data.cur_idx == data.count) + *addr = data.addr_temp; + else { + /* This can easily happen if we have IPv6 link-local + addresses. Just shorten the array. */ + *addr = (krb5_address **) realloc (data.addr_temp, + (sizeof (krb5_address *) + * data.cur_idx)); + if (*addr == 0) + /* Okay, shortening failed, but the original should still + be intact. */ + *addr = data.addr_temp; + } + +#ifdef DEBUG + { + int j; + fprintf (stderr, "addresses:\n"); + for (j = 0; addr[0][j]; j++) { + struct sockaddr_storage ss; + int err2; + char namebuf[NI_MAXHOST]; + void *addrp = 0; + + fprintf (stderr, "%2d: ", j); + fprintf (stderr, "addrtype %2d, length %2d", addr[0][j]->addrtype, + addr[0][j]->length); + memset (&ss, 0, sizeof (ss)); + switch (addr[0][j]->addrtype) { + case ADDRTYPE_INET: + { + struct sockaddr_in *sinp = ss2sin (&ss); + sinp->sin_family = AF_INET; + addrp = &sinp->sin_addr; + break; + } + case ADDRTYPE_INET6: + { + struct sockaddr_in6 *sin6p = ss2sin6 (&ss); + sin6p->sin6_family = AF_INET6; + addrp = &sin6p->sin6_addr; + break; + } + default: + ss2sa(&ss)->sa_family = 0; + break; + } + if (addrp) + memcpy (addrp, addr[0][j]->contents, addr[0][j]->length); + err2 = getnameinfo (ss2sa(&ss), sa_socklen (ss2sa (&ss)), + namebuf, sizeof (namebuf), 0, 0, + NI_NUMERICHOST); + if (err2 == 0) + fprintf (stderr, ": addr %s\n", namebuf); + else + fprintf (stderr, ": getnameinfo error %d\n", err2); + } + } +#endif + + return 0; +} + +#endif /* not TESTing */ + +#else /* Windows/Mac version */ + +/* + * Hold on to your lunch! Backup kludge method of obtaining your + * local IP address, courtesy of Windows Socket Network Programming, + * by Robert Quinn + */ +#if defined(_WIN32) +static struct hostent *local_addr_fallback_kludge() +{ + static struct hostent host; + static SOCKADDR_IN addr; + static char * ip_ptrs[2]; + SOCKET sock; + int size = sizeof(SOCKADDR); + int err; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == INVALID_SOCKET) + return NULL; + set_cloexec_fd(sock); + + /* connect to arbitrary port and address (NOT loopback) */ + addr.sin_family = AF_INET; + addr.sin_port = htons(IPPORT_ECHO); + addr.sin_addr.s_addr = inet_addr("204.137.220.51"); + + err = connect(sock, (LPSOCKADDR) &addr, sizeof(SOCKADDR)); + if (err == SOCKET_ERROR) + return NULL; + + err = getsockname(sock, (LPSOCKADDR) &addr, (int *) size); + if (err == SOCKET_ERROR) + return NULL; + + closesocket(sock); + + host.h_name = 0; + host.h_aliases = 0; + host.h_addrtype = AF_INET; + host.h_length = 4; + host.h_addr_list = ip_ptrs; + ip_ptrs[0] = (char *) &addr.sin_addr.s_addr; + ip_ptrs[1] = NULL; + + return &host; +} +#endif + +/* No ioctls in winsock so we just assume there is only one networking + * card per machine, so gethostent is good enough. + */ +krb5_error_code KRB5_CALLCONV +krb5_os_localaddr (krb5_context context, krb5_address ***addr) { + char host[64]; /* Name of local machine */ + struct hostent *hostrec; + int err, count, i; + krb5_address ** paddr; + + *addr = 0; + paddr = 0; + err = 0; + + if (gethostname (host, sizeof(host))) { + err = SOCKET_ERRNO; + } + + if (!err) { + hostrec = gethostbyname (host); + if (hostrec == NULL) { + err = SOCKET_ERRNO; + } + } + + if (err) { + hostrec = local_addr_fallback_kludge(); + if (!hostrec) + return err; + else + err = 0; /* otherwise we will die at cleanup */ + } + + for (count = 0; hostrec->h_addr_list[count]; count++); + + + paddr = (krb5_address **)calloc(count+1, sizeof(krb5_address *)); + if (!paddr) { + err = ENOMEM; + goto cleanup; + } + + for (i = 0; i < count; i++) { + paddr[i] = (krb5_address *)malloc(sizeof(krb5_address)); + if (paddr[i] == NULL) { + err = ENOMEM; + goto cleanup; + } + + paddr[i]->magic = KV5M_ADDRESS; + paddr[i]->addrtype = hostrec->h_addrtype; + paddr[i]->length = hostrec->h_length; + paddr[i]->contents = (unsigned char *)malloc(paddr[i]->length); + if (!paddr[i]->contents) { + err = ENOMEM; + goto cleanup; + } + memcpy(paddr[i]->contents, + hostrec->h_addr_list[i], + paddr[i]->length); + } + +cleanup: + if (err) { + if (paddr) { + for (i = 0; i < count; i++) + { + if (paddr[i]) { + if (paddr[i]->contents) + free(paddr[i]->contents); + free(paddr[i]); + } + } + free(paddr); + } + } + else + *addr = paddr; + + return(err); +} +#endif |
