/* checkpeerlocal.c -- check if the peer address of a socket is on a * local network. * (C) 2002 by Matthias Andree * * This library is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU Lesser General Public License as * published by the Free Software Foundation. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library (look for the COPYING.LGPL file); if * not, write to the Free Software Foundation, Inc., 59 Temple Place, * Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include "mastring.h" #include "critmem.h" #include "leafnode.h" #include #include #ifdef HAVE_SYS_SOCKIO_H /* needed for SunOS 5, IRIX, ... */ #include #endif #include #include #include #include #ifndef __LCLINT__ #include #endif /* not __LCLINT__ */ #include #include #include #include "strlcpy.h" #ifdef TEST #define D(a) a #else #define D(a) #endif #ifdef TEST static void pat(struct sockaddr *addr) { char buf[512]; /* RATS: ignore */ char *tag = ""; switch (addr->sa_family) { #ifdef HAVE_IPV6 case AF_INET6: inet_ntop(addr->sa_family, &((struct sockaddr_in6 *)addr)->sin6_addr, buf, sizeof(buf)); tag = "IPv6: "; break; #endif case AF_INET: strlcpy(buf, inet_ntoa(((struct sockaddr_in *)addr)->sin_addr), sizeof(buf)); tag = "IPv4: "; break; default: strlcpy(buf, "unsupported address type", sizeof(buf)); break; } printf("%s%s\n", tag, buf); } #endif /* * checks whether the peer of the socket is local to any of our network * interfaces, returns -1 for error (check errno), -2 for other error, * 0 for no, 1 for yes. If sock is not a socket, also returns 1. */ int checkpeerlocal(int sock) { char addr[128]; /* RATS: ignore */ mastr *buf; struct ifconf ifc; struct ifreq *ifr, *end; int type; socklen_t size; int newsock; /* obtain peer address */ size = sizeof(addr); #ifdef TEST_LOCAL sock = socket(AF_INET, SOCK_DGRAM, 0); if (getsockname(sock, (struct sockaddr *)addr, &size)) { #else if (getpeername(sock, (struct sockaddr *)addr, &size)) { #endif if (errno == ENOTSOCK) return 1; else return -1; } type = ((struct sockaddr *)addr)->sa_family; D(printf("address type of peer socket: %d\n", type)); switch(type) { case AF_INET: break; #ifdef HAVE_IPV6 case AF_INET6: break; #endif default: D(printf("address type not supported.\n")); return -2; } D(pat((struct sockaddr *)addr)); #ifdef HAVE_IPV6 if (type == AF_INET6) { struct sockaddr_in6 *i6 = (struct sockaddr_in6 *)addr; D((printf("IPv6 address\n"))); /* IPv6 localhost */ if (IN6_IS_ADDR_LOOPBACK(&i6->sin6_addr)) { D((printf("IPv6 loopback address\n"))); return 1; } else if (IN6_IS_ADDR_LINKLOCAL(&i6->sin6_addr)) { D((printf("IPv6 link local address\n"))); return 1; } else if (IN6_IS_ADDR_SITELOCAL(&i6->sin6_addr)) { D((printf("IPv6 site local address\n"))); return 1; } else if (IN6_IS_ADDR_V4MAPPED(&i6->sin6_addr)) { /* map to IPv4 */ struct sockaddr_in si; D((printf("IPv4 mapped IPv6 address\n"))); si.sin_family = AF_INET; si.sin_port = i6->sin6_port; memcpy(&si.sin_addr, &(i6->sin6_addr.s6_addr[12]), 4); memcpy(addr, &si, sizeof(struct sockaddr_in)); } else { return 0; } } #endif D(pat((struct sockaddr *)addr)); buf = mastr_new(2048); newsock = socket(PF_INET, SOCK_DGRAM, 0); if (sock < 0) return -1; /* get list of address information */ for (;;) { ifc.ifc_len = mastr_size(buf); ifc.ifc_buf = mastr_modifyable_str(buf); if (ioctl(newsock, SIOCGIFCONF, (char *)&ifc) < 0) { if (errno != EINVAL) { close(sock); mastr_delete(buf); return -1; } } /* work around bugs in old Solaris (see Postfix' * inet_addr_local.c for details) */ if ((unsigned)ifc.ifc_len < mastr_size(buf) / 2) { break; } mastr_resizekill(buf, mastr_size(buf) * 2); } /* get addresses and netmasks */ end = (struct ifreq *)((char *)ifc.ifc_buf + ifc.ifc_len); for (ifr = ifc.ifc_req ; ifr < end ; #ifdef HAVE_SALEN ifr = ((struct ifreq *) ((char *) ifr + sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len)) #else ifr++ #endif ) { struct in_addr sia; #ifdef HAVE_SALEN D(printf("interface: name %s, address type: %d, sa_len: %d\n", ifr->ifr_name, ifr->ifr_addr.sa_family, ifr->ifr_addr.sa_len)); #else D(printf("interface: name %s, address type: %d\n", ifr->ifr_name, ifr->ifr_addr.sa_family)); #endif sia = ((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr; switch (ifr->ifr_addr.sa_family) { case AF_INET: break; #ifdef HAVE_IPV6 case AF_INET6: break; #endif default: continue; } D(pat((struct sockaddr *)&ifr->ifr_addr)); if (sia.s_addr != INADDR_ANY) { struct in_addr adr, msk; struct ifreq ir; memcpy(&ir, ifr, sizeof(struct ifreq)); /* Prevent nasty surprises on old Linux kernels with * BSD-style IP aliasing (more than one IPv4 address on * the same interface) -- SIOCGIFADDR/...NETMASK will * return address and netmask for the first address, * while SIOCGIFCONF will return the alias address, so * there's no way to figure the NETMASK for these * addresses. */ if (ioctl(newsock, SIOCGIFADDR, &ir) < 0) goto bail_errno; adr = ((struct sockaddr_in *)(&ir.ifr_addr))->sin_addr; if (adr.s_addr != sia.s_addr) { char *buf; buf = critstrdup(inet_ntoa(sia), "checkpeerlocal"); syslog(LOG_CRIT, "Problem: your kernel cannot deal with 4.4BSD-style IP aliases properly. " "SIOCGIFADDR for interface %s address %s yields %s -> mismatch. " "Configure Solaris-style aliases like %s:0 instead. Ignoring this interface, addresses in its range will be considered remote.", ifr->ifr_name, buf, inet_ntoa(adr), ifr->ifr_name); D(printf("Kernel does not handle 4.4BSD-style IP alias for interface %s address %s, ignoring this interface.\n", ifr->ifr_name, buf)); free(buf); continue; } memcpy(&ir, ifr, sizeof(struct ifreq)); if (ioctl(newsock, SIOCGIFNETMASK, &ir) < 0) goto bail_errno; msk = ((struct sockaddr_in *)(&ir.ifr_addr))->sin_addr; D(printf("address/netmask: %s/", inet_ntoa(adr))); D(printf("%s\n", inet_ntoa(msk))); if ((((struct sockaddr_in *)addr)->sin_addr.s_addr & msk.s_addr) == (adr.s_addr & msk.s_addr)) { D(printf("found\n")); close(newsock); mastr_delete(buf); return 1; } } } close(newsock); mastr_delete(buf); return 0; bail_errno: close(newsock); mastr_delete(buf); return -1; } #ifdef TEST #include int verbose = 0; int debug = 0; int main(void) { int r; printf("checkpeerlocal returned: %d\n", (r = checkpeerlocal(0))); if (r == -1) printf("errno: %d (%s)\n", errno, strerror(errno)); return 0; } #endif