diff options
Diffstat (limited to 'common/gdm-address.c')
-rw-r--r-- | common/gdm-address.c | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/common/gdm-address.c b/common/gdm-address.c new file mode 100644 index 0000000..a8b73e2 --- /dev/null +++ b/common/gdm-address.c @@ -0,0 +1,551 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#ifdef HAVE_STROPTS_H +#include <stropts.h> +#endif +#include <string.h> +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_SYS_SOCKIO_H +#include <sys/sockio.h> +#endif +#include <netdb.h> +#include <sys/ioctl.h> +#include <net/if.h> + +#ifndef G_OS_WIN32 +#include <sys/select.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#else +#include <winsock2.h> +#include <ws2tcpip.h> +#endif + +#include <glib-object.h> + +#include "gdm-address.h" + +struct _GdmAddress +{ + struct sockaddr_storage *ss; +}; + +/* Register GdmAddress in the glib type system */ +GType +gdm_address_get_type (void) +{ + static GType addr_type = 0; + + if (addr_type == 0) { + addr_type = g_boxed_type_register_static ("GdmAddress", + (GBoxedCopyFunc) gdm_address_copy, + (GBoxedFreeFunc) gdm_address_free); + } + + return addr_type; +} + +/** + * gdm_address_get_family_type: + * @address: A pointer to a #GdmAddress + * + * Use this function to retrive the address family of @address. + * + * Return value: The address family of @address. + **/ +int +gdm_address_get_family_type (GdmAddress *address) +{ + g_return_val_if_fail (address != NULL, -1); + + return address->ss->ss_family; +} + + +/** + * gdm_address_new_from_sockaddr: + * @sa: A pointer to a sockaddr. + * @size: size of sockaddr in bytes. + * + * Creates a new #GdmAddress from @sa. + * + * Return value: The new #GdmAddress + * or %NULL if @sa was invalid or the address family isn't supported. + **/ +GdmAddress * +gdm_address_new_from_sockaddr (struct sockaddr *sa, + size_t size) +{ + GdmAddress *addr; + + g_return_val_if_fail (sa != NULL, NULL); + g_return_val_if_fail (size >= sizeof (struct sockaddr), NULL); + g_return_val_if_fail (size <= sizeof (struct sockaddr_storage), NULL); + + addr = g_new0 (GdmAddress, 1); + addr->ss = g_new0 (struct sockaddr_storage, 1); + memcpy (addr->ss, sa, size); + + return addr; +} + +/** + * gdm_address_get_sockaddr_storage: + * @address: A #GdmAddress + * + * This function tanslates @address into a equivalent + * sockaddr_storage + * + * Return value: A newly allocated sockaddr_storage structure the caller must free + * or %NULL if @address did not point to a valid #GdmAddress. + **/ +struct sockaddr_storage * +gdm_address_get_sockaddr_storage (GdmAddress *address) +{ + struct sockaddr_storage *ss; + + g_return_val_if_fail (address != NULL, NULL); + g_return_val_if_fail (address->ss != NULL, NULL); + + ss = g_memdup (address->ss, sizeof (struct sockaddr_storage)); + + return ss; +} + +struct sockaddr_storage * +gdm_address_peek_sockaddr_storage (GdmAddress *address) +{ + g_return_val_if_fail (address != NULL, NULL); + + return address->ss; +} + +static gboolean +v4_v4_equal (const struct sockaddr_in *a, + const struct sockaddr_in *b) +{ + return a->sin_addr.s_addr == b->sin_addr.s_addr; +} + +#ifdef ENABLE_IPV6 +static gboolean +v6_v6_equal (struct sockaddr_in6 *a, + struct sockaddr_in6 *b) +{ + return IN6_ARE_ADDR_EQUAL (&a->sin6_addr, &b->sin6_addr); +} +#endif + +#define SA(__s) ((struct sockaddr *) __s) +#define SIN(__s) ((struct sockaddr_in *) __s) +#define SIN6(__s) ((struct sockaddr_in6 *) __s) + +gboolean +gdm_address_equal (GdmAddress *a, + GdmAddress *b) +{ + guint8 fam_a; + guint8 fam_b; + + g_return_val_if_fail (a != NULL, FALSE); + g_return_val_if_fail (a->ss != NULL, FALSE); + g_return_val_if_fail (b != NULL, FALSE); + g_return_val_if_fail (b->ss != NULL, FALSE); + + fam_a = a->ss->ss_family; + fam_b = b->ss->ss_family; + + if (fam_a == AF_INET && fam_b == AF_INET) { + return v4_v4_equal (SIN (a->ss), SIN (b->ss)); + } +#ifdef ENABLE_IPV6 + else if (fam_a == AF_INET6 && fam_b == AF_INET6) { + return v6_v6_equal (SIN6 (a->ss), SIN6 (b->ss)); + } +#endif + return FALSE; +} + +/* for debugging */ +static const char * +address_family_str (GdmAddress *address) +{ + const char *str; + switch (address->ss->ss_family) { + case AF_INET: + str = "inet"; + break; + case AF_INET6: + str = "inet6"; + break; + case AF_UNIX: + str = "unix"; + break; + case AF_UNSPEC: + str = "unspecified"; + break; + default: + str = "unknown"; + break; + } + return str; +} + +static void +_gdm_address_debug (GdmAddress *address, + const char *host, + const char *port) +{ + g_debug ("Address family:%d (%s) host:%s port:%s local:%d loopback:%d", + + address->ss->ss_family, + address_family_str (address) ? address_family_str (address) : "(null)", + host ? host : "(null)", + port ? port : "(null)", + gdm_address_is_local (address), + gdm_address_is_loopback (address)); +} + +void +gdm_address_debug (GdmAddress *address) +{ + char *hostname = NULL; + char *host = NULL; + char *port = NULL; + + gdm_address_get_numeric_info (address, &host, &port); + + _gdm_address_debug (address, host, port); + + g_free (hostname); + g_free (host); + g_free (port); +} + +gboolean +gdm_address_get_hostname (GdmAddress *address, + char **hostnamep) +{ + char host [NI_MAXHOST]; + int res; + gboolean ret; + + g_return_val_if_fail (address != NULL, FALSE); + g_return_val_if_fail (address->ss != NULL, FALSE); + + ret = FALSE; + + host [0] = '\0'; + res = getnameinfo ((const struct sockaddr *)address->ss, + (int) gdm_sockaddr_len (address->ss), + host, sizeof (host), + NULL, 0, + 0); + if (res == 0) { + ret = TRUE; + goto done; + } else { + const char *err_msg; + + err_msg = gai_strerror (res); + g_warning ("Unable to lookup hostname: %s", + err_msg ? err_msg : "(null)"); + _gdm_address_debug (address, NULL, NULL); + + } + + /* try numeric? */ + + done: + if (hostnamep != NULL) { + *hostnamep = g_strdup (host); + } + + return ret; +} + +gboolean +gdm_address_get_numeric_info (GdmAddress *address, + char **hostp, + char **servp) +{ + char host [NI_MAXHOST]; + char serv [NI_MAXSERV]; + int res; + gboolean ret; + + g_return_val_if_fail (address != NULL, FALSE); + g_return_val_if_fail (address->ss != NULL, FALSE); + + ret = FALSE; + + host [0] = '\0'; + serv [0] = '\0'; + res = getnameinfo ((const struct sockaddr *)address->ss, + (int) gdm_sockaddr_len (address->ss), + host, sizeof (host), + serv, sizeof (serv), + NI_NUMERICHOST | NI_NUMERICSERV); + if (res != 0) { + const char *err_msg; + + err_msg = gai_strerror (res); + g_warning ("Unable to lookup numeric info: %s", + err_msg ? err_msg : "(null)"); + _gdm_address_debug (address, NULL, NULL); + } else { + ret = TRUE; + } + + if (servp != NULL) { + if (g_str_has_prefix (serv, "::ffff:")) { + *servp = g_strdup (serv + 7); + } else { + *servp = g_strdup (serv); + } + } + if (hostp != NULL) { + if (g_str_has_prefix (host, "::ffff:")) { + *hostp = g_strdup (host + 7); + } else { + *hostp = g_strdup (host); + } + } + + return ret; +} + +gboolean +gdm_address_is_loopback (GdmAddress *address) +{ + g_return_val_if_fail (address != NULL, FALSE); + g_return_val_if_fail (address->ss != NULL, FALSE); + + switch (address->ss->ss_family){ +#ifdef AF_INET6 + case AF_INET6: + return IN6_IS_ADDR_LOOPBACK (&((struct sockaddr_in6 *)address->ss)->sin6_addr); + break; +#endif + case AF_INET: + return (INADDR_LOOPBACK == htonl (((struct sockaddr_in *)address->ss)->sin_addr.s_addr)); + break; + default: + break; + } + + return FALSE; +} + +static void +add_local_siocgifconf (GList **list) +{ + struct ifconf ifc; + struct ifreq ifreq; + struct ifreq *ifr; + struct ifreq *the_end; + int sock; + char buf[BUFSIZ]; + + if ((sock = socket (PF_INET, SOCK_DGRAM, 0)) < 0) { + perror ("socket"); + return; + } + + ifc.ifc_len = sizeof (buf); + ifc.ifc_buf = buf; + if (ioctl (sock, SIOCGIFCONF, (char *) &ifc) < 0) { + perror ("SIOCGIFCONF"); + close (sock); + return; + } + + /* Get IP address of each active IP network interface. */ + the_end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len); + + for (ifr = ifc.ifc_req; ifr < the_end; ifr++) { + if (ifr->ifr_addr.sa_family == AF_INET) { + /* IP net interface */ + ifreq = *ifr; + + if (ioctl (sock, SIOCGIFFLAGS, (char *) &ifreq) < 0) { + perror("SIOCGIFFLAGS"); + } else if (ifreq.ifr_flags & IFF_UP) { /* active interface */ + if (ioctl (sock, SIOCGIFADDR, (char *) &ifreq) < 0) { + perror("SIOCGIFADDR"); + } else { + GdmAddress *address; + address = gdm_address_new_from_sockaddr ((struct sockaddr *)&ifreq.ifr_addr, + sizeof (struct sockaddr)); + + *list = g_list_append (*list, address); + } + } + } + + /* Support for variable-length addresses. */ +#ifdef HAS_SA_LEN + ifr = (struct ifreq *) ((caddr_t) ifr + + ifr->ifr_addr.sa_len - sizeof(struct sockaddr)); +#endif + } + + close (sock); +} + +static void +add_local_addrinfo (GList **list) +{ + char hostbuf[BUFSIZ]; + struct addrinfo *result; + struct addrinfo *res; + struct addrinfo hints; + + hostbuf[BUFSIZ-1] = '\0'; + if (gethostname (hostbuf, BUFSIZ-1) != 0) { + g_debug ("%s: Could not get server hostname, using localhost", "gdm_peek_local_address_list"); + snprintf (hostbuf, BUFSIZ-1, "localhost"); + } + + memset (&hints, 0, sizeof (hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_CANONNAME | AI_NUMERICHOST; + + + g_debug ("GdmAddress: looking up hostname: %s", hostbuf); + result = NULL; + if (getaddrinfo (hostbuf, NULL, &hints, &result) != 0) { + g_debug ("%s: Could not get address from hostname!", "gdm_peek_local_address_list"); + + return; + } + + for (res = result; res != NULL; res = res->ai_next) { + GdmAddress *address; + + g_debug ("family=%d sock_type=%d protocol=%d flags=0x%x canonname=%s\n", + res->ai_family, + res->ai_socktype, + res->ai_protocol, + res->ai_flags, + res->ai_canonname ? res->ai_canonname : "(null)"); + address = gdm_address_new_from_sockaddr (res->ai_addr, res->ai_addrlen); + *list = g_list_append (*list, address); + } + + if (result != NULL) { + freeaddrinfo (result); + result = NULL; + } +} + +const GList * +gdm_address_peek_local_list (void) +{ + static GList *list = NULL; + static time_t last_time = 0; + + /* Don't check more then every 5 seconds */ + if (last_time + 5 > time (NULL)) { + return list; + } + + g_list_foreach (list, (GFunc)gdm_address_free, NULL); + g_list_free (list); + list = NULL; + + last_time = time (NULL); + + add_local_siocgifconf (&list); + add_local_addrinfo (&list); + + return list; +} + +gboolean +gdm_address_is_local (GdmAddress *address) +{ + const GList *list; + + if (gdm_address_is_loopback (address)) { + return TRUE; + } + + list = gdm_address_peek_local_list (); + + while (list != NULL) { + GdmAddress *addr = list->data; + + if (gdm_address_equal (address, addr)) { + return TRUE; + } + + list = list->next; + } + + return FALSE; +} + +/** + * gdm_address_copy: + * @address: A #GdmAddress. + * + * Duplicates @address. + * + * Return value: Duplicated @address or %NULL if @address was not valid. + **/ +GdmAddress * +gdm_address_copy (GdmAddress *address) +{ + GdmAddress *addr; + + g_return_val_if_fail (address != NULL, NULL); + + addr = g_new0 (GdmAddress, 1); + addr->ss = g_memdup (address->ss, sizeof (struct sockaddr_storage)); + + return addr; +} + +/** + * gdm_address_free: + * @address: A #GdmAddress. + * + * Frees the memory allocated for @address. + **/ +void +gdm_address_free (GdmAddress *address) +{ + g_return_if_fail (address != NULL); + + g_free (address->ss); + g_free (address); +} + |