Logo Search packages:      
Sourcecode: leafnode version File versions

checkpeerlocal.c

/* checkpeerlocal.c -- check if the peer address of a socket is on a
 *                     local network.
 * (C) 2002 by Matthias Andree <matthias.andree@gmx.de>
 *
 * 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 <sys/types.h>
#include <sys/socket.h>
#ifdef HAVE_SYS_SOCKIO_H
/* needed for SunOS 5, IRIX, ... */
#include <sys/sockio.h>
#endif
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <errno.h>
#ifndef __LCLINT__
#include <arpa/inet.h>
#endif /* not __LCLINT__ */
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#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 <string.h>

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

Generated by  Doxygen 1.6.0   Back to index