/************************************************************************ * IRC - Internet Relay Chat, src/match.c * Copyright (C) 1990 Jarkko Oikarinen * * 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA. * */ /* * Compare if a given string (name) matches the given * mask (which can contain wild cards: '*' - match any * number of chars, '?' - match any single character. * * return 1, if match * 0, if no match * * Originally by Douglas A Lewis (dalewis@acsu.buffalo.edu) * Rewritten by Timothy Vogelsang (netski), net@astrolink.org */ /** Check a string against a mask. * This test checks using traditional IRC wildcards only: '*' means * match zero or more characters of any type; '?' means match exactly * one character of any type. * * @param[in] mask Wildcard-containing mask. * @param[in] name String to check against \a mask. * @return Zero if \a mask matches \a name, non-zero if no match. */ bool ircd::match(const char *const &mask, const char *const &name) { const char *m = mask, *n = name; const char *m_tmp = mask, *n_tmp = name; int star_p; s_assert(mask != NULL); s_assert(name != NULL); for (;;) { switch (*m) { case '\0': if (!*n) return 1; backtrack: if (m_tmp == mask) return 0; m = m_tmp; n = ++n_tmp; break; case '*': case '?': for (star_p = 0;; m++) { if (*m == '*') star_p = 1; else if (*m == '?') { if (!*n++) goto backtrack; } else break; } if (star_p) { if (!*m) return 1; else { m_tmp = m; for (n_tmp = n; *n && rfc1459::tolower(*n) != rfc1459::tolower(*m); n++); } } /* and fall through */ default: if (!*n) return (*m != '\0' ? 0 : 1); if (rfc1459::tolower(*m) != rfc1459::tolower(*n)) goto backtrack; m++; n++; break; } } } bool ircd::match(const std::string &mask, const std::string &name) { return match(mask.c_str(), name.c_str()); } /** Check a mask against a mask. * This test checks using traditional IRC wildcards only: '*' means * match zero or more characters of any type; '?' means match exactly * one character of any type. * The difference between match_mask() and match() is that in match_mask() * a '?' in mask does not match a '*' in name. * * @param[in] mask Existing wildcard-containing mask. * @param[in] name New wildcard-containing mask. * @return 1 if \a name is equal to or more specific than \a mask, 0 otherwise. */ bool ircd::match_mask(const char *const &mask, const char *const &name) { const char *m = mask, *n = name; const char *m_tmp = mask, *n_tmp = name; int star_p; s_assert(mask != NULL); s_assert(name != NULL); for (;;) { switch (*m) { case '\0': if (!*n) return 1; backtrack: if (m_tmp == mask) return 0; m = m_tmp; n = ++n_tmp; break; case '*': case '?': for (star_p = 0;; m++) { if (*m == '*') star_p = 1; else if (*m == '?') { /* changed for match_mask() */ if (*n == '*' || !*n) goto backtrack; n++; } else break; } if (star_p) { if (!*m) return 1; else { m_tmp = m; for (n_tmp = n; *n && rfc1459::tolower(*n) != rfc1459::tolower(*m); n++); } } /* and fall through */ default: if (!*n) return (*m != '\0' ? 0 : 1); if (rfc1459::tolower(*m) != rfc1459::tolower(*n)) goto backtrack; m++; n++; break; } } } bool ircd::match_mask(const std::string &mask, const std::string &name) { return match_mask(mask.c_str(), name.c_str()); } // ACK! This dies when it's less that this and we have long lines to parse #define MATCH_MAX_CALLS 512 /** Check a string against a mask. * This test checks using extended wildcards: '*' means match zero * or more characters of any type; '?' means match exactly one * character of any type; '#' means match exactly one character that * is a number; '@' means match exactly one character that is a * letter; '\s' means match a space. * * This function supports escaping, so that a wildcard may be matched * exactly. * * @param[in] mask Wildcard-containing mask. * @param[in] name String to check against \a mask. * @return Zero if \a mask matches \a name, non-zero if no match. */ int ircd::match_esc(const char *const &mask, const char *const &name) { const unsigned char *m = (const unsigned char *)mask; const unsigned char *n = (const unsigned char *)name; const unsigned char *ma = (const unsigned char *)mask; const unsigned char *na = (const unsigned char *)name; int wild = 0; int calls = 0; int quote = 0; int match1 = 0; s_assert(mask != NULL); s_assert(name != NULL); if(!mask || !name) return 0; /* if the mask is "*", it matches everything */ if((*m == '*') && (*(m + 1) == '\0')) return 1; while(calls++ < MATCH_MAX_CALLS) { if(quote) quote++; if(quote == 3) quote = 0; if(*m == '\\' && !quote) { m++; quote = 1; continue; } if(!quote && *m == '*') { /* * XXX - shouldn't need to spin here, the mask should have been * collapsed before match is called */ while(*m == '*') m++; wild = 1; ma = m; na = n; if(*m == '\\') { m++; /* This means it is an invalid mask -A1kmm. */ if(!*m) return 0; quote++; continue; } } if(!*m) { if(!*n) return 1; if(quote) return 0; for(m--; (m > (const unsigned char *)mask) && (*m == '?'); m--) ; if(*m == '*' && (m > (const unsigned char *)mask)) return 1; if(!wild) return 0; m = ma; n = ++na; } else if(!*n) { /* * XXX - shouldn't need to spin here, the mask should have been * collapsed before match is called */ if(quote) return 0; while(*m == '*') m++; return (*m == 0); } if(quote) match1 = *m == 's' ? *n == ' ' : rfc1459::tolower(*m) == rfc1459::tolower(*n); else if(*m == '?') match1 = 1; else if(*m == '@') match1 = rfc1459::is_letter(*n); else if(*m == '#') match1 = rfc1459::is_digit(*n); else match1 = rfc1459::tolower(*m) == rfc1459::tolower(*n); if(match1) { if(*m) m++; if(*n) n++; } else { if(!wild) return 0; m = ma; n = ++na; } } return 0; } int ircd::match_esc(const std::string &mask, const std::string &name) { return match_esc(mask.c_str(), name.c_str()); } /* * match_ips() * * Input - cidr ip mask, address */ bool ircd::match_ips(const char *const &s1, const char *const &s2) { struct rb_sockaddr_storage ipaddr, maskaddr; char mask[BUFSIZE]; char address[HOSTLEN + 1]; char *len; void *ipptr, *maskptr; int cidrlen, aftype; rb_strlcpy(mask, s1, sizeof(mask)); rb_strlcpy(address, s2, sizeof(address)); len = strrchr(mask, '/'); if (len == NULL) return 0; *len++ = '\0'; cidrlen = atoi(len); if (cidrlen <= 0) return 0; #ifdef RB_IPV6 if (strchr(mask, ':') && strchr(address, ':')) { if (cidrlen > 128) return 0; aftype = AF_INET6; ipptr = &((struct sockaddr_in6 *)&ipaddr)->sin6_addr; maskptr = &((struct sockaddr_in6 *)&maskaddr)->sin6_addr; } else #endif if (!strchr(mask, ':') && !strchr(address, ':')) { if (cidrlen > 32) return 0; aftype = AF_INET; ipptr = &((struct sockaddr_in *)&ipaddr)->sin_addr; maskptr = &((struct sockaddr_in *)&maskaddr)->sin_addr; } else return 0; if (rb_inet_pton(aftype, address, ipptr) <= 0) return 0; if (rb_inet_pton(aftype, mask, maskptr) <= 0) return 0; if (comp_with_mask(ipptr, maskptr, cidrlen)) return 1; else return 0; } bool ircd::match_ips(const std::string &s1, const std::string &s2) { return match_ips(s1.c_str(), s2.c_str()); } /* match_cidr() * * Input - mask, address * Ouput - 1 = Matched 0 = Did not match */ bool ircd::match_cidr(const char *const &s1, const char *const &s2) { struct rb_sockaddr_storage ipaddr, maskaddr; char mask[BUFSIZE]; char address[NICKLEN + USERLEN + HOSTLEN + 6]; char *ipmask; char *ip; char *len; void *ipptr, *maskptr; int cidrlen, aftype; rb_strlcpy(mask, s1, sizeof(mask)); rb_strlcpy(address, s2, sizeof(address)); ipmask = strrchr(mask, '@'); if (ipmask == NULL) return 0; *ipmask++ = '\0'; ip = strrchr(address, '@'); if (ip == NULL) return 0; *ip++ = '\0'; len = strrchr(ipmask, '/'); if (len == NULL) return 0; *len++ = '\0'; cidrlen = atoi(len); if (cidrlen <= 0) return 0; #ifdef RB_IPV6 if (strchr(ip, ':') && strchr(ipmask, ':')) { if (cidrlen > 128) return 0; aftype = AF_INET6; ipptr = &((struct sockaddr_in6 *)&ipaddr)->sin6_addr; maskptr = &((struct sockaddr_in6 *)&maskaddr)->sin6_addr; } else #endif if (!strchr(ip, ':') && !strchr(ipmask, ':')) { if (cidrlen > 32) return 0; aftype = AF_INET; ipptr = &((struct sockaddr_in *)&ipaddr)->sin_addr; maskptr = &((struct sockaddr_in *)&maskaddr)->sin_addr; } else return 0; if (rb_inet_pton(aftype, ip, ipptr) <= 0) return 0; if (rb_inet_pton(aftype, ipmask, maskptr) <= 0) return 0; if (comp_with_mask(ipptr, maskptr, cidrlen) && match(mask, address)) return 1; else return 0; } bool ircd::match_cidr(const std::string &s1, const std::string &s2) { return match_cidr(s1.c_str(), s2.c_str()); } int ircd::comp_with_mask(void *addr, void *dest, unsigned int mask) { if (memcmp(addr, dest, mask / 8) == 0) { int n = mask / 8; int m = ((-1) << (8 - (mask % 8))); if (mask % 8 == 0 || (((unsigned char *) addr)[n] & m) == (((unsigned char *) dest)[n] & m)) { return (1); } } return (0); } int ircd::comp_with_mask_sock(struct sockaddr *addr, struct sockaddr *dest, unsigned int mask) { void *iaddr = NULL; void *idest = NULL; if (addr->sa_family == AF_INET) { iaddr = &((struct sockaddr_in *)(void *)addr)->sin_addr; idest = &((struct sockaddr_in *)(void *)dest)->sin_addr; } #ifdef RB_IPV6 else { iaddr = &((struct sockaddr_in6 *)(void *)addr)->sin6_addr; idest = &((struct sockaddr_in6 *)(void *)dest)->sin6_addr; } #endif return (comp_with_mask(iaddr, idest, mask)); } /* collapse() * * collapses a string containing multiple *'s. */ char * ircd::collapse(char *const &pattern) { char *p = pattern, *po = pattern; char c; int f = 0; if (p == NULL) return NULL; while ((c = *p++)) { if (c == '*') { if (!(f & 1)) *po++ = '*'; f |= 1; } else { *po++ = c; f &= ~1; } } *po++ = 0; return pattern; } /* collapse_esc() * * The collapse() function with support for escaping characters */ char * ircd::collapse_esc(char *const &pattern) { char *p = pattern, *po = pattern; char c; int f = 0; if (p == NULL) return NULL; while ((c = *p++)) { if (!(f & 2) && c == '*') { if (!(f & 1)) *po++ = '*'; f |= 1; } else if (!(f & 2) && c == '\\') { *po++ = '\\'; f |= 2; } else { *po++ = c; f &= ~3; } } *po++ = 0; return pattern; } /* * irccmp - case insensitive comparison of two 0 terminated strings. * * returns 0, if s1 equal to s2 * <0, if s1 lexicographically less than s2 * >0, if s1 lexicographically greater than s2 */ int ircd::irccmp(const char *const &s1, const char *const &s2) { const unsigned char *str1 = (const unsigned char *)s1; const unsigned char *str2 = (const unsigned char *)s2; int res; s_assert(s1 != NULL); s_assert(s2 != NULL); while ((res = rfc1459::toupper(*str1) - rfc1459::toupper(*str2)) == 0) { if (*str1 == '\0') return 0; str1++; str2++; } return (res); } int ircd::irccmp(const std::string &s1, const std::string &s2) { return irccmp(s1.c_str(), s2.c_str()); } int ircd::ircncmp(const char *const &s1, const char *const &s2, size_t n) { const unsigned char *str1 = (const unsigned char *)s1; const unsigned char *str2 = (const unsigned char *)s2; int res; s_assert(s1 != NULL); s_assert(s2 != NULL); while ((res = rfc1459::toupper(*str1) - rfc1459::toupper(*str2)) == 0) { str1++; str2++; n--; if (n == 0 || (*str1 == '\0' && *str2 == '\0')) return 0; } return (res); }