3 * @brief Connection rule parser and checker
6 * by Tony Vencill (Tonto on IRC) <vencill@bga.com>
8 * The majority of this file is a recusive descent parser used to convert
9 * connection rules into expression trees when the conf file is read.
10 * All parsing structures and types are hidden in the interest of good
11 * programming style and to make possible future data structure changes
12 * without affecting the interface between this patch and the rest of the
13 * server. The only functions accessible externally are crule_parse,
14 * crule_free, and crule_eval. Prototypes for these functions can be
17 * Please direct any connection rule or SmartRoute questions to Tonto on
18 * IRC or by email to vencill@bga.com.
20 * For parser testing, defining CR_DEBUG generates a stand-alone parser
21 * that takes rules from stdin and prints out memory allocation
22 * information and the parsed rule. This stand alone parser is ignorant
23 * of the irc server and thus cannot do rule evaluation. Do not define
24 * this flag when compiling the server! If you wish to generate the
25 * test parser, compile from the ircd directory with a line similar to
26 * cc -o parser -DCR_DEBUG crule.c
28 * The define CR_CHKCONF is provided to generate routines needed in
29 * chkconf. These consist of the parser, a different crule_parse that
30 * prints errors to stderr, and crule_free (just for good style and to
31 * more closely simulate the actual ircd environment). crule_eval and
32 * the rule functions are made empty functions as in the stand-alone
35 * The production rules for the grammar are as follows ("rule" is the
36 * starting production):
39 * orexpr END END is end of input or :
51 * word ( ) word is alphanumeric string, first character
52 * word ( arglist ) must be a letter
62 /* ircd functions and types we need */
65 #include "ircd_alloc.h"
66 #include "ircd_chattr.h"
67 #include "ircd_string.h"
76 #else /* includes and defines to make the stand-alone test parser */
81 #define BadPtr(x) (!(x) || (*(x) == '\0'))
82 #define DupString(x,y) \
84 x = (char*) MyMalloc(strlen(y)+1); \
88 /* We don't care about collation discrepacies here, it seems.... */
89 #define ircd_strcmp strcasecmp
96 #if defined(CR_DEBUG) || defined(CR_CHKCONF)
99 #define MyMalloc malloc
105 /* some constants and shared data types */
106 #define CR_MAXARGLEN 80 /**< Maximum arg length (must be > HOSTLEN) */
107 #define CR_MAXARGS 3 /**< Maximum number of args for a rule */
110 * Some symbols for easy reading
113 /** Input scanner tokens. */
115 CR_UNKNOWN, /**< Unknown token type. */
116 CR_END, /**< End of input ('\\0' or ':'). */
117 CR_AND, /**< Logical and operator (&&). */
118 CR_OR, /**< Logical or operator (||). */
119 CR_NOT, /**< Logical not operator (!). */
120 CR_OPENPAREN, /**< Open parenthesis. */
121 CR_CLOSEPAREN, /**< Close parenthesis. */
122 CR_COMMA, /**< Comma. */
123 CR_WORD /**< Something that looks like a hostmask (alphanumerics, "*?.-"). */
126 /** Parser error codes. */
128 CR_NOERR, /**< No error. */
129 CR_UNEXPCTTOK, /**< Invalid token given context. */
130 CR_UNKNWTOK, /**< Input did not form a valid token. */
131 CR_EXPCTAND, /**< Did not see expected && operator. */
132 CR_EXPCTOR, /**< Did not see expected || operator. */
133 CR_EXPCTPRIM, /**< Expected a primitive (parentheses, ! or word). */
134 CR_EXPCTOPEN, /**< Expected an open parenthesis after function name. */
135 CR_EXPCTCLOSE, /**< Expected a close parenthesis to match open parenthesis. */
136 CR_UNKNWFUNC, /**< Attempt to use an unknown function. */
137 CR_ARGMISMAT /**< Wrong number of arguments to function. */
141 * Expression tree structure, function pointer, and tree pointer local!
143 /** Evaluation function for a connection rule. */
144 typedef int (*crule_funcptr) (int, void **);
146 /** Node in a connection rule tree. */
148 crule_funcptr funcptr; /**< Evaluation function for this node. */
149 int numargs; /**< Number of arguments. */
150 void *arg[CR_MAXARGS]; /**< Array of arguments. For operators, each arg
151 is a tree element; for functions, each arg is
155 /** Typedef to save typing effort. */
156 typedef struct CRuleNode* CRuleNodePtr;
158 /* local rule function prototypes */
159 static int crule_connected(int, void *[]);
160 static int crule_directcon(int, void *[]);
161 static int crule_via(int, void *[]);
162 static int crule_directop(int, void *[]);
163 static int crule__andor(int, void *[]);
164 static int crule__not(int, void *[]);
166 /* local parsing function prototypes */
167 static int crule_gettoken(int* token, const char** str);
168 static void crule_getword(char*, int*, size_t, const char**);
169 static int crule_parseandexpr(CRuleNodePtr*, int *, const char**);
170 static int crule_parseorexpr(CRuleNodePtr*, int *, const char**);
171 static int crule_parseprimary(CRuleNodePtr*, int *, const char**);
172 static int crule_parsefunction(CRuleNodePtr*, int *, const char**);
173 static int crule_parsearglist(CRuleNodePtr, int *, const char**);
175 #if defined(CR_DEBUG) || defined(CR_CHKCONF)
177 * Prototypes for the test parser; if not debugging,
178 * these are defined in h.h
180 struct CRuleNode* crule_parse(const char*);
181 void crule_free(struct CRuleNode**);
183 void print_tree(CRuleNodePtr);
187 /** Error messages, indexed by the corresponding crule_errcode. */
188 char *crule_errstr[] = {
189 "Unknown error", /* NOERR? - for completeness */
190 "Unexpected token", /* UNEXPCTTOK */
191 "Unknown token", /* UNKNWTOK */
192 "And expr expected", /* EXPCTAND */
193 "Or expr expected", /* EXPCTOR */
194 "Primary expected", /* EXPCTPRIM */
195 "( expected", /* EXPCTOPEN */
196 ") expected", /* EXPCTCLOSE */
197 "Unknown function", /* UNKNWFUNC */
198 "Argument mismatch" /* ARGMISMAT */
201 /** Connection rule function table entry. */
202 struct crule_funclistent {
203 char name[15]; /**< Function name. */
204 int reqnumargs; /**< Required number of arguments. */
205 crule_funcptr funcptr; /**< Handler function. */
208 /** Defined connection rules. */
209 struct crule_funclistent crule_funclist[] = {
210 /* maximum function name length is 14 chars */
211 {"connected", 1, crule_connected},
212 {"directcon", 1, crule_directcon},
213 {"via", 2, crule_via},
214 {"directop", 0, crule_directop},
215 {"", 0, NULL} /* this must be here to mark end of list */
218 /** Check whether any connected server matches crulearg[0].
219 * @param[in] numargs Number of valid args in \a crulearg.
220 * @param[in] crulearg Argument array.
221 * @return Non-zero if the condition is true, zero if not.
223 static int crule_connected(int numargs, void *crulearg[])
225 #if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
226 struct Client *acptr;
228 /* taken from m_links */
229 for (acptr = GlobalClientList; acptr; acptr = cli_next(acptr))
231 if (!IsServer(acptr) && !IsMe(acptr))
233 if (match((char *)crulearg[0], cli_name(acptr)))
241 /** Check whether any directly connected server matches crulearg[0].
242 * @param[in] numargs Number of valid args in \a crulearg.
243 * @param[in] crulearg Argument array.
244 * @return Non-zero if the condition is true, zero if not.
246 static int crule_directcon(int numargs, void *crulearg[])
248 #if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
250 struct Client *acptr;
252 /* adapted from m_trace and exit_one_client */
253 for (i = 0; i <= HighestFd; i++)
255 if (!(acptr = LocalClientArray[i]) || !IsServer(acptr))
257 if (match((char *)crulearg[0], cli_name(acptr)))
265 /** Check whether a connected server matching crulearg[1] is
266 * connnected to me behind one matching crulearg[0].
267 * @param[in] numargs Number of valid args in \a crulearg.
268 * @param[in] crulearg Argument array.
269 * @return Non-zero if the condition is true, zero if not.
271 static int crule_via(int numargs, void *crulearg[])
273 #if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
274 struct Client *acptr;
276 /* adapted from m_links */
277 for (acptr = GlobalClientList; acptr; acptr = cli_next(acptr))
279 if (!IsServer(acptr) && !IsMe(acptr))
281 if (match((char *)crulearg[1], cli_name(acptr)))
283 if (match((char *)crulearg[0],
284 cli_name(LocalClientArray[cli_fd(cli_from(acptr))])))
292 /** Check whether we have a local IRC operator.
293 * @param[in] numargs Number of valid args in \a crulearg.
294 * @param[in] crulearg Argument array.
295 * @return Non-zero if the condition is true, zero if not.
297 static int crule_directop(int numargs, void *crulearg[])
299 #if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
301 struct Client *acptr;
303 /* adapted from m_trace */
304 for (i = 0; i <= HighestFd; i++)
306 if (!(acptr = LocalClientArray[i]) || !IsAnOper(acptr))
314 /** Evaluate a connection rule.
315 * @param[in] rule Rule to evalute.
316 * @return Non-zero if the rule allows the connection, zero otherwise.
318 int crule_eval(struct CRuleNode* rule)
320 return (rule->funcptr(rule->numargs, rule->arg));
323 /** Perform an and-or-or test on crulearg[0] and crulearg[1].
324 * If crulearg[2] is non-NULL, it means do OR; if it is NULL, do AND.
325 * @param[in] numargs Number of valid args in \a crulearg.
326 * @param[in] crulearg Argument array.
327 * @return Non-zero if the condition is true, zero if not.
329 static int crule__andor(int numargs, void *crulearg[])
333 result1 = crule_eval(crulearg[0]);
334 if (crulearg[2]) /* or */
335 return (result1 || crule_eval(crulearg[1]));
337 return (result1 && crule_eval(crulearg[1]));
340 /** Logically invert the result of crulearg[0].
341 * @param[in] numargs Number of valid args in \a crulearg.
342 * @param[in] crulearg Argument array.
343 * @return Non-zero if the condition is true, zero if not.
345 static int crule__not(int numargs, void *crulearg[])
347 return (!crule_eval(crulearg[0]));
350 /** Scan an input token from \a ruleptr.
351 * @param[out] next_tokp Receives type of next token.
352 * @param[in,out] ruleptr Next readable character from input.
353 * @return Either CR_UNKNWTOK if the input was unrecognizable, else CR_NOERR.
355 static int crule_gettoken(int* next_tokp, const char** ruleptr)
359 *next_tokp = CR_UNKNOWN;
360 while (*next_tokp == CR_UNKNOWN)
361 switch (*(*ruleptr)++)
369 else if (pending == '&')
372 return (CR_UNKNWTOK);
377 else if (pending == '|')
380 return (CR_UNKNWTOK);
386 *next_tokp = CR_OPENPAREN;
389 *next_tokp = CR_CLOSEPAREN;
392 *next_tokp = CR_COMMA;
402 if ((IsAlpha(*(--(*ruleptr)))) || (**ruleptr == '*') ||
403 (**ruleptr == '?') || (**ruleptr == '.') || (**ruleptr == '-'))
404 *next_tokp = CR_WORD;
406 return (CR_UNKNWTOK);
412 /** Scan a word from \a ruleptr.
413 * @param[out] word Output buffer.
414 * @param[out] wordlenp Length of word written to \a word (not including terminating NUL).
415 * @param[in] maxlen Maximum number of bytes writable to \a word.
416 * @param[in,out] ruleptr Next readable character from input.
418 static void crule_getword(char* word, int* wordlenp, size_t maxlen, const char** ruleptr)
423 while ((size_t)(word_ptr - word) < maxlen
424 && (IsAlnum(**ruleptr)
425 || **ruleptr == '*' || **ruleptr == '?'
426 || **ruleptr == '.' || **ruleptr == '-'))
427 *word_ptr++ = *(*ruleptr)++;
429 *wordlenp = word_ptr - word;
432 /** Parse an entire rule.
433 * @param[in] rule Text form of rule.
434 * @return CRuleNode for rule, or NULL if there was a parse error.
436 struct CRuleNode* crule_parse(const char *rule)
438 const char* ruleptr = rule;
440 struct CRuleNode* ruleroot = 0;
441 int errcode = CR_NOERR;
443 if ((errcode = crule_gettoken(&next_tok, &ruleptr)) == CR_NOERR) {
444 if ((errcode = crule_parseorexpr(&ruleroot, &next_tok, &ruleptr)) == CR_NOERR) {
445 if (ruleroot != NULL) {
446 if (next_tok == CR_END)
449 errcode = CR_UNEXPCTTOK;
452 errcode = CR_EXPCTOR;
455 if (ruleroot != NULL)
456 crule_free(&ruleroot);
457 #if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
458 Debug((DEBUG_ERROR, "%s in rule: %s", crule_errstr[errcode], rule));
460 fprintf(stderr, "%s in rule: %s\n", crule_errstr[errcode], rule);
465 /** Parse an or expression.
466 * @param[out] orrootp Receives parsed node.
467 * @param[in,out] next_tokp Next input token type.
468 * @param[in,out] ruleptr Next input character.
469 * @return A crule_errcode value.
471 static int crule_parseorexpr(CRuleNodePtr * orrootp, int *next_tokp, const char** ruleptr)
473 int errcode = CR_NOERR;
474 CRuleNodePtr andexpr;
478 while (errcode == CR_NOERR)
480 errcode = crule_parseandexpr(&andexpr, next_tokp, ruleptr);
481 if ((errcode == CR_NOERR) && (*next_tokp == CR_OR))
483 orptr = (CRuleNodePtr) MyMalloc(sizeof(struct CRuleNode));
485 fprintf(stderr, "allocating or element at %ld\n", orptr);
487 orptr->funcptr = crule__andor;
489 orptr->arg[2] = (void *)1;
490 if (*orrootp != NULL)
492 (*orrootp)->arg[1] = andexpr;
493 orptr->arg[0] = *orrootp;
496 orptr->arg[0] = andexpr;
501 if (*orrootp != NULL)
505 (*orrootp)->arg[1] = andexpr;
510 (*orrootp)->arg[1] = NULL; /* so free doesn't seg fault */
511 return (CR_EXPCTAND);
520 if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
526 /** Parse an and expression.
527 * @param[out] androotp Receives parsed node.
528 * @param[in,out] next_tokp Next input token type.
529 * @param[in,out] ruleptr Next input character.
530 * @return A crule_errcode value.
532 static int crule_parseandexpr(CRuleNodePtr * androotp, int *next_tokp, const char** ruleptr)
534 int errcode = CR_NOERR;
535 CRuleNodePtr primary;
539 while (errcode == CR_NOERR)
541 errcode = crule_parseprimary(&primary, next_tokp, ruleptr);
542 if ((errcode == CR_NOERR) && (*next_tokp == CR_AND))
544 andptr = (CRuleNodePtr) MyMalloc(sizeof(struct CRuleNode));
546 fprintf(stderr, "allocating and element at %ld\n", andptr);
548 andptr->funcptr = crule__andor;
550 andptr->arg[2] = (void *)0;
551 if (*androotp != NULL)
553 (*androotp)->arg[1] = primary;
554 andptr->arg[0] = *androotp;
557 andptr->arg[0] = primary;
562 if (*androotp != NULL)
566 (*androotp)->arg[1] = primary;
571 (*androotp)->arg[1] = NULL; /* so free doesn't seg fault */
572 return (CR_EXPCTPRIM);
581 if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
587 /** Parse a primary expression.
588 * @param[out] primrootp Receives parsed node.
589 * @param[in,out] next_tokp Next input token type.
590 * @param[in,out] ruleptr Next input character.
591 * @return A crule_errcode value.
593 static int crule_parseprimary(CRuleNodePtr* primrootp, int *next_tokp, const char** ruleptr)
595 CRuleNodePtr *insertionp;
596 int errcode = CR_NOERR;
599 insertionp = primrootp;
600 while (errcode == CR_NOERR)
605 if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
607 if ((errcode = crule_parseorexpr(insertionp, next_tokp, ruleptr)) != CR_NOERR)
609 if (*insertionp == NULL)
611 errcode = CR_EXPCTAND;
614 if (*next_tokp != CR_CLOSEPAREN)
616 errcode = CR_EXPCTCLOSE;
619 errcode = crule_gettoken(next_tokp, ruleptr);
622 *insertionp = (CRuleNodePtr) MyMalloc(sizeof(struct CRuleNode));
624 fprintf(stderr, "allocating primary element at %ld\n", *insertionp);
626 (*insertionp)->funcptr = crule__not;
627 (*insertionp)->numargs = 1;
628 (*insertionp)->arg[0] = NULL;
629 insertionp = (CRuleNodePtr *) & ((*insertionp)->arg[0]);
630 if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
634 errcode = crule_parsefunction(insertionp, next_tokp, ruleptr);
637 if (*primrootp == NULL)
640 errcode = CR_EXPCTPRIM;
648 /** Parse a function call.
649 * @param[out] funcrootp Receives parsed node.
650 * @param[in,out] next_tokp Next input token type.
651 * @param[in,out] ruleptr Next input character.
652 * @return A crule_errcode value.
654 static int crule_parsefunction(CRuleNodePtr* funcrootp, int* next_tokp, const char** ruleptr)
656 int errcode = CR_NOERR;
657 char funcname[CR_MAXARGLEN];
662 crule_getword(funcname, &namelen, CR_MAXARGLEN - 1, ruleptr);
663 if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
665 if (*next_tokp == CR_OPENPAREN)
667 for (funcnum = 0;; funcnum++)
669 if (0 == ircd_strcmp(crule_funclist[funcnum].name, funcname))
671 if (crule_funclist[funcnum].name[0] == '\0')
672 return (CR_UNKNWFUNC);
674 if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
676 *funcrootp = (CRuleNodePtr) MyMalloc(sizeof(struct CRuleNode));
678 fprintf(stderr, "allocating function element at %ld\n", *funcrootp);
680 (*funcrootp)->funcptr = NULL; /* for freeing aborted trees */
682 crule_parsearglist(*funcrootp, next_tokp, ruleptr)) != CR_NOERR)
684 if (*next_tokp != CR_CLOSEPAREN)
685 return (CR_EXPCTCLOSE);
686 if ((crule_funclist[funcnum].reqnumargs != (*funcrootp)->numargs) &&
687 (crule_funclist[funcnum].reqnumargs != -1))
688 return (CR_ARGMISMAT);
689 if ((errcode = crule_gettoken(next_tokp, ruleptr)) != CR_NOERR)
691 (*funcrootp)->funcptr = crule_funclist[funcnum].funcptr;
695 return (CR_EXPCTOPEN);
698 /** Parse the argument list to a CRuleNode.
699 * @param[in,out] argrootp Node whos argument list is being populated.
700 * @param[in,out] next_tokp Next input token type.
701 * @param[in,out] ruleptr Next input character.
702 * @return A crule_errcode value.
704 static int crule_parsearglist(CRuleNodePtr argrootp, int *next_tokp, const char** ruleptr)
706 int errcode = CR_NOERR;
707 char *argelemp = NULL;
708 char currarg[CR_MAXARGLEN];
710 char word[CR_MAXARGLEN];
713 argrootp->numargs = 0;
715 while (errcode == CR_NOERR)
720 crule_getword(word, &wordlen, CR_MAXARGLEN - 1, ruleptr);
721 if (currarg[0] != '\0')
723 if ((arglen + wordlen) < (CR_MAXARGLEN - 1))
725 strcat(currarg, " ");
726 strcat(currarg, word);
727 arglen += wordlen + 1;
732 strcpy(currarg, word);
735 errcode = crule_gettoken(next_tokp, ruleptr);
738 #if !defined(CR_DEBUG) && !defined(CR_CHKCONF)
741 if (!BadPtr(currarg))
743 DupString(argelemp, currarg);
744 argrootp->arg[argrootp->numargs++] = (void *)argelemp;
746 if (*next_tokp != CR_COMMA)
749 errcode = crule_gettoken(next_tokp, ruleptr);
757 * This function is recursive.. I wish I knew a nonrecursive way but
758 * I dont. anyway, recursion is fun.. :)
759 * DO NOT CALL THIS FUNTION WITH A POINTER TO A NULL POINTER
760 * (ie: If *elem is NULL, you're doing it wrong - seg fault)
762 /** Free a connection rule and all its children.
763 * @param[in,out] elem Pointer to pointer to element to free. MUST NOT BE NULL.
765 void crule_free(struct CRuleNode** elem)
769 if ((*(elem))->funcptr == crule__not)
771 /* type conversions and ()'s are fun! ;) here have an asprin.. */
772 if ((*(elem))->arg[0] != NULL)
773 crule_free((struct CRuleNode**) &((*(elem))->arg[0]));
775 else if ((*(elem))->funcptr == crule__andor)
777 crule_free((struct CRuleNode**) &((*(elem))->arg[0]));
778 if ((*(elem))->arg[1] != NULL)
779 crule_free((struct CRuleNode**) &((*(elem))->arg[1]));
783 numargs = (*(elem))->numargs;
784 for (arg = 0; arg < numargs; arg++)
785 MyFree((*(elem))->arg[arg]);
788 fprintf(stderr, "freeing element at %ld\n", *elem);
795 /** Display a connection rule as text.
796 * @param[in] printelem Connection rule to display.
798 static void print_tree(CRuleNodePtr printelem)
802 if (printelem->funcptr == crule__not)
805 print_tree((CRuleNodePtr) printelem->arg[0]);
808 else if (printelem->funcptr == crule__andor)
811 print_tree((CRuleNodePtr) printelem->arg[0]);
812 if (printelem->arg[2])
816 print_tree((CRuleNodePtr) printelem->arg[1]);
821 for (funcnum = 0;; funcnum++)
823 if (printelem->funcptr == crule_funclist[funcnum].funcptr)
825 if (crule_funclist[funcnum].funcptr == NULL)
828 printf("%s(", crule_funclist[funcnum].name);
829 for (arg = 0; arg < printelem->numargs; arg++)
833 printf("%s", (char *)printelem->arg[arg]);
842 /** Read connection rules from stdin and display parsed forms as text.
851 while (fgets(indata, 256, stdin) != NULL)
853 indata[strlen(indata) - 1] = '\0'; /* lose the newline */
854 if ((rule = crule_parse(indata)) != NULL)
856 printf("equivalent rule: ");
857 print_tree((CRuleNodePtr) rule);