| Current Path : /home/mudbot/eggdrop/src/ |
| Current File : //home/mudbot/eggdrop/src/match.c |
/*
* match.c
* wildcard matching functions
* hostmask matching
* cidr matching
*
* Once this code was working, I added support for % so that I could
* use the same code both in Eggdrop and in my IrcII client.
* Pleased with this, I added the option of a fourth wildcard, ~,
* which matches varying amounts of whitespace (at LEAST one space,
* though, for sanity reasons).
*
* This code would not have been possible without the prior work and
* suggestions of various sourced. Special thanks to Robey for
* all his time/help tracking down bugs and his ever-helpful advice.
*
* 04/09: Fixed the "*\*" against "*a" bug (caused an endless loop)
*
* Chris Fuller (aka Fred1@IRC & Fwitz@IRC)
* crf@cfox.bchs.uh.edu
*
* I hereby release this code into the public domain
*
*/
#include "main.h"
#define QUOTE '\\' /* quoting character (overrides wildcards) */
#define WILDS '*' /* matches 0 or more characters (including spaces) */
#define WILDP '%' /* matches 0 or more non-space characters */
#define WILDQ '?' /* matches exactly one character */
#define WILDT '~' /* matches 1 or more spaces */
#define NOMATCH 0
#define MATCH (match+sofar)
#define PERMATCH (match+saved+sofar)
int cidr_support = 0;
int casecharcmp(unsigned char a, unsigned char b)
{
return (rfc_toupper(a) - rfc_toupper(b));
}
int charcmp(unsigned char a, unsigned char b)
{
return (a - b);
}
/* Wildcard-matches mask m to n. Uses the comparison function cmp1. If chgpoint
* isn't NULL, it points at the first character in n where we start using cmp2.
*/
int _wild_match_per(unsigned char *m, unsigned char *n,
int (*cmp1)(unsigned char, unsigned char),
int (*cmp2)(unsigned char, unsigned char),
unsigned char *chgpoint)
{
unsigned char *ma = m, *lsm = 0, *lsn = 0, *lpm = 0, *lpn = 0;
int match = 1, saved = 0, space;
unsigned int sofar = 0;
/* null strings should never match */
if ((m == 0) || (n == 0) || (!*n) || (!cmp1))
return NOMATCH;
if (!cmp2) /* Don't change cmpfunc if it's not valid */
chgpoint = NULL;
while (*n) {
if (*m == WILDT) { /* Match >=1 space */
space = 0; /* Don't need any spaces */
do {
m++;
space++;
} /* Tally 1 more space ... */
while ((*m == WILDT) || (*m == ' ')); /* for each space or ~ */
sofar += space; /* Each counts as exact */
while (*n == ' ') {
n++;
space--;
} /* Do we have enough? */
if (space <= 0)
continue; /* Had enough spaces! */
}
/* Do the fallback */
else {
switch (*m) {
case 0:
do
m--; /* Search backwards */
while ((m > ma) && (*m == '?')); /* For first non-? char */
if ((m > ma) ? ((*m == '*') && (m[-1] != QUOTE)) : (*m == '*'))
return PERMATCH; /* nonquoted * = match */
break;
case WILDP:
while (*(++m) == WILDP); /* Zap redundant %s */
if (*m != WILDS) { /* Don't both if next=* */
if (*n != ' ') { /* WILDS can't match ' ' */
lpm = m;
lpn = n; /* Save '%' fallback spot */
saved += sofar;
sofar = 0; /* And save tally count */
}
continue; /* Done with '%' */
}
/* FALL THROUGH */
case WILDS:
do
m++; /* Zap redundant wilds */
while ((*m == WILDS) || (*m == WILDP));
lsm = m;
lsn = n;
lpm = 0; /* Save '*' fallback spot */
match += (saved + sofar); /* Save tally count */
saved = sofar = 0;
continue; /* Done with '*' */
case WILDQ:
m++;
n++;
continue; /* Match one char */
case QUOTE:
m++; /* Handle quoting */
}
if (((!chgpoint || n < chgpoint) && !(*cmp1)(*m, *n)) ||
(chgpoint && n >= chgpoint && !(*cmp2)(*m, *n))) { /* If matching */
m++;
n++;
sofar++;
continue; /* Tally the match */
}
#ifdef WILDT
}
#endif
if (lpm) { /* Try to fallback on '%' */
n = ++lpn;
m = lpm;
sofar = 0; /* Restore position */
if ((*n | 32) == 32)
lpm = 0; /* Can't match 0 or ' ' */
continue; /* Next char, please */
}
if (lsm) { /* Try to fallback on '*' */
n = ++lsn;
m = lsm; /* Restore position */
saved = sofar = 0;
continue; /* Next char, please */
}
return NOMATCH; /* No fallbacks=No match */
}
while ((*m == WILDS) || (*m == WILDP))
m++; /* Zap leftover %s & *s */
return (*m) ? NOMATCH : PERMATCH; /* End of both = match */
}
/* Generic string matching, use addr_match() for hostmasks! */
int _wild_match(unsigned char *m, unsigned char *n)
{
unsigned char *ma = m, *na = n, *lsm = 0, *lsn = 0;
int match = 1;
int sofar = 0;
/* null strings should never match */
if ((ma == 0) || (na == 0) || (!*ma) || (!*na))
return NOMATCH;
/* find the end of each string */
while (*(++m));
m--;
while (*(++n));
n--;
while (n >= na) {
/* If the mask runs out of chars before the string, fall back on
* a wildcard or fail. */
if (m < ma) {
if (lsm) {
n = --lsn;
m = lsm;
if (n < na)
lsm = 0;
sofar = 0;
}
else
return NOMATCH;
}
switch (*m) {
case WILDS: /* Matches anything */
do
m--; /* Zap redundant wilds */
while ((m >= ma) && (*m == WILDS));
lsm = m;
lsn = n;
match += sofar;
sofar = 0; /* Update fallback pos */
if (m < ma)
return MATCH;
continue; /* Next char, please */
case WILDQ:
m--;
n--;
continue; /* '?' always matches */
}
if (toupper(*m) == toupper(*n)) { /* If matching char */
m--;
n--;
sofar++; /* Tally the match */
continue; /* Next char, please */
}
if (lsm) { /* To to fallback on '*' */
n = --lsn;
m = lsm;
if (n < na)
lsm = 0; /* Rewind to saved pos */
sofar = 0;
continue; /* Next char, please */
}
return NOMATCH; /* No fallback=No match */
}
while ((m >= ma) && (*m == WILDS))
m--; /* Zap leftover %s & *s */
return (m >= ma) ? NOMATCH : MATCH; /* Start of both = match */
}
/* cidr and RFC1459 compatible host matching
* Returns: 1 if the address in n matches the hostmask in m.
* If cmp != 0, m and n will be compared as masks. Returns 1
* if m is broader, 0 otherwise.
* If user != 0, the masks are eggdrop user hosts and should
* be matched regardless of the cidr_support variable.
* This is required as userhost matching shouldn't depend on
* server support of cidr.
*/
int addr_match(char *m, char *n, int user, int cmp)
{
char *p, *q, *r = 0, *s = 0;
char mu[UHOSTLEN], nu[UHOSTLEN];
int tmpscore, score = 0;
/* copy the strings into our own buffers
and convert to rfc uppercase */
for (p = mu; *m && (p - mu < UHOSTLEN - 1); m++) {
if (*m == '@')
r = p;
*p++ = rfc_toupper(*m);
}
for (q = nu; *n && (q - nu < UHOSTLEN - 1); n++) {
if (*n == '@')
s = q;
*q++ = rfc_toupper(*n);
}
*p = *q = 0;
if ((!user && !cidr_support) || !r || !s)
return wild_match(mu, nu);
*r++ = *s++ = 0;
if (!(tmpscore = wild_match(mu, nu)))
return NOMATCH; /* nick!ident parts don't match */
score += tmpscore;
if (!*r && !*s)
return score; /* end of nonempty strings */
/* check for CIDR notation and perform
generic string matching if not found */
if (!(p = strrchr(r, '/')) || !str_isdigit(p + 1)) {
tmpscore = wild_match(r, s);
if (!tmpscore)
return NOMATCH;
score += tmpscore;
return score;
}
/* if the two strings are both cidr masks,
use the broader prefix */
if (cmp && (q = strrchr(s, '/')) && str_isdigit(q + 1)) {
if (atoi(p + 1) > atoi(q + 1))
return NOMATCH;
*q = 0;
}
*p = 0;
/* looks like a cidr mask */
if (!(tmpscore = cidr_match(r, s, atoi(p + 1))))
return NOMATCH;
score += tmpscore;
return score;
}
/* Checks for overlapping masks
* Returns: > 0 if the two masks in m and n overlap, 0 otherwise.
*/
int mask_match(char *m, char *n)
{
int prefix;
char *p, *q, *r = 0, *s = 0;
char mu[UHOSTLEN], nu[UHOSTLEN];
for (p = mu; *m && (p - mu < UHOSTLEN - 1); m++) {
if (*m == '@')
r = p;
*p++ = rfc_toupper(*m);
}
for (q = nu; *n && (q - nu < UHOSTLEN - 1); n++) {
if (*n == '@')
s = q;
*q++ = rfc_toupper(*n);
}
*p = *q = 0;
if (!cidr_support || !r || !s)
return (wild_match(mu, nu) || wild_match(nu, mu));
*r++ = *s++ = 0;
if (!wild_match(mu, nu) && !wild_match(nu, mu))
return 0;
if (!*r && !*s)
return 1;
p = strrchr(r, '/');
q = strrchr(s, '/');
if ((!p || !str_isdigit(p + 1)) && (!q || !str_isdigit(q + 1)))
return (wild_match(r, s) || wild_match(s, r));
if (p) {
*p = 0;
prefix = atoi(p + 1);
} else
prefix = (strchr(r, ':') ? 128 : 32);
if (q) {
*q = 0;
if (atoi(q + 1) < prefix)
prefix = atoi(q + 1);
}
return cidr_match(r, s, prefix);
}
/* Performs bitwise comparison of two IP addresses stored in presentation
* (string) format. IPs are first internally converted to binary form.
* Returns: 1 if the first count bits are equal, 0 otherwise.
*/
int cidr_match(char *m, char *n, int count)
{
#ifdef IPV6
int c, af = AF_INET, rest;
uint8_t block[16], addr[16];
if (strchr(m, ':') || strchr(n, ':')) {
af = AF_INET6;
if (count > 128)
return NOMATCH;
} else if (count > 32)
return NOMATCH;
if (inet_pton(af, m, &block) != 1 ||
inet_pton(af, n, &addr) != 1)
return NOMATCH;
if (count < 1)
return 1;
for (c = 0; c < (count / 8); c++)
if (block[c] != addr[c])
return NOMATCH;
if (!(count % 8))
return count;
rest = 8 - (count % 8);
/* Normalize returned score to max. 32 for a /128 ipv6, 32 for ipv4 */
return ((block[c] >> rest) == (addr[c] >> rest)) ? (count / (af == AF_INET6 ? 4 : 1)): 0;
#else
int rest;
IP block, addr;
if (count > 32)
return NOMATCH;
block = ntohl(inet_addr(m));
addr = ntohl(inet_addr(n));
if (block == INADDR_NONE || addr == INADDR_NONE)
return NOMATCH;
if (count < 1)
return 1;
rest = 32 - count;
return ((block >> rest) == (addr >> rest)) ? count : 0;
#endif
}
/* Inline for cron_match (obviously).
* Matches a single field of a crontab expression.
*/
static int cron_matchfld(char *mask, int match)
{
int skip = 0, f, t;
char *p, *q;
for (; mask && *mask; mask = p) {
/* loop through a list of values, if such is given */
if ((p = strchr(mask, ',')))
*p++ = 0;
/* check for the step operator */
if ((q = strchr(mask, '/'))) {
if (q == mask)
continue;
*q++ = 0;
skip = atoi(q);
}
if (!strcmp(mask, "*") && (!skip || !(match % skip)))
return 1;
/* ranges, e.g 10-20 */
if (strchr(mask, '-')) {
if (sscanf(mask, "%d-%d", &f, &t) != 2)
continue;
if (t < f) {
if (match <= t)
match += 60;
t += 60;
}
if ((match >= f && match <= t) &&
(!skip || !((match - f) % skip)))
return 1;
}
/* no operator found, should be exact match */
f = strtol(mask, &q, 10);
if ((q > mask) &&
(skip ? !((match - f) % skip) : (match == f)))
return 1;
}
return 0;
}
/* Check if the current time matches a crontab-like specification.
*
* mask contains a cron-style series of time fields. The following
* crontab operators are supported: ranges '-', asterisks '*',
* lists ',' and steps '/'.
* match must have 5 space separated integers representing in order
* the current minute, hour, day of month, month and weekday.
* It should look like this: "53 17 01 03 06", which means
* Sunday 01 March, 17:53.
*/
int cron_match(const char *mask, const char *match)
{
int d = 0, i, m = 1, t[5];
char *p, *q, *buf;
if (!mask[0])
return 0;
if (sscanf(match, "%d %d %d %d %d",
&t[0], &t[1], &t[2], &t[3], &t[4]) < 5)
return 0;
buf = nmalloc(strlen(mask) + 1);
strcpy(buf, mask);
for (p = buf, i = 0; *p && i < 5; i++) {
q = newsplit(&p);
if (!strcmp(q, "*"))
continue;
m = (cron_matchfld(q, t[i]) ||
(i == 4 && !t[i] && cron_matchfld(q, 7)));
if (i == 2)
d = m;
else if (!m || (i == 3 && d))
break;
}
nfree(buf);
return m;
}