/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2007 William Jon McCann * * 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 #include #include #include #ifdef HAVE_STROPTS_H #include #endif #include #ifdef HAVE_SYS_SOCKET_H #include #endif #ifdef HAVE_SYS_SOCKIO_H #include #endif #include #include #include #ifndef G_OS_WIN32 #include #include #include #else #include #include #endif #include #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); }