/* * Unreal Internet Relay Chat Daemon, src/modules/crule.c * crule parser and checker. * (C) Tony Vencill (Tonto on IRC) * (C) 2023- Bram Matthys and the UnrealIRCd Team * * 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. */ #include "unrealircd.h" ModuleHeader MOD_HEADER = { "crule", "1.0.1", "Crule support for and deny link::rule and spamfilter::rule", "UnrealIRCd Team", "unrealircd-6", }; /* Originally by Tony Vencill (Tonto on IRC) * * The majority of this file is a recursive descent parser used to convert * connection rules into expression trees when the conf file is read. * All parsing structures and types are hidden in the interest of good * programming style and to make possible future data structure changes * without affecting the interface between this patch and the rest of the * server. The only functions accessible externally are crule_parse, * crule_free, and crule_eval. Prototypes for these functions can be * found in h.h. * * The production rules for the grammar are as follows ("rule" is the * starting production): * * rule: * orexpr END END is end of input or : * orexpr: * andexpr * andexpr || orexpr * andexpr: * primary * primary && andexpr * primary: * function * ! primary * ( orexpr ) * function: * word ( ) word is alphanumeric string, first character * word ( arglist ) must be a letter * arglist: * word * word , arglist */ /* Last update of parser functions taken from ircu on 2023-03-19 * matching ircu's ircd/crule.c from 2021-09-04. * Then ported / UnrealIRCd-ized by Syzop and re-adding crule_test() * and such. All the actual "functions" like crule_connected() are * our own and not re-feteched (but were based on older versions). */ /** Input scanner tokens. */ typedef enum crule_token crule_token; enum crule_token { CR_UNKNOWN, /**< Unknown token type. */ CR_END, /**< End of input ('\\0' or ':'). */ CR_AND, /**< Logical and operator (&&). */ CR_OR, /**< Logical or operator (||). */ CR_NOT, /**< Logical not operator (!). */ CR_OPENPAREN, /**< Open parenthesis. */ CR_CLOSEPAREN, /**< Close parenthesis. */ CR_COMMA, /**< Comma. */ CR_EQUAL, /**< Operator == */ CR_LESS_THAN, /**< Operator < */ CR_MORE_THAN, /**< Operator > */ CR_WORD /**< Something that looks like a hostmask (alphanumerics, "*?.-"). */ }; #define IsComparisson(x) ((x == CR_EQUAL) || (x == CR_LESS_THAN) || (x == CR_MORE_THAN)) /** Parser error codes. */ typedef enum crule_errcode crule_errcode; enum crule_errcode { CR_NOERR, /**< No error. */ CR_UNEXPCTTOK, /**< Invalid token given context. */ CR_UNKNWTOK, /**< Input did not form a valid token. */ CR_EXPCTAND, /**< Did not see expected && operator. */ CR_EXPCTOR, /**< Did not see expected || operator. */ CR_EXPCTPRIM, /**< Expected a primitive (parentheses, ! or word). */ CR_EXPCTOPEN, /**< Expected an open parenthesis after function name. */ CR_EXPCTCLOSE, /**< Expected a close parenthesis to match open parenthesis. */ CR_UNKNWFUNC, /**< Attempt to use an unknown function. */ CR_ARGMISMAT, /**< Wrong number of arguments to function. */ CR_EXPCTVALUE, /**< Missing value in a comparisson */ }; /* NOTE: Expression tree structure, function pointer, and tree pointer local! */ int _crule_test(const char *rule); CRuleNode *_crule_parse(const char *rule); void _crule_free(CRuleNode**); int _crule_eval(crule_context *context, CRuleNode *rule); const char *_crule_errstring(int errcode); /* rule function prototypes - local! */ static int crule_connected(crule_context *, int, void **); static int crule_directcon(crule_context *, int, void **); static int crule_via(crule_context *, int, void **); static int crule_directop(crule_context *, int, void **); static int crule__andor(crule_context *, int, void **); static int crule__not(crule_context *, int, void **); // newstyle static int crule_online_time(crule_context *, int, void **); static int crule_reputation(crule_context *, int, void **); static int crule_tag(crule_context *, int, void **); static int crule_in_channel(crule_context *, int, void **); static int crule_destination(crule_context *, int, void **); static int crule_cap_version(crule_context *, int, void **); static int crule_cap_set(crule_context *, int, void **); static int crule_has_user_mode(crule_context *, int, void **); static int crule_has_channel_mode(crule_context *, int, void **); static int crule_away(crule_context *, int, void **); static int crule_is_identified(crule_context *, int, void **); static int crule_is_webirc(crule_context *, int, void **); static int crule_is_websocket(crule_context *, int, void **); static int crule_tls(crule_context *, int, void **); static int crule_in_security_group(crule_context *, int, void **); static int crule_match_mask(crule_context *, int, void **); static int crule_match_ip(crule_context *, int, void **); static int crule_match_account(crule_context *, int, void **); static int crule_match_country(crule_context *, int, void **); static int crule_match_asn(crule_context *, int, void **); static int crule_match_certfp(crule_context *, int, void **); static int crule_match_realname(crule_context *, int, void **); static int crule_unicode_count(crule_context *, int, void **); static int crule_server_port(crule_context *, int, void **); /* parsing function prototypes - local! */ static int crule_gettoken(crule_token *next_tokp, const char **str); static void crule_getword(char *, int *, size_t, const char **); static int crule_parseandexpr(CRuleNode **, crule_token *, const char **); static int crule_parseorexpr(CRuleNode **, crule_token *, const char **); static int crule_parseprimary(CRuleNode **, crule_token *, const char **); static int crule_parsefunction(CRuleNode **, crule_token *, const char **); static int crule_parsearglist(CRuleNode *, crule_token *, const char **); /* error messages */ char *crule_errstr[] = { "Unknown error", /* NOERR? - for completeness */ "Unexpected token", /* UNEXPCTTOK */ "Unknown token", /* UNKNWTOK */ "And expr expected", /* EXPCTAND */ "Or expr expected", /* EXPCTOR */ "Primary expected", /* EXPCTPRIM */ "( expected", /* EXPCTOPEN */ ") expected", /* EXPCTCLOSE */ "Unknown function", /* UNKNWFUNC */ "Argument mismatch", /* ARGMISMAT */ "Missing value in comparisson", /* CR_EXPCTVALUE */ }; /* function table - null terminated */ struct crule_funclistent { char name[32]; int reqnumargs; crule_funcptr funcptr; }; struct crule_funclistent crule_funclist[] = { {"connected", 1, crule_connected}, {"online_time", 0, crule_online_time}, {"reputation", 0, crule_reputation}, {"tag", 1, crule_tag}, {"inchannel", 1, crule_in_channel}, // old name, keep it around for now.. {"in_channel", 1, crule_in_channel}, // new name (6.1.6+) {"destination", 1, crule_destination}, {"cap_version", 0, crule_cap_version}, {"cap_set", 1, crule_cap_set}, {"directcon", 1, crule_directcon}, {"via", 2, crule_via}, {"directop", 0, crule_directop}, {"has_user_mode", 1, crule_has_user_mode}, {"has_channel_mode", 1, crule_has_channel_mode}, {"is_away", 0, crule_away}, {"is_identified", 0, crule_is_identified}, {"is_tls", 0, crule_tls}, {"is_webirc", 0, crule_is_webirc}, {"is_websocket", 0, crule_is_websocket}, {"in_security_group", 1, crule_in_security_group}, {"match_mask", 1, crule_match_mask}, {"match_ip", 1, crule_match_ip}, {"match_account", 1, crule_match_account}, {"match_country", 1, crule_match_country}, {"match_asn", 1, crule_match_asn}, {"match_certfp", 1, crule_match_certfp}, {"match_realname", 1, crule_match_realname}, {"unicode_count", 1, crule_unicode_count}, {"server_port", 0, crule_server_port}, {"", 0, NULL} /* this must be here to mark end of list */ }; MOD_TEST() { MARK_AS_OFFICIAL_MODULE(modinfo); EfunctionAdd(modinfo->handle, EFUNC_CRULE_TEST, _crule_test); EfunctionAddPVoid(modinfo->handle, EFUNC_CRULE_PARSE, TO_PVOIDFUNC(_crule_parse)); EfunctionAddVoid(modinfo->handle, EFUNC_CRULE_FREE, _crule_free); EfunctionAdd(modinfo->handle, EFUNC_CRULE_EVAL, _crule_eval); EfunctionAddConstString(modinfo->handle, EFUNC_CRULE_ERRSTRING, _crule_errstring); return MOD_SUCCESS; } MOD_INIT() { return MOD_SUCCESS; } MOD_LOAD() { return MOD_SUCCESS; } MOD_UNLOAD() { return MOD_SUCCESS; } static int crule_away(crule_context *context, int numargs, void *crulearg[]) { if (!context || !context->client || !IsUser(context->client)) return 0; return (!BadPtr(context->client->user->away)) ? 1 : 0; } static int crule_is_identified(crule_context *context, int numargs, void *crulearg[]) { if (!context || !context->client) return 0; return (IsLoggedIn(context->client)) ? 1 : 0; } static int crule_is_websocket(crule_context *context, int numargs, void *crulearg[]) { if (!context || !context->client) return 0; return (moddata_client_get(context->client, "websocket")) ? 1 : 0; } static int crule_is_webirc(crule_context *context, int numargs, void *crulearg[]) { if (!context || !context->client) return 0; return (moddata_client_get(context->client, "webirc")) ? 1 : 0; } static int crule_tls(crule_context *context, int numargs, void *crulearg[]) { if (!context || !context->client) return 0; return (IsSecure(context->client) || IsSecureConnect(context->client)) ? 1 : 0; } static int crule_has_user_mode(crule_context *context, int numargs, void *crulearg[]) { const char *modes = (char *)crulearg[0]; if (!context || !context->client || !strlen(modes)) return 0; for (; *modes; modes++) if (!has_user_mode(context->client, *modes)) return 0; return 1; } static int crule_has_channel_mode(crule_context *context, int numargs, void *crulearg[]) { const char *modes = (char *)crulearg[0]; Channel *channel; if (!context || !context->destination || (context->destination[0] != '#')) return 0; channel = find_channel(context->destination); if (!channel) return 0; for (; *modes; modes++) if (!has_channel_mode(channel, *modes)) return 0; return 1; } static int crule_connected(crule_context *context, int numargs, void *crulearg[]) { Client *client; /* taken from cmd_links */ /* Faster this way -- codemastr*/ list_for_each_entry(client, &global_server_list, client_node) { if (!match_simple((char *)crulearg[0], client->name)) continue; return 1; } return 0; } static int crule_directcon(crule_context *context, int numargs, void *crulearg[]) { Client *client; /* adapted from cmd_trace and exit_one_client */ /* XXX: iterate server_list when added */ list_for_each_entry(client, &lclient_list, lclient_node) { if (!IsServer(client)) continue; if (!match_simple((char *)crulearg[0], client->name)) continue; return 1; } return 0; } static int crule_via(crule_context *context, int numargs, void *crulearg[]) { Client *client; /* adapted from cmd_links */ /* Faster this way -- codemastr */ list_for_each_entry(client, &global_server_list, client_node) { if (!match_simple((char *)crulearg[1], client->name)) continue; if (!match_simple((char *)crulearg[0], client->uplink->name)) continue; return 1; } return 0; } static int crule_directop(crule_context *context, int numargs, void *crulearg[]) { Client *client; /* adapted from cmd_trace */ list_for_each_entry(client, &lclient_list, lclient_node) { if (!IsOper(client)) continue; return 1; } return 0; } static int crule_online_time(crule_context *context, int numargs, void *crulearg[]) { if (context && context->client) return get_connected_time(context->client); return 0; } static int crule_reputation(crule_context *context, int numargs, void *crulearg[]) { if (context && context->client) return GetReputation(context->client); return 0; } static int crule_tag(crule_context *context, int numargs, void *crulearg[]) { Tag *tag; if (!context || !context->client) return 0; tag = find_tag(context->client, (char *)crulearg[0]); if (tag) return tag->value; return 0; } static int crule_in_channel(crule_context *context, int numargs, void *crulearg[]) { Membership *lp; const char *channelname = (char *)crulearg[0]; const char *p = channelname; char symbol = '\0'; if (!context || !context->client || !context->client->user) return 0; /* The following code is taken from src/modules/extbans/inchannel.c * function extban_inchannel_is_banned() */ if (*p != '#') { symbol = *p; p++; } for (lp = context->client->user->channel; lp; lp = lp->next) { if (match_esc(p, lp->channel->name)) { /* Channel matched, check symbol if needed (+/%/@/etc) */ if (symbol) { if (inchannel_compareflags(symbol, lp->member_modes)) return 1; } else return 1; } } return 0; } static int crule_destination(crule_context *context, int numargs, void *crulearg[]) { const char *arg = (char *)crulearg[0]; if (!context || !context->destination) return 0; return match_simple(arg, context->destination); } static int crule_cap_version(crule_context *context, int numargs, void *crulearg[]) { if (context && context->client && context->client->local) return context->client->local->cap_protocol; return 0; } static int crule_cap_set(crule_context *context, int numargs, void *crulearg[]) { const char *capname = (char *)crulearg[0]; if (!context || !context->client || !context->client->local ) return 0; if (HasCapability(context->client, capname)) return 1; return 0; } static int crule_in_security_group(crule_context *context, int numargs, void *crulearg[]) { const char *arg = (char *)crulearg[0]; if (!context || !context->client) return 0; if (user_allowed_by_security_group_name(context->client, arg)) return 1; return 0; } static int crule_match_mask(crule_context *context, int numargs, void *crulearg[]) { const char *arg = (char *)crulearg[0]; if (!context || !context->client) return 0; if (match_user(arg, context->client, MATCH_CHECK_REAL_HOST|MATCH_CHECK_IP|MATCH_CHECK_EXTENDED)) return 1; return 0; } static int crule_match_ip(crule_context *context, int numargs, void *crulearg[]) { const char *arg = (char *)crulearg[0]; if (!context || !context->client) return 0; if (match_user(arg, context->client, MATCH_CHECK_IP|MATCH_MASK_IS_HOST)) return 1; return 0; } static int crule_match_account(crule_context *context, int numargs, void *crulearg[]) { const char *arg = (char *)crulearg[0]; if (context && context->client && user_matches_extended_server_ban(context->client, "account", arg)) return 1; return 0; } static int crule_match_country(crule_context *context, int numargs, void *crulearg[]) { const char *arg = (char *)crulearg[0]; if (context && context->client && user_matches_extended_server_ban(context->client, "country", arg)) return 1; return 0; } static int crule_match_asn(crule_context *context, int numargs, void *crulearg[]) { const char *arg = (char *)crulearg[0]; if (context && context->client && user_matches_extended_server_ban(context->client, "asn", arg)) return 1; return 0; } static int crule_match_certfp(crule_context *context, int numargs, void *crulearg[]) { const char *arg = (char *)crulearg[0]; if (context && context->client && user_matches_extended_server_ban(context->client, "certfp", arg)) return 1; return 0; } static int crule_match_realname(crule_context *context, int numargs, void *crulearg[]) { const char *arg = (char *)crulearg[0]; if (context && context->client && match_simple(arg, context->client->info)) return 1; return 0; } static int crule_unicode_count(crule_context *context, int numargs, void *crulearg[]) { const char *arg = (char *)crulearg[0]; if (context && context->clictx && context->clictx->textanalysis) { int i = utf8_get_block_number(arg); if (i < 0) return -1; /* Block name not found */ return context->clictx->textanalysis->unicode_blockmap[i]; } return 0; } static int crule_server_port(crule_context *context, int numargs, void *crulearg[]) { if (context && context->client) return get_server_port(context->client); return 0; } /** Evaluate a connection rule. * @param[in] rule Rule to evalute. * @return Non-zero if the rule allows the connection, zero otherwise. */ int _crule_eval(crule_context *context, CRuleNode* rule) { int ret = rule->funcptr(context, rule->numargs, rule->arg); switch (rule->func_test_type) { case CR_EQUAL: /* something()==xyz */ if (ret == rule->func_test_value) return 1; return 0; case CR_LESS_THAN: /* something()func_test_value) return 1; return 0; case CR_MORE_THAN: /* something()>xyz */ if (ret > rule->func_test_value) return 1; return 0; default: /* Basic true/false handling */ return ret; } } /** Perform an and-or-or test on crulearg[0] and crulearg[1]. * If crulearg[2] is non-NULL, it means do OR; if it is NULL, do AND. * @param[in] numargs Number of valid args in \a crulearg. * @param[in] crulearg Argument array. * @return Non-zero if the condition is true, zero if not. */ static int crule__andor(crule_context *context, int numargs, void *crulearg[]) { int result1; result1 = crule_eval(context, crulearg[0]); if (crulearg[2]) /* or */ return (result1 || crule_eval(context, crulearg[1])); else return (result1 && crule_eval(context, crulearg[1])); } /** Logically invert the result of crulearg[0]. * @param[in] numargs Number of valid args in \a crulearg. * @param[in] crulearg Argument array. * @return Non-zero if the condition is true, zero if not. */ static int crule__not(crule_context *context, int numargs, void *crulearg[]) { return !crule_eval(context, crulearg[0]); } /** Scan an input token from \a ruleptr. * @param[out] next_tokp Receives type of next token. * @param[in,out] ruleptr Next readable character from input. * @return Either CR_UNKNWTOK if the input was unrecognizable, else CR_NOERR. */ static int crule_gettoken(crule_token *next_tokp, const char **ruleptr) { char pending = '\0'; *next_tokp = CR_UNKNOWN; while (*next_tokp == CR_UNKNOWN) switch (*(*ruleptr)++) { case ' ': case '\t': break; case '&': if (pending == '\0') pending = '&'; else if (pending == '&') *next_tokp = CR_AND; else return CR_UNKNWTOK; break; case '|': if (pending == '\0') pending = '|'; else if (pending == '|') *next_tokp = CR_OR; else return CR_UNKNWTOK; break; case '!': *next_tokp = CR_NOT; break; case '(': *next_tokp = CR_OPENPAREN; break; case ')': *next_tokp = CR_CLOSEPAREN; break; case '<': *next_tokp = CR_LESS_THAN; break; case '>': *next_tokp = CR_MORE_THAN; break; case '=': if (pending == '\0') pending = '='; else if (pending == '=') *next_tokp = CR_EQUAL; else return CR_UNKNWTOK; break; case ',': *next_tokp = CR_COMMA; break; case '\0': (*ruleptr)--; *next_tokp = CR_END; break; case ':': *next_tokp = CR_END; break; default: if ((isalnum(*(--(*ruleptr)))) || (**ruleptr == '*') || (**ruleptr == '?') || (**ruleptr == '.') || (**ruleptr == '-') || (**ruleptr == '_') || (**ruleptr == '\'')) *next_tokp = CR_WORD; else return CR_UNKNWTOK; break; } return CR_NOERR; } /** Scan a word from \a ruleptr. * @param[out] word Output buffer. * @param[out] wordlenp Length of word written to \a word (not including terminating NUL). * @param[in] maxlen Maximum number of bytes writable to \a word. * @param[in,out] ruleptr Next readable character from input. */ static void crule_getword(char *word, int *wordlenp, size_t maxlen, const char **ruleptr) { char *word_ptr; char quoted = 0; word_ptr = word; if (**ruleptr == '\'') { (*ruleptr)++; quoted = 1; } while ((size_t)(word_ptr - word) < maxlen && (isalnum(**ruleptr) || **ruleptr == '*' || **ruleptr == '?' || **ruleptr == '.' || **ruleptr == '-' || **ruleptr == '_' || (quoted && (**ruleptr != '\'')))) { *word_ptr++ = *(*ruleptr)++; } *word_ptr = '\0'; *wordlenp = word_ptr - word; /* Eat the remaining ' quote, if needed... */ if (quoted && (**ruleptr == '\'')) (*ruleptr)++; } /** Parse an entire rule. * @param[in] rule Text form of rule. * @return CRuleNode for rule, or NULL if there was a parse error. */ CRuleNode *_crule_parse(const char *rule) { const char *ruleptr = rule; crule_token next_tok; CRuleNode* ruleroot = 0; int errcode = CR_NOERR; if ((errcode = crule_gettoken(&next_tok, &ruleptr)) == CR_NOERR) { if ((errcode = crule_parseorexpr(&ruleroot, &next_tok, &ruleptr)) == CR_NOERR) { if (ruleroot != NULL) { if (next_tok == CR_END) return ruleroot; else errcode = CR_UNEXPCTTOK; } else errcode = CR_EXPCTOR; } } if (ruleroot != NULL) crule_free(&ruleroot); return 0; } /** Test-parse an entire rule. * @param[in] rule Text form of rule. * @return error code, or 0 for no failure */ int _crule_test(const char *rule) { const char *ruleptr = rule; crule_token next_tok; CRuleNode* ruleroot = 0; int errcode = CR_NOERR; if ((errcode = crule_gettoken(&next_tok, &ruleptr)) == CR_NOERR) { if ((errcode = crule_parseorexpr(&ruleroot, &next_tok, &ruleptr)) == CR_NOERR) { if (ruleroot != NULL) { if (next_tok == CR_END) { /* PASS */ crule_free(&ruleroot); return 0; } else { errcode = CR_UNEXPCTTOK; } } else errcode = CR_EXPCTOR; } } if (ruleroot != NULL) crule_free(&ruleroot); return errcode + 1; } const char *_crule_errstring(int errcode) { if (errcode == 0) return "No error"; else return crule_errstr[errcode-1]; } /** Parse an or expression. * @param[out] orrootp Receives parsed node. * @param[in,out] next_tokp Next input token type. * @param[in,out] ruleptr Next input character. * @return A crule_errcode value. */ static int crule_parseorexpr(CRuleNode **orrootp, crule_token *next_tokp, const char **ruleptr) { int errcode = CR_NOERR; CRuleNode *andexpr; CRuleNode *orptr; *orrootp = NULL; while (errcode == CR_NOERR) { errcode = crule_parseandexpr(&andexpr, next_tokp, ruleptr); if ((errcode == CR_NOERR) && (*next_tokp == CR_OR)) { orptr = safe_alloc(sizeof(CRuleNode)); orptr->funcptr = crule__andor; orptr->flags |= CRULE_FLAG_AND_OR; orptr->numargs = 3; orptr->arg[2] = (void *)1; if (*orrootp != NULL) { (*orrootp)->arg[1] = andexpr; orptr->arg[0] = *orrootp; } else orptr->arg[0] = andexpr; *orrootp = orptr; } else { if (*orrootp != NULL) { if (andexpr != NULL) { (*orrootp)->arg[1] = andexpr; return errcode; } else { (*orrootp)->arg[1] = NULL; /* so free doesn't seg fault */ return CR_EXPCTAND; } } else { *orrootp = andexpr; return errcode; } } errcode = crule_gettoken(next_tokp, ruleptr); } return errcode; } /** Parse an and expression. * @param[out] androotp Receives parsed node. * @param[in,out] next_tokp Next input token type. * @param[in,out] ruleptr Next input character. * @return A crule_errcode value. */ static int crule_parseandexpr(CRuleNode **androotp, crule_token *next_tokp, const char **ruleptr) { int errcode = CR_NOERR; CRuleNode *primary; CRuleNode *andptr; *androotp = NULL; while (errcode == CR_NOERR) { errcode = crule_parseprimary(&primary, next_tokp, ruleptr); if ((errcode == CR_NOERR) && (*next_tokp == CR_AND)) { andptr = safe_alloc(sizeof(CRuleNode)); andptr->funcptr = crule__andor; andptr->flags |= CRULE_FLAG_AND_OR; andptr->numargs = 3; andptr->arg[2] = (void *)0; if (*androotp != NULL) { (*androotp)->arg[1] = primary; andptr->arg[0] = *androotp; } else andptr->arg[0] = primary; *androotp = andptr; } else { if (*androotp != NULL) { if (primary != NULL) { (*androotp)->arg[1] = primary; return errcode; } else { (*androotp)->arg[1] = NULL; /* so free doesn't seg fault */ return CR_EXPCTPRIM; } } else { *androotp = primary; return errcode; } } errcode = crule_gettoken(next_tokp, ruleptr); } return errcode; } /** Parse a primary expression. * @param[out] primrootp Receives parsed node. * @param[in,out] next_tokp Next input token type. * @param[in,out] ruleptr Next input character. * @return A crule_errcode value. */ static int crule_parseprimary(CRuleNode **primrootp, crule_token *next_tokp, const char **ruleptr) { CRuleNode **insertionp; int errcode = CR_NOERR; *primrootp = NULL; insertionp = primrootp; while (errcode == CR_NOERR) { switch (*next_tokp) { case CR_OPENPAREN: if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR) break; if ((errcode = crule_parseorexpr(insertionp, next_tokp, ruleptr)) != CR_NOERR) break; if (*insertionp == NULL) { errcode = CR_EXPCTAND; break; } if (*next_tokp != CR_CLOSEPAREN) { errcode = CR_EXPCTCLOSE; break; } errcode = crule_gettoken(next_tokp, ruleptr); break; case CR_NOT: *insertionp = safe_alloc(sizeof(CRuleNode)); (*insertionp)->funcptr = crule__not; (*insertionp)->flags |= CRULE_FLAG_NOT; (*insertionp)->numargs = 1; (*insertionp)->arg[0] = NULL; insertionp = (CRuleNode **) & ((*insertionp)->arg[0]); if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR) break; continue; case CR_WORD: errcode = crule_parsefunction(insertionp, next_tokp, ruleptr); break; default: if (*primrootp == NULL) errcode = CR_NOERR; else errcode = CR_EXPCTPRIM; break; } break; /* loop only continues after a CR_NOT */ } return errcode; } /** Parse a function call. * @param[out] funcrootp Receives parsed node. * @param[in,out] next_tokp Next input token type. * @param[in,out] ruleptr Next input character. * @return A crule_errcode value. */ static int crule_parsefunction(CRuleNode **funcrootp, crule_token *next_tokp, const char **ruleptr) { int errcode = CR_NOERR; char funcname[CR_MAXARGLEN]; int namelen; int i; struct crule_funclistent *func; *funcrootp = NULL; crule_getword(funcname, &namelen, CR_MAXARGLEN - 1, ruleptr); if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR) return errcode; if (*next_tokp == CR_OPENPAREN) { for (i = 0;; i++) { func = &crule_funclist[i]; if (!strcasecmp(func->name, funcname)) break; if (func->name[0] == '\0') return CR_UNKNWFUNC; } if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR) return errcode; *funcrootp = safe_alloc(sizeof(CRuleNode)); (*funcrootp)->funcptr = NULL; /* for freeing aborted trees */ if ((errcode = crule_parsearglist(*funcrootp, next_tokp, ruleptr)) != CR_NOERR) return errcode; if (*next_tokp != CR_CLOSEPAREN) return CR_EXPCTCLOSE; if ((func->reqnumargs != (*funcrootp)->numargs) && (func->reqnumargs != -1)) return CR_ARGMISMAT; if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR) return errcode; if (IsComparisson(*next_tokp)) { char value[CR_MAXARGLEN]; int valuelen = 0; /* a > < or == */ (*funcrootp)->func_test_type = *next_tokp; if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR) return errcode; /* now expect and get the value to compare against, eg '5' */ if (*next_tokp != CR_WORD) return CR_EXPCTVALUE; crule_getword(value, &valuelen, CR_MAXARGLEN - 1, ruleptr); if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR) return errcode; (*funcrootp)->func_test_value = atoi(value); } (*funcrootp)->funcptr = func->funcptr; return CR_NOERR; } else return CR_EXPCTOPEN; } /** Parse the argument list to a CRuleNode. * @param[in,out] argrootp Node whos argument list is being populated. * @param[in,out] next_tokp Next input token type. * @param[in,out] ruleptr Next input character. * @return A crule_errcode value. */ static int crule_parsearglist(CRuleNode *argrootp, crule_token *next_tokp, const char **ruleptr) { int errcode = CR_NOERR; char *argelemp = NULL; char currarg[CR_MAXARGLEN]; int arglen = 0; char word[CR_MAXARGLEN]; int wordlen = 0; argrootp->numargs = 0; currarg[0] = '\0'; while (errcode == CR_NOERR) { switch (*next_tokp) { case CR_WORD: crule_getword(word, &wordlen, CR_MAXARGLEN - 1, ruleptr); if (currarg[0] != '\0') { if ((arglen + wordlen) < (CR_MAXARGLEN - 1)) { strcat(currarg, " "); strcat(currarg, word); arglen += wordlen + 1; } } else { strcpy(currarg, word); arglen = wordlen; } errcode = crule_gettoken(next_tokp, ruleptr); break; default: /* Syzop/2023-07-16: Removed the collapse() call as all crule * stuff is now about more than just masks and stuff, was this * really needed at all actually? */ //collapse(currarg); if (currarg[0] != '\0') { argelemp = raw_strdup(currarg); argrootp->arg[argrootp->numargs++] = (void *)argelemp; } if (*next_tokp != CR_COMMA) return CR_NOERR; currarg[0] = '\0'; errcode = crule_gettoken(next_tokp, ruleptr); break; } } return errcode; } /* * This function is recursive.. I wish I knew a nonrecursive way but * I don't. Anyway, recursion is fun.. :) * DO NOT CALL THIS FUNCTION WITH A POINTER TO A NULL POINTER * (i.e.: If *elem is NULL, you're doing it wrong - seg fault) */ /** Free a connection rule and all its children. * @param[in,out] elem Pointer to pointer to element to free. MUST NOT BE NULL. */ void _crule_free(CRuleNode **elem) { int arg, numargs; if ((*(elem))->flags & CRULE_FLAG_NOT) { /* type conversions and ()'s are fun! ;) here have an aspirin.. */ if ((*(elem))->arg[0] != NULL) crule_free((CRuleNode**) &((*(elem))->arg[0])); } else if ((*(elem))->flags & CRULE_FLAG_AND_OR) { if ((*(elem))->arg[0] != NULL) crule_free((CRuleNode**) &((*(elem))->arg[0])); if ((*(elem))->arg[1] != NULL) crule_free((CRuleNode**) &((*(elem))->arg[1])); } else { numargs = (*(elem))->numargs; for (arg = 0; arg < numargs; arg++) safe_free((*(elem))->arg[arg]); } safe_free(*elem); *elem = 0; }