/* 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
syntax highlighted by Code2HTML, v. 0.9.1