/* -*- c-file-style: "java"; indent-tabs-mode: nil; tab-width: 4; fill-column: 78 -*- * * distcc -- A simple distributed compiler system * * Copyright (C) 2003, 2004 by Martin Pool * * 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. */ /* * They that forsake the law praise the wicked: but such * as keep the law contend with them. * -- Proverbs 28:4 */ #include #include #include #include #include #include #include #include #include #include "types.h" #include "distcc.h" #include "trace.h" #include "access.h" #include "exitcode.h" /** * @file * * IP-based access-control system */ static const uint32_t allones = 0xffffffffUL; static const uint8_t allones8 = 0xffU; /** * Split a "HOST/BITS" mask specification into HOST and BITS. * * @returns 0 on success or -1 if the program ran out of heap space **/ static int split_mask(const char *spec, char **host, const char **bits) { int value_len = strcspn(spec, "/"); if (host != NULL) { char *host_str; host_str = malloc(value_len + 1); if (host_str == NULL) { rs_log_error("could not allocate memory (%d bytes)", value_len + 1); return -1; } strncpy(host_str, spec, value_len); host_str[value_len] = '\0'; *host = host_str; } if (bits != NULL) { if (spec[value_len] && spec[value_len + 1]) *bits = spec + value_len + 1; else *bits = NULL; } return 0; } /** * Set a dcc_address_t to a full mask. **/ static void set_allones(dcc_address_t *addr) { #ifndef ENABLE_RFC2553 addr->s_addr = allones; #else memset(&addr->addr, allones8, sizeof(struct in6_addr)); #endif } /** * Set a 32-bit address, @p addr, to a mask of @p mask_size bits. **/ static inline void set_mask_inet(struct in_addr *addr, int mask_bits) { addr->s_addr = htonl(~(allones >> mask_bits)); } #ifdef ENABLE_RFC2553 /** * Set a v6 address, @p addr, to a mask of @p mask_size bits. **/ static void set_mask_inet6(struct in6_addr *addr, int mask_bits) { uint8_t *ip6_addr = addr->s6_addr; int allones_count = mask_bits / 8; int i; for (i = 0; i < allones_count; i++) ip6_addr[i] = allones8; ip6_addr[i] = ~(allones8 >> (mask_bits % 8)); i++; for (; i < 16; i++) ip6_addr[i] = 0; } #endif /* ENABLE_RFC2553 */ /** * Set a dcc_address_t to a mask of @p mask_bits bits. The family field of * the address should be set appropriately if RFC2553 is enabled. **/ static void set_mask(dcc_address_t *mask, int mask_bits) { #ifndef ENABLE_RFC2553 set_mask_inet(mask, mask_bits); #else if (mask->family == AF_INET) set_mask_inet(&mask->addr.inet, mask_bits); else set_mask_inet6(&mask->addr.inet6, mask_bits); #endif } /** * Interpret a "HOST/BITS" mask specification. Return @p value and @p mask. **/ int dcc_parse_mask(const char *spec, dcc_address_t *value, dcc_address_t *mask) { char *value_str; int ret; const char *mask_str; #ifndef ENABLE_RFC2553 struct in_addr ia; #else struct addrinfo hints; struct addrinfo *res; #endif ret = split_mask(spec, &value_str, &mask_str); if (ret == -1) return EXIT_OUT_OF_MEMORY; /* extract and parse value part */ #ifndef ENABLE_RFC2553 ret = inet_aton(value_str, &ia); if (ret == 0) { rs_log_error("can't parse internet address \"%s\"", value_str); free(value_str); return EXIT_BAD_ARGUMENTS; } *value = ia; #else /* ENABLE_RFC2553 */ memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_NUMERICHOST; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; ret = getaddrinfo(value_str, NULL, &hints, &res); if (ret != 0) { rs_log_error("can't parse internet address \"%s\": %s", value_str, gai_strerror(ret)); free(value_str); return EXIT_BAD_ARGUMENTS; } value->family = res->ai_family; if (res->ai_family == AF_INET) { value->addr.inet = ((struct sockaddr_in *)res->ai_addr)->sin_addr; } else if (res->ai_family == AF_INET6) { value->addr.inet6 = ((struct sockaddr_in6 *)res->ai_addr)->sin6_addr; } else { rs_log_error("unsupported address family %d for address \"%s\"", res->ai_family, value_str); freeaddrinfo(res); free(value_str); return EXIT_BAD_ARGUMENTS; } freeaddrinfo(res); #endif /* ENABLE_RFC2553 */ free(value_str); #ifdef ENABLE_RFC2553 mask->family = value->family; #endif if (mask_str != NULL) { int mask_bits; #ifndef ENABLE_RFC2553 static const int mask_max_bits = 32; #else int mask_max_bits; if (mask->family == AF_INET) mask_max_bits = 32; else mask_max_bits = 128; #endif /* find mask length as a number of bits */ mask_bits = atoi(mask_str); if (mask_bits < 0 || mask_bits > mask_max_bits) { rs_log_error("invalid mask \"%s\"", mask_str); return EXIT_BAD_ARGUMENTS; } /* Make a network-endian mask with the top mask_bits set. */ if (mask_bits == mask_max_bits) { set_allones(mask); } else { set_mask(mask, mask_bits); } } else { set_allones(mask); } return 0; } /** * Check whether two 32-bit addresses match. * * @returns 0 if they match, or EXIT_ACCESS_DENIED. **/ static int check_address_inet(const struct in_addr *client, const struct in_addr *value, const struct in_addr *mask) { if ((client->s_addr & mask->s_addr) == (value->s_addr & mask->s_addr)) { rs_trace("match client %#lx, value %#lx, mask %#lx", (long) client->s_addr, (long) value->s_addr, (long) mask->s_addr); return 0; } else { rs_trace("deny client %#lx, value %#lx, mask %#lx", (long) client->s_addr, (long) value->s_addr, (long) mask->s_addr); return EXIT_ACCESS_DENIED; } } #ifdef ENABLE_RFC2553 /** * Compare the masked components of two v6 addresses * * @returns 1 if they match, or 0. **/ static int compare_address_inet6(const struct in6_addr *client, const struct in6_addr *value, const struct in6_addr *mask) { int i; for (i = 0; i < 16; ++i) if ((client->s6_addr[i] & mask->s6_addr[i]) != (value->s6_addr[i] & mask->s6_addr[i])) return 0; return 1; } /** * Check whether two v6 addresses match. * * @returns 0 if they match, or EXIT_ACCESS_DENIED. **/ static int check_address_inet6(const struct in6_addr *client, const struct in6_addr *value, const struct in6_addr *mask) { if (compare_address_inet6(client, value, mask)) { rs_trace("match v6 client"); //rs_trace("match client %#lx, value %#lx, mask %#lx", //(long) *client, (long) *value, (long) *mask); return 0; } else { rs_trace("deny v6 client"); //rs_trace("deny client %#lx, value %#lx, mask %#lx", //(long) *client, (long) *value, (long) *mask); return EXIT_ACCESS_DENIED; } } /** * Extract the lower 32-bits of a v6 address. **/ static uint32_t inet_from_inet6(const struct in6_addr *addr) { uint32_t dest = 0; int i; for (i = 0; i < 4; ++i) { uint32_t dest_byte = addr->s6_addr[12 + i]; dest |= (dest_byte << (8 * (3-i))); } return dest; } #endif /* ENABLE_RFC2553 */ /** * Check whether a client ought to be allowed. * * @returns 0 for allowed, or EXIT_ACCESS_DENIED. **/ int dcc_check_address(const struct sockaddr *client, const dcc_address_t *value, const dcc_address_t *mask) { if (client->sa_family == AF_INET) { /* The double-cast here avoids warnings from -Wcast-align. */ const struct sockaddr_in *sockaddr = (const struct sockaddr_in *) (void *) client; const struct in_addr *cli_addr = &sockaddr->sin_addr; #ifndef ENABLE_RFC2553 return check_address_inet(cli_addr, value, mask); #else if (value->family == AF_INET) return check_address_inet(cli_addr, &value->addr.inet, &mask->addr.inet); else return EXIT_ACCESS_DENIED; } else if (client->sa_family == AF_INET6) { const struct sockaddr_in6 *sa6 = (const struct sockaddr_in6 *)client; const struct in6_addr *a6 = &sa6->sin6_addr; if (value->family == AF_INET) { if (IN6_IS_ADDR_V4MAPPED(a6) || IN6_IS_ADDR_V4COMPAT(a6)) { struct in_addr a4; a4.s_addr = inet_from_inet6(a6); return check_address_inet(&a4, &value->addr.inet, &mask->addr.inet); } else return EXIT_ACCESS_DENIED; } else return check_address_inet6(a6, &value->addr.inet6, &mask->addr.inet6); #endif } else { rs_log_notice("access denied from unsupported address family %d", client->sa_family); return EXIT_ACCESS_DENIED; } }