* 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.
- *
- * $Id$
+ */
+/** @file
+ * @brief Functions to match strings against IRC mask strings.
+ * @version $Id$
*/
#include "config.h"
#include "match.h"
#include "ircd_chattr.h"
+#include "ircd_string.h"
+#include "ircd_snprintf.h"
+
/*
* mmatch()
*
* And last but not least, '\?' and '\*' in `new_mask' now become one character.
*/
+/** Compares one mask against another.
+ * One wildcard mask may be said to be a superset of another if the
+ * set of strings matched by the first is a proper superset of the set
+ * of strings matched by the second. In practical terms, this means
+ * that the second is made redundant by the first.
+ *
+ * The logic for this test is similar to that in match(), but a
+ * backslash in old_mask only matches a backslash in new_mask (and
+ * requires the next character to match exactly), and -- after
+ * contiguous runs of wildcards are logically collapsed -- a '?' in
+ * old_mask does not match a '*' in new_mask.
+ *
+ * @param[in] old_mask One wildcard mask.
+ * @param[in] new_mask Another wildcard mask.
+ * @return Zero if \a old_mask is a superset of \a new_mask, non-zero otherwise.
+ */
int mmatch(const char *old_mask, const char *new_mask)
{
const char *m = old_mask;
* 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. A backslash escapes the next character
+ * 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 match(const char *mask, const char *name)
{
const char *m = mask, *n = name;
wild = 1;
}
if (*m == '\\') /* next wildcard is disregarded */
- *m++;
+ m++;
if (!*m) {
if (!*n)
* Note that this new optimized alghoritm can *only* work in place.
*/
+/** Collapse a mask string to remove redundancies.
+ * Specifically, it replaces a sequence of '*' followed by additional
+ * '*' or '?' with the same number of '?'s as the input, followed by
+ * one '*'. This minimizes useless backtracking when matching later.
+ * @param[in,out] mask Mask string to collapse.
+ * @return Pointer to the start of the string.
+ */
char *collapse(char *mask)
{
int star = 0;
***************** Nemesi's matchcomp() / matchexec() **************
*/
-/* These functions allow the use of "compiled" masks, you compile a mask
+/** @page compiledmasks Compiled Masks
+ * These functions allow the use of "compiled" masks, you compile a mask
* by means of matchcomp() that gets the plain text mask as input and writes
* its result in the memory locations addressed by the 3 parameters:
* - *cmask will contain the text of the compiled mask
* or when you expect to use mmexec() instead of mmatch() 3 times.
*/
- /*
- * matchcomp()
- *
- * Compiles a mask into a form suitable for using in matchexec().
- */
-
+/** Compile a mask for faster matching.
+ * See also @ref compiledmasks.
+ * @param[out] cmask Output buffer for compiled mask.
+ * @param[out] minlen Minimum length of matching strings.
+ * @param[out] charset Character attributes used in compiled mask.
+ * @param[out] mask Input mask.
+ * @return Length of compiled mask, not including NUL terminator.
+ */
int matchcomp(char *cmask, int *minlen, int *charset, const char *mask)
{
const char *m = mask;
}
-/*
- * matchexec()
- *
- * Executes a match with a mask previosuly compiled with matchcomp()
- * Note 1: If the mask isn't correctly produced by matchcomp() I will core
- * Note 2: 'min' MUST be the value returned by matchcomp on that mask,
- * or.... I will core even faster :-)
- * Note 3: This piece of code is not intended to be nice but efficient.
+/** Compare a string to a compiled mask.
+ * If \a cmask is not from matchcomp(), or if \a minlen is not the value
+ * passed out of matchcomp(), this may core.
+ * See also @ref compiledmasks.
+ * @param[in] string String to test.
+ * @param[in] cmask Compiled mask string.
+ * @param[in] minlen Minimum length of strings that match \a cmask.
+ * @return Zero if the string matches, non-zero otherwise.
*/
-
int matchexec(const char *string, const char *cmask, int minlen)
{
const char *s = string - 1;
* It returns the number of chars actually written to *mask;
*/
+/** Decompile a compiled mask into printable form.
+ * See also @ref compiledmasks.
+ * @param[out] mask Output mask buffer.
+ * @param[in] cmask Compiled mask.
+ * @return Number of characters written to \a mask.
+ */
int matchdecomp(char *mask, const char *cmask)
{
char *rtb = mask;
* written by Run (Carlo Wood), this function is written by
* Nemesi (Andrea Cocito)
*/
-
+/** Tests for a superset relationship between compiled masks. This
+ * function does for compiled masks what mmatch() is does for normal
+ * masks.
+ * See also @ref compiledmasks.
+ * @param[in] wcm Compiled mask believed to be wider.
+ * @param[in] wminlen Minimum match length for \a wcm.
+ * @param[in] rcm Compiled mask believed to be restricted.
+ * @param[in] rminlen Minimum match length for \a rcm.
+ * @return Zero if \a wcm is a superset of \a rcm, non-zero if not.
+ */
int mmexec(const char *wcm, int wminlen, const char *rcm, int rminlen)
{
const char *w, *r, *br, *bw, *rx, *rz;
return 1; /* Auch... something left out ? Fail */
}
-/*
- * matchcompIP()
- * Compiles an IP mask into an in_mask structure
- * The given <mask> can either be:
- * - An usual irc type mask, containing * and or ?
- * - An ip number plus a /bitnumber part, that will only consider
- * the first "bitnumber" bits of the IP (bitnumber must be in 0-31 range)
- * - An ip numer plus a /ip.bit.mask.values that will consider
- * only the bits marked as 1 in the ip.bit.mask.values
- * In the last two cases both the ip number and the bitmask can specify
- * less than 4 bytes, the missing bytes then default to zero, note that
- * this is *different* from the way inet_aton() does and that this does
- * NOT happen for normal IPmasks (not containing '/')
- * If the returned value is zero the produced in_mask might match some IP,
- * if it's nonzero it will never match anything (and the imask struct is
- * set so that always fails).
- *
- * The returned structure contains 3 fields whose meaning is the following:
- * im.mask = The bits considered significative in the IP
- * im.bits = What these bits should look like to have a match
- * im.fall = If zero means that the above information used as
- * ((IP & im.mask) == im.bits) is enough to tell if the compiled
- * mask matches the given IP, nonzero means that it is needed,
- * in case they did match, to call also the usual text match
- * functions, because the mask wasn't "completely compiled"
- *
- * They should be used like:
- * matchcompIP(&im, mask);
- * if ( ((IP & im.mask)!=im.bits)) || (im.fall&&match(mask,inet_ntoa(IP))) )
- * { handle_non_match } else { handle_match };
- * instead of:
- * if ( match(mask, inet_ntoa(IP)) )
- * { handle_non_match } else { handle_match };
- *
- * Note: This function could be smarter when dealing with complex masks,
- * this implementation is quite lazy and understands only very simple
- * cases, whatever contains a ? anywhere or contains a '*' that isn't
- * part of a trailing '.*' will fallback to text-match, this could be
- * avoided for masks like 12?3.5.6 12.*.3.4 1.*.*.2 72?72?72?72 and
- * so on that "could" be completely compiled to IP masks.
- * If you try to improve this be aware of the fact that ? and *
- * could match both dots and digits and we _must_ always reject
- * what doesn't match in textform (like leading zeros and so on),
- * so it's a LOT more tricky than it might seem. By now most common
- * cases are optimized.
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+/** Parse an input string as an IPv4 address.
+ * @param[in] in Text form of address.
+ * @param[out] out IPv4 address in network representation.
+ * @return Number of address bits specified by \a in.
*/
+static int ipmask_parse_ipv4(const char *in, struct in_addr *out)
+{
+ int class;
+ int ad[4] = { 0 };
+ int bits = 0;
+
+ class = sscanf(in, "%d.%d.%d.%d/%d", &ad[0], &ad[1], &ad[2], &ad[3], &bits);
+ if (class != 5)
+ bits = class * 8;
+ out->s_addr = ntohl((ad[0] << 24) | (ad[1] << 16) | (ad[2] << 8) | ad[3]);
+ return bits;
+}
-int matchcompIP(struct in_mask *imask, const char *mask)
+/** Test whether a string looks like it matches only IPv4 addresses.
+ * @param[in] mask Hostname matching mask.
+ * @return Non-zero if \a mask can only match IPv4 addresses, zero otherwise.
+ */
+int check_if_ipmask(const char *mask)
{
- const char *m = mask;
- unsigned int bits = 0;
- unsigned int filt = 0;
- int unco = 0;
- int digits = 0;
- int shift = 24;
- int tmp = 0;
-
- do
- {
- switch (*m)
+ int has_digit = 0;
+ const char *p;
+
+ for (p = mask; *p; ++p)
+ if (*p != '*' && *p != '?' && *p != '.' && *p != '/')
{
- case '\\':
- if ((m[1] == '\\') || (m[1] == '*') || (m[1] == '?')
- || (m[1] == '\0'))
- break;
- continue;
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- if (digits && !tmp) /* Leading zeros */
- break;
- digits++;
- tmp *= 10;
- tmp += (*m - '0'); /* Can't overflow, INT_MAX > 2559 */
- if (tmp > 255)
- break;
- continue;
- case '\0':
- filt = 0xFFFFFFFF;
- /* Intentional fallthrough */
- case '.':
- if ((!shift) != (!*m))
- break;
- /* Intentional fallthrough */
- case '/':
- bits |= (tmp << shift);
- shift -= 8;
- digits = 0;
- tmp = 0;
- if (*m != '/')
- continue;
- shift = 24;
- do
- {
- m++;
- if (IsDigit(*m))
- {
- if (digits && !tmp) /* Leading zeros */
- break;
- digits++;
- tmp *= 10;
- tmp += (*m - '0'); /* Can't overflow, INT_MAX > 2559 */
- if (tmp > 255)
- break;
- }
- else
- {
- switch (*m)
- {
- case '.':
- case '\0':
- if ((!shift) && (*m))
- break;
- filt |= (tmp << shift);
- shift -= 8;
- tmp = 0;
- digits = 0;
- continue;
- default:
- break;
- }
- break;
- }
- }
- while (*m);
- if (*m)
- break;
- if (filt && (!(shift < 16)) && (!(filt & 0xE0FFFFFF)))
- filt = 0xFFFFFFFF << (32 - ((filt >> 24)));
- bits &= filt;
- continue;
- case '?':
- unco = 1;
- /* Intentional fallthrough */
- case '*':
- if (digits)
- unco = 1;
- filt = (0xFFFFFFFF << (shift)) << 8;
- while (*++m)
- {
- if (IsDigit(*m))
- unco = 1;
- else
- {
- switch (*m)
- {
- case '.':
- if (m[1] != '*')
- unco = 1;
- if (!shift)
- break;
- shift -= 8;
- continue;
- case '?':
- unco = 1;
- case '*':
- continue;
- default:
- break;
- }
- break;
- }
- }
- if (*m)
- break;
- continue;
- default:
- break;
+ if (!IsDigit(*p))
+ return 0;
+ has_digit = -1;
}
- /* If we get here there is some error and this can't ever match */
- filt = 0;
- bits = ~0;
- unco = 0;
- break; /* This time break the loop :) */
+ return has_digit;
+}
+/** Try to parse an IPv4 or IPv6 address mask.
+ * @param[in] in Address matching mask.
+ * @param[out] mask Fixed bits of address mask.
+ * @param[out] bits_ptr If non-NULL, receives number of bits specified in address mask.
+ * @return Non-zero on successful parse; zero on error.
+ */
+int ipmask_parse(const char *in, struct irc_in_addr *mask, unsigned char *bits_ptr)
+{
+ struct in_addr ipv4;
+ char *p;
+ int bits = 0;
+
+ if (check_if_ipmask(in)) {
+ bits = ipmask_parse_ipv4(in, &ipv4);
+ mask->in6_16[0] = mask->in6_16[1] = mask->in6_16[2] = 0;
+ mask->in6_16[3] = mask->in6_16[4] = 0;
+ mask->in6_16[5] = 0xffff;
+ memcpy(&mask->in6_16[6], &ipv4.s_addr, sizeof(ipv4.s_addr));
+ bits += 96;
+ } else if (in[0] == '*' && in[1] == '\0') {
+ /* accept as a 0-bit mask */
+ } else {
+ if (!(p = strchr(in, '/')))
+ bits = 128;
+ else
+ *p = 0;
+ if (!ircd_aton(mask, in)) {
+ if (p)
+ *p = '/';
+ return 0;
+ }
+ if (p) {
+ bits = atoi(p + 1);
+ *p = '/';
+ }
}
- while (*m++);
- imask->bits.s_addr = htonl(bits);
- imask->mask.s_addr = htonl(filt);
- imask->fall = unco;
- return ((bits & ~filt) ? -1 : 0);
+ if (bits_ptr)
+ *bits_ptr = bits;
+ return 1;
+}
+
+/** Test whether an address matches the most significant bits of a mask.
+ * @param[in] addr Address to test.
+ * @param[in] mask Address to test against.
+ * @param[in] bits Number of bits to test.
+ * @return 0 on mismatch, 1 if bits < 128 and all bits match; -1 if
+ * bits == 128 and all bits match.
+ */
+int ipmask_check(const struct irc_in_addr *addr, const struct irc_in_addr *mask, unsigned char bits)
+{
+ int k;
+ for (k = 0; k < 8; k++) {
+ if (bits < 16)
+ return (addr->in6_16[k] & htons(0xffff << (16-bits))) == mask->in6_16[k];
+ if (addr->in6_16[k] != mask->in6_16[k])
+ return 0;
+ if (!(bits -= 16))
+ return 1;
+ }
+ return -1;
}