diff options
Diffstat (limited to 'src/hostmask.c')
-rw-r--r-- | src/hostmask.c | 820 |
1 files changed, 820 insertions, 0 deletions
diff --git a/src/hostmask.c b/src/hostmask.c new file mode 100644 index 0000000..f7cca41 --- /dev/null +++ b/src/hostmask.c @@ -0,0 +1,820 @@ +/* + * ircd-hybrid: an advanced Internet Relay Chat Daemon(ircd). + * hostmask.c: Code to efficiently find IP & hostmask based configs. + * + * Copyright (C) 2005 by the past and present ircd coders, and others. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + * $Id$ + */ + +#include "stdinc.h" +#include "memory.h" +#include "ircd_defs.h" +#include "list.h" +#include "conf.h" +#include "hostmask.h" +#include "send.h" +#include "irc_string.h" +#include "ircd.h" + + +#define DigitParse(ch) do { \ + if (ch >= '0' && ch <= '9') \ + ch = ch - '0'; \ + else if (ch >= 'A' && ch <= 'F') \ + ch = ch - 'A' + 10; \ + else if (ch >= 'a' && ch <= 'f') \ + ch = ch - 'a' + 10; \ + } while (0); + +/* The mask parser/type determination code... */ + +/* int try_parse_v6_netmask(const char *, struct irc_ssaddr *, int *); + * Input: A possible IPV6 address as a string. + * Output: An integer describing whether it is an IPV6 or hostmask, + * an address(if it is IPV6), a bitlength(if it is IPV6). + * Side effects: None + * Comments: Called from parse_netmask + */ +/* Fixed so ::/0 (any IPv6 address) is valid + Also a bug in DigitParse above. + -Gozem 2002-07-19 gozem@linux.nu +*/ +#ifdef IPV6 +static int +try_parse_v6_netmask(const char *text, struct irc_ssaddr *addr, int *b) +{ + const char *p; + char c; + int d[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + int dp = 0; + int nyble = 4; + int finsert = -1; + int bits = 128; + int deficit = 0; + short dc[8]; + struct sockaddr_in6 *v6 = (struct sockaddr_in6 *)addr; + + for (p = text; (c = *p); p++) + if (IsXDigit(c)) + { + if (nyble == 0) + return HM_HOST; + DigitParse(c); + d[dp] |= c << (4 * --nyble); + } + else if (c == ':') + { + if (p > text && *(p - 1) == ':') + { + if (finsert >= 0) + return HM_HOST; + finsert = dp; + } + else + { + /* If there were less than 4 hex digits, e.g. :ABC: shift right + * so we don't interpret it as ABC0 -A1kmm */ + d[dp] = d[dp] >> 4 * nyble; + nyble = 4; + if (++dp >= 8) + return HM_HOST; + } + } + else if (c == '*') + { + /* * must be last, and * is ambiguous if there is a ::... -A1kmm */ + if (finsert >= 0 || *(p + 1) || dp == 0 || *(p - 1) != ':') + return HM_HOST; + bits = dp * 16; + } + else if (c == '/') + { + char *after; + + d[dp] = d[dp] >> 4 * nyble; + dp++; + bits = strtoul(p + 1, &after, 10); + + if (bits < 0 || *after) + return HM_HOST; + if (bits > dp * 4 && !(finsert >= 0 && bits <= 128)) + return HM_HOST; + break; + } + else + return HM_HOST; + + d[dp] = d[dp] >> 4 * nyble; + + if (c == 0) + dp++; + if (finsert < 0 && bits == 0) + bits = dp * 16; + + /* How many words are missing? -A1kmm */ + deficit = bits / 16 + ((bits % 16) ? 1 : 0) - dp; + + /* Now fill in the gaps(from ::) in the copied table... -A1kmm */ + for (dp = 0, nyble = 0; dp < 8; dp++) + { + if (nyble == finsert && deficit) + { + dc[dp] = 0; + deficit--; + } + else + dc[dp] = d[nyble++]; + } + + /* Set unused bits to 0... -A1kmm */ + if (bits < 128 && (bits % 16 != 0)) + dc[bits / 16] &= ~((1 << (15 - bits % 16)) - 1); + for (dp = bits / 16 + (bits % 16 ? 1 : 0); dp < 8; dp++) + dc[dp] = 0; + + /* And assign... -A1kmm */ + if (addr) + for (dp = 0; dp < 8; dp++) + /* The cast is a kludge to make netbsd work. */ + ((unsigned short *)&v6->sin6_addr)[dp] = htons(dc[dp]); + + if (b != NULL) + *b = bits; + return HM_IPV6; +} +#endif + +/* int try_parse_v4_netmask(const char *, struct irc_ssaddr *, int *); + * Input: A possible IPV4 address as a string. + * Output: An integer describing whether it is an IPV4 or hostmask, + * an address(if it is IPV4), a bitlength(if it is IPV4). + * Side effects: None + * Comments: Called from parse_netmask + */ +static int +try_parse_v4_netmask(const char *text, struct irc_ssaddr *addr, int *b) +{ + const char *p; + const char *digits[4]; + unsigned char addb[4]; + int n = 0, bits = 0; + char c; + struct sockaddr_in *v4 = (struct sockaddr_in *)addr; + + digits[n++] = text; + + for (p = text; (c = *p); p++) + if (c >= '0' && c <= '9') /* empty */ + ; + else if (c == '.') + { + if (n >= 4) + return HM_HOST; + + digits[n++] = p + 1; + } + else if (c == '*') + { + if (*(p + 1) || n == 0 || *(p - 1) != '.') + return HM_HOST; + + bits = (n - 1) * 8; + break; + } + else if (c == '/') + { + char *after; + bits = strtoul(p + 1, &after, 10); + + if (!bits || *after) + return HM_HOST; + if (bits > n * 8) + return HM_HOST; + + break; + } + else + return HM_HOST; + + if (n < 4 && bits == 0) + bits = n * 8; + if (bits) + while (n < 4) + digits[n++] = "0"; + + for (n = 0; n < 4; n++) + addb[n] = strtoul(digits[n], NULL, 10); + + if (bits == 0) + bits = 32; + + /* Set unused bits to 0... -A1kmm */ + if (bits < 32 && bits % 8) + addb[bits / 8] &= ~((1 << (8 - bits % 8)) - 1); + for (n = bits / 8 + (bits % 8 ? 1 : 0); n < 4; n++) + addb[n] = 0; + if (addr) + v4->sin_addr.s_addr = + htonl(addb[0] << 24 | addb[1] << 16 | addb[2] << 8 | addb[3]); + if (b != NULL) + *b = bits; + return HM_IPV4; +} + +/* int parse_netmask(const char *, struct irc_ssaddr *, int *); + * Input: A hostmask, or an IPV4/6 address. + * Output: An integer describing whether it is an IPV4, IPV6 address or a + * hostmask, an address(if it is an IP mask), + * a bitlength(if it is IP mask). + * Side effects: None + */ +int +parse_netmask(const char *text, struct irc_ssaddr *addr, int *b) +{ +#ifdef IPV6 + if (strchr(text, ':')) + return try_parse_v6_netmask(text, addr, b); +#endif + if (strchr(text, '.')) + return try_parse_v4_netmask(text, addr, b); + return HM_HOST; +} + +/* The address matching stuff... */ +/* int match_ipv6(struct irc_ssaddr *, struct irc_ssaddr *, int) + * Input: An IP address, an IP mask, the number of bits in the mask. + * Output: if match, -1 else 0 + * Side effects: None + */ +#ifdef IPV6 +int +match_ipv6(const struct irc_ssaddr *addr, const struct irc_ssaddr *mask, int bits) +{ + int i, m, n = bits / 8; + const struct sockaddr_in6 *v6 = (const struct sockaddr_in6 *)addr; + const struct sockaddr_in6 *v6mask = (const struct sockaddr_in6 *)mask; + + for (i = 0; i < n; i++) + if (v6->sin6_addr.s6_addr[i] != v6mask->sin6_addr.s6_addr[i]) + return 0; + if ((m = bits % 8) == 0) + return -1; + if ((v6->sin6_addr.s6_addr[n] & ~((1 << (8 - m)) - 1)) == + v6mask->sin6_addr.s6_addr[n]) + return -1; + return 0; +} +#endif + +/* int match_ipv4(struct irc_ssaddr *, struct irc_ssaddr *, int) + * Input: An IP address, an IP mask, the number of bits in the mask. + * Output: if match, -1 else 0 + * Side Effects: None + */ +int +match_ipv4(const struct irc_ssaddr *addr, const struct irc_ssaddr *mask, int bits) +{ + const struct sockaddr_in *v4 = (const struct sockaddr_in *)addr; + const struct sockaddr_in *v4mask = (const struct sockaddr_in *)mask; + + if ((ntohl(v4->sin_addr.s_addr) & ~((1 << (32 - bits)) - 1)) != + ntohl(v4mask->sin_addr.s_addr)) + return 0; + return -1; +} + +/* + * mask_addr + * + * inputs - pointer to the ip to mask + * - bitlen + * output - NONE + * side effects - + */ +void +mask_addr(struct irc_ssaddr *ip, int bits) +{ + int mask; +#ifdef IPV6 + struct sockaddr_in6 *v6_base_ip; + int i, m, n; +#endif + struct sockaddr_in *v4_base_ip; + +#ifdef IPV6 + if (ip->ss.ss_family != AF_INET6) +#endif + { + v4_base_ip = (struct sockaddr_in *)ip; + + mask = ~((1 << (32 - bits)) - 1); + v4_base_ip->sin_addr.s_addr = htonl(ntohl(v4_base_ip->sin_addr.s_addr) & mask); + } +#ifdef IPV6 + else + { + n = bits / 8; + m = bits % 8; + v6_base_ip = (struct sockaddr_in6 *)ip; + + mask = ~((1 << (8 - m)) -1 ); + v6_base_ip->sin6_addr.s6_addr[n] = v6_base_ip->sin6_addr.s6_addr[n] & mask; + + for (i = n + 1; i < 16; i++) + v6_base_ip->sin6_addr.s6_addr[i] = 0; + } +#endif +} + +/* Hashtable stuff...now external as its used in m_stats.c */ +dlink_list atable[ATABLE_SIZE]; + +void +init_host_hash(void) +{ + memset(&atable, 0, sizeof(atable)); +} + +/* unsigned long hash_ipv4(struct irc_ssaddr*) + * Input: An IP address. + * Output: A hash value of the IP address. + * Side effects: None + */ +static uint32_t +hash_ipv4(struct irc_ssaddr *addr, int bits) +{ + if (bits != 0) + { + struct sockaddr_in *v4 = (struct sockaddr_in *)addr; + uint32_t av = ntohl(v4->sin_addr.s_addr) & ~((1 << (32 - bits)) - 1); + + return (av ^ (av >> 12) ^ (av >> 24)) & (ATABLE_SIZE - 1); + } + + return 0; +} + +/* unsigned long hash_ipv6(struct irc_ssaddr*) + * Input: An IP address. + * Output: A hash value of the IP address. + * Side effects: None + */ +#ifdef IPV6 +static uint32_t +hash_ipv6(struct irc_ssaddr *addr, int bits) +{ + uint32_t v = 0, n; + struct sockaddr_in6 *v6 = (struct sockaddr_in6 *)addr; + + for (n = 0; n < 16; n++) + { + if (bits >= 8) + { + v ^= v6->sin6_addr.s6_addr[n]; + bits -= 8; + } + else if (bits) + { + v ^= v6->sin6_addr.s6_addr[n] & ~((1 << (8 - bits)) - 1); + return v & (ATABLE_SIZE - 1); + } + else + return v & (ATABLE_SIZE - 1); + } + return v & (ATABLE_SIZE - 1); +} +#endif + +/* int hash_text(const char *start) + * Input: The start of the text to hash. + * Output: The hash of the string between 1 and (TH_MAX-1) + * Side-effects: None. + */ +static uint32_t +hash_text(const char *start) +{ + const char *p = start; + uint32_t h = 0; + + for (; *p; ++p) + h = (h << 4) - (h + (unsigned char)ToLower(*p)); + + return h & (ATABLE_SIZE - 1); +} + +/* unsigned long get_hash_mask(const char *) + * Input: The text to hash. + * Output: The hash of the string right of the first '.' past the last + * wildcard in the string. + * Side-effects: None. + */ +static uint32_t +get_mask_hash(const char *text) +{ + const char *hp = "", *p; + + for (p = text + strlen(text) - 1; p >= text; p--) + if (IsMWildChar(*p)) + return hash_text(hp); + else if (*p == '.') + hp = p + 1; + return hash_text(text); +} + +/* struct AccessItem *find_conf_by_address(const char *, struct irc_ssaddr *, + * int type, int fam, const char *username) + * Input: The hostname, the address, the type of mask to find, the address + * family, the username. + * Output: The matching value with the highest precedence. + * Side-effects: None + * Note: Setting bit 0 of the type means that the username is ignored. + * Warning: IsNeedPassword for everything that is not an auth{} entry + * should always be true (i.e. aconf->flags & CONF_FLAGS_NEED_PASSWORD == 0) + */ +struct AccessItem * +find_conf_by_address(const char *name, struct irc_ssaddr *addr, unsigned int type, + int fam, const char *username, const char *password, int do_match) +{ + unsigned int hprecv = 0; + dlink_node *ptr = NULL; + struct AccessItem *hprec = NULL; + struct AddressRec *arec; + int b; + int (*cmpfunc)(const char *, const char *) = do_match ? match : irccmp; + + if (username == NULL) + username = ""; + if (password == NULL) + password = ""; + + if (addr) + { + /* Check for IPV6 matches... */ +#ifdef IPV6 + if (fam == AF_INET6) + { + for (b = 128; b >= 0; b -= 16) + { + DLINK_FOREACH(ptr, atable[hash_ipv6(addr, b)].head) + { + arec = ptr->data; + + if (arec->type == (type & ~0x1) && + arec->precedence > hprecv && + arec->masktype == HM_IPV6 && + match_ipv6(addr, &arec->Mask.ipa.addr, + arec->Mask.ipa.bits) && + (type & 0x1 || cmpfunc(arec->username, username) == do_match) && + (IsNeedPassword(arec->aconf) || arec->aconf->passwd == NULL || + match_conf_password(password, arec->aconf))) + { + hprecv = arec->precedence; + hprec = arec->aconf; + } + } + } + } + else +#endif + if (fam == AF_INET) + { + for (b = 32; b >= 0; b -= 8) + { + DLINK_FOREACH(ptr, atable[hash_ipv4(addr, b)].head) + { + arec = ptr->data; + + if (arec->type == (type & ~0x1) && + arec->precedence > hprecv && + arec->masktype == HM_IPV4 && + match_ipv4(addr, &arec->Mask.ipa.addr, + arec->Mask.ipa.bits) && + (type & 0x1 || cmpfunc(arec->username, username) == do_match) && + (IsNeedPassword(arec->aconf) || arec->aconf->passwd == NULL || + match_conf_password(password, arec->aconf))) + { + hprecv = arec->precedence; + hprec = arec->aconf; + } + } + } + } + } + + if (name != NULL) + { + const char *p = name; + + while (1) + { + DLINK_FOREACH(ptr, atable[hash_text(p)].head) + { + arec = ptr->data; + if ((arec->type == (type & ~0x1)) && + arec->precedence > hprecv && + (arec->masktype == HM_HOST) && + cmpfunc(arec->Mask.hostname, name) == do_match && + (type & 0x1 || cmpfunc(arec->username, username) == do_match) && + (IsNeedPassword(arec->aconf) || arec->aconf->passwd == NULL || + match_conf_password(password, arec->aconf))) + { + hprecv = arec->precedence; + hprec = arec->aconf; + } + } + p = strchr(p, '.'); + if (p == NULL) + break; + p++; + } + + DLINK_FOREACH(ptr, atable[0].head) + { + arec = ptr->data; + + if (arec->type == (type & ~0x1) && + arec->precedence > hprecv && + arec->masktype == HM_HOST && + cmpfunc(arec->Mask.hostname, name) == do_match && + (type & 0x1 || cmpfunc(arec->username, username) == do_match) && + (IsNeedPassword(arec->aconf) || arec->aconf->passwd == NULL || + match_conf_password(password, arec->aconf))) + { + hprecv = arec->precedence; + hprec = arec->aconf; + } + } + } + + return hprec; +} + +/* struct AccessItem* find_address_conf(const char*, const char*, + * struct irc_ssaddr*, int, char *); + * Input: The hostname, username, address, address family. + * Output: The applicable AccessItem. + * Side-effects: None + */ +struct AccessItem * +find_address_conf(const char *host, const char *user, + struct irc_ssaddr *ip, int aftype, char *password) +{ + struct AccessItem *iconf, *kconf; + + /* Find the best auth{} block... If none, return NULL -A1kmm */ + if ((iconf = find_conf_by_address(host, ip, CONF_CLIENT, aftype, user, + password, 1)) == NULL) + return NULL; + + /* If they are exempt from K-lines, return the best auth{} block. -A1kmm */ + if (IsConfExemptKline(iconf)) + return iconf; + + /* Find the best K-line... -A1kmm */ + kconf = find_conf_by_address(host, ip, CONF_KLINE, aftype, user, NULL, 1); + + /* + * If they are K-lined, return the K-line. Otherwise, return the + * auth{} block. -A1kmm + */ + if (kconf != NULL) + return kconf; + + kconf = find_conf_by_address(host, ip, CONF_GLINE, aftype, user, NULL, 1); + if (kconf != NULL && !IsConfExemptGline(iconf)) + return kconf; + + return iconf; +} + +/* struct AccessItem* find_dline_conf(struct irc_ssaddr*, int) + * + * Input: An address, an address family. + * Output: The best matching D-line or exempt line. + * Side effects: None. + */ +struct AccessItem * +find_dline_conf(struct irc_ssaddr *addr, int aftype) +{ + struct AccessItem *eline; + + eline = find_conf_by_address(NULL, addr, CONF_EXEMPTDLINE | 1, aftype, + NULL, NULL, 1); + if (eline != NULL) + return eline; + + return find_conf_by_address(NULL, addr, CONF_DLINE | 1, aftype, NULL, NULL, 1); +} + +/* void add_conf_by_address(int, struct AccessItem *aconf) + * Input: + * Output: None + * Side-effects: Adds this entry to the hash table. + */ +void +add_conf_by_address(const unsigned int type, struct AccessItem *aconf) +{ + const char *address; + const char *username; + static unsigned int prec_value = 0xFFFFFFFF; + int bits = 0; + struct AddressRec *arec; + + address = aconf->host; + username = aconf->user; + + assert(type != 0); + assert(aconf != NULL); + + if (EmptyString(address)) + address = "/NOMATCH!/"; + + arec = MyMalloc(sizeof(struct AddressRec)); + arec->masktype = parse_netmask(address, &arec->Mask.ipa.addr, &bits); + arec->Mask.ipa.bits = bits; + arec->username = username; + arec->aconf = aconf; + arec->precedence = prec_value--; + arec->type = type; + + switch (arec->masktype) + { + case HM_IPV4: + /* We have to do this, since we do not re-hash for every bit -A1kmm. */ + bits -= bits % 8; + dlinkAdd(arec, &arec->node, &atable[hash_ipv4(&arec->Mask.ipa.addr, bits)]); + break; +#ifdef IPV6 + case HM_IPV6: + /* We have to do this, since we do not re-hash for every bit -A1kmm. */ + bits -= bits % 16; + dlinkAdd(arec, &arec->node, &atable[hash_ipv6(&arec->Mask.ipa.addr, bits)]); + break; +#endif + default: /* HM_HOST */ + arec->Mask.hostname = address; + dlinkAdd(arec, &arec->node, &atable[get_mask_hash(address)]); + break; + } +} + +/* void delete_one_address(const char*, struct AccessItem*) + * Input: An address string, the associated AccessItem. + * Output: None + * Side effects: Deletes an address record. Frees the AccessItem if there + * is nothing referencing it, sets it as illegal otherwise. + */ +void +delete_one_address_conf(const char *address, struct AccessItem *aconf) +{ + int bits = 0; + uint32_t hv = 0; + dlink_node *ptr = NULL, *ptr_next = NULL; + struct irc_ssaddr addr; + + switch (parse_netmask(address, &addr, &bits)) + { + case HM_IPV4: + /* We have to do this, since we do not re-hash for every bit -A1kmm. */ + bits -= bits % 8; + hv = hash_ipv4(&addr, bits); + break; +#ifdef IPV6 + case HM_IPV6: + /* We have to do this, since we do not re-hash for every bit -A1kmm. */ + bits -= bits % 16; + hv = hash_ipv6(&addr, bits); + break; +#endif + default: /* HM_HOST */ + hv = get_mask_hash(address); + break; + } + + DLINK_FOREACH_SAFE(ptr, ptr_next, atable[hv].head) + { + struct AddressRec *arec = ptr->data; + + if (arec->aconf == aconf) + { + dlinkDelete(&arec->node, &atable[hv]); + aconf->status |= CONF_ILLEGAL; + + if (!aconf->clients) + free_access_item(aconf); + + MyFree(arec); + return; + } + } +} + +/* void clear_out_address_conf(void) + * Input: None + * Output: None + * Side effects: Clears out all address records in the hash table, + * frees them, and frees the AccessItems if nothing references + * them, otherwise sets them as illegal. + */ +void +clear_out_address_conf(void) +{ + unsigned int i = 0; + dlink_node *ptr = NULL, *ptr_next = NULL; + + for (i = 0; i < ATABLE_SIZE; ++i) + { + DLINK_FOREACH_SAFE(ptr, ptr_next, atable[i].head) + { + struct AddressRec *arec = ptr->data; + + /* We keep the temporary K-lines and destroy the + * permanent ones, just to be confusing :) -A1kmm + */ + if (!(arec->aconf->flags & CONF_FLAGS_TEMPORARY)) + { + dlinkDelete(&arec->node, &atable[i]); + /* unlink it from link list - Dianora */ + arec->aconf->status |= CONF_ILLEGAL; + + if (!arec->aconf->clients) + free_access_item(arec->aconf); + MyFree(arec); + } + } + } +} + +static void +hostmask_send_expiration(struct AddressRec *arec) +{ + char ban_type = '\0'; + + if (!ConfigFileEntry.tkline_expire_notices) + return; + + switch (arec->type) + { + case CONF_KLINE: + ban_type = 'K'; + break; + case CONF_DLINE: + ban_type = 'D'; + break; + case CONF_GLINE: + ban_type = 'G'; + break; + } + + sendto_realops_flags(UMODE_ALL, L_ALL, + "Temporary %c-line for [%s@%s] expired", ban_type, + (arec->aconf->user) ? arec->aconf->user : "*", + (arec->aconf->host) ? arec->aconf->host : "*"); +} + +void +hostmask_expire_temporary(void) +{ + unsigned int i = 0; + dlink_node *ptr = NULL, *ptr_next = NULL; + + for (i = 0; i < ATABLE_SIZE; ++i) + { + DLINK_FOREACH_SAFE(ptr, ptr_next, atable[i].head) + { + struct AddressRec *arec = ptr->data; + + if (!IsConfTemporary(arec->aconf) || arec->aconf->hold > CurrentTime) + continue; + + switch (arec->type) + { + case CONF_KLINE: + case CONF_DLINE: + case CONF_GLINE: + hostmask_send_expiration(arec); + + dlinkDelete(&arec->node, &atable[i]); + free_access_item(arec->aconf); + MyFree(arec); + break; + } + } + } +} |