入侵检测系统首推开源项目snort,完善的匹配规则定义,灵活的插件扩充能力使其有强大的生命力,创建规则匹配的核心代码节选如下:
可以看出,在分解规则的时候,就已经用多个链表,将不同的规则进行分类了,可以根据分析的场景,有选择的去使用合适的规则,实现高效。
void ParseRule(char *prule, int inclevel) { char **toks; /* dbl ptr for mSplit call, holds rule tokens */ int num_toks; /* holds number of tokens found by mSplit */ int rule_type; /* rule type enumeration variable */ char rule[1024]; int protocol; RuleTreeNode proto_node; /* clean house */ bzero((char *) &proto_node, sizeof(RuleTreeNode)); /* chop off the <CR/LF> from the string */ strip(prule); /* expand all variables */ strcpy(rule, ExpandVars(prule)); /* break out the tokens from the rule string */ toks = mSplit(rule, " ", 10, &num_toks, 0); #ifdef DEBUG printf("[*] Rule start\n"); #endif /* figure out what we're looking at */ rule_type = RuleType(toks[0]); #ifdef DEBUG printf("Rule type: "); #endif /* handle non-rule entries */ switch (rule_type) { case RULE_PASS: #ifdef DEBUG printf("Pass\n"); #endif break; case RULE_LOG: #ifdef DEBUG printf("Log\n"); #endif break; case RULE_ALERT: #ifdef DEBUG printf("Alert\n"); #endif break; case RULE_INCLUDE: #ifdef DEBUG printf("Include\n"); #endif ParseRulesFile(toks[1], inclevel + 1); return; case RULE_VAR: #ifdef DEBUG printf("Variable\n"); #endif VarDefine(toks[1], toks[2]); return; case RULE_PREPROCESS: #ifdef DEBUG printf("Preprocessor\n"); #endif ParsePreprocessor(rule); return; case RULE_OUTPUT: #ifdef DEBUG printf("Output Plugin\n"); #endif ParseOutputPlugin(rule); return; case RULE_ACTIVATE: #ifdef DEBUG printf("Activation rule\n"); #endif break; case RULE_DYNAMIC: #ifdef DEBUG printf("Dynamic rule\n"); #endif break; case RULE_CONFIG: #ifdef DEBUG printf("Rule file config\n"); #endif ParseConfig(rule); return; case RULE_DECLARE: #ifdef DEBUG printf("Rule type declaration\n"); #endif ParseRuleTypeDeclaration(rule); return; case RULE_UNKNOWN: #ifdef DEBUG printf("Unknown rule type, might be declared\n"); #endif ParseDeclaredRuleType(rule); return; default: printf("Invalid input: %s\n", prule); return; } proto_node.type = rule_type; /* set the rule protocol */ protocol = WhichProto(toks[1]); /* Process the IP address and CIDR netmask */ /* changed version 1.2.1 */ /* * "any" IP's are now set to addr 0, netmask 0, and the normal rules are * applied instead of checking the flag */ /* * if we see a "!<ip number>" we need to set a flag so that we can * properly deal with it when we are processing packets */ if (*toks[2] == '!') /* we found a negated address */ { proto_node.flags |= EXCEPT_SRC_IP; ParseIP(&toks[2][1], (u_long *) & proto_node.sip, (u_long *) & proto_node.smask); } else { ParseIP(toks[2], (u_long *) & proto_node.sip, (u_long *) & proto_node.smask); } /* do the same for the port */ if (ParsePort(toks[3], (u_short *) & proto_node.hsp, (u_short *) & proto_node.lsp, toks[1], (int *) &proto_node.not_sp_flag)) { proto_node.flags |= ANY_SRC_PORT; } if (proto_node.not_sp_flag) proto_node.flags |= EXCEPT_SRC_PORT; /* New in version 1.3: support for bidirectional rules */ /* * this checks the rule "direction" token and sets the bidirectional flag * if the token = '<>' */ if (!strncmp("<>", toks[4], 2)) { #ifdef DEBUG printf("Bidirectional rule!\n"); #endif proto_node.flags |= BIDIRECTIONAL; } /* changed version 1.2.1 */ /* * "any" IP's are now set to addr 0, netmask 0, and the normal rules are * applied instead of checking the flag */ /* * if we see a "!<ip number>" we need to set a flag so that we can * properly deal with it when we are processing packets */ if (*toks[5] == '!') /* we found a negated address */ { #ifdef DEBUG printf("setting exception flag for dest IP\n"); #endif proto_node.flags |= EXCEPT_DST_IP; ParseIP(&toks[5][1], (u_long *) & proto_node.dip, (u_long *) & proto_node.dmask); } else ParseIP(toks[5], (u_long *) & proto_node.dip, (u_long *) & proto_node.dmask); if (ParsePort(toks[6], (u_short *) & proto_node.hdp, (u_short *) & proto_node.ldp, toks[1], (int *) &proto_node.not_dp_flag)) { proto_node.flags |= ANY_DST_PORT; } if (proto_node.not_dp_flag) proto_node.flags |= EXCEPT_DST_PORT; #ifdef DEBUG printf("proto_node.flags = 0x%X\n", proto_node.flags); printf("Processing Head Node....\n"); #endif switch (rule_type) { case RULE_ALERT: ProcessHeadNode(&proto_node, &Alert, protocol); break; case RULE_LOG: ProcessHeadNode(&proto_node, &Log, protocol); break; case RULE_PASS: ProcessHeadNode(&proto_node, &Pass, protocol); break; case RULE_ACTIVATE: ProcessHeadNode(&proto_node, &Activation, protocol); break; case RULE_DYNAMIC: ProcessHeadNode(&proto_node, &Dynamic, protocol); break; default: FatalError("Unable to determine rule type (%s) for processing, exiting!\n", toks[0]); } rule_count++; #ifdef DEBUG printf("Parsing Rule Options...\n"); #endif ParseRuleOptions(rule, rule_type, protocol); free(toks); return; } /**************************************************************************** * * Function: ProcessHeadNode(RuleTreeNode *, ListHead *, int) * * Purpose: Process the header block info and add to the block list if * necessary * * Arguments: test_node => data generated by the rules parsers * list => List Block Header refernece * protocol => ip protocol * * Returns: void function * ***************************************************************************/ void ProcessHeadNode(RuleTreeNode * test_node, ListHead * list, int protocol) { int match = 0; RuleTreeNode *rtn_idx; int count = 0; /* select the proper protocol list to attach the current rule to */ switch (protocol) { case IPPROTO_TCP: rtn_idx = list->TcpList; break; case IPPROTO_UDP: rtn_idx = list->UdpList; break; case IPPROTO_ICMP: rtn_idx = list->IcmpList; break; default: rtn_idx = NULL; break; } /* * if the list head is NULL (empty), make a new one and attach the * ListHead to it */ if (rtn_idx == NULL) { head_count++; switch (protocol) { case IPPROTO_TCP: list->TcpList = (RuleTreeNode *) calloc(sizeof(RuleTreeNode), sizeof(char)); rtn_tmp = list->TcpList; break; case IPPROTO_UDP: list->UdpList = (RuleTreeNode *) calloc(sizeof(RuleTreeNode), sizeof(char)); rtn_tmp = list->UdpList; break; case IPPROTO_ICMP: list->IcmpList = (RuleTreeNode *) calloc(sizeof(RuleTreeNode), sizeof(char)); rtn_tmp = list->IcmpList; break; } /* copy the prototype header data into the new node */ XferHeader(test_node, rtn_tmp); rtn_tmp->head_node_number = head_count; /* null out the down (options) pointer */ rtn_tmp->down = NULL; /* add the function list to the new rule */ SetupRTNFuncList(rtn_tmp); /* add link to parent listhead */ rtn_tmp->listhead = list; return; } /* see if this prototype node matches any of the existing header nodes */ match = TestHeader(rtn_idx, test_node); while ((rtn_idx->right != NULL) && !match) { count++; match = TestHeader(rtn_idx, test_node); if (!match) rtn_idx = rtn_idx->right; else break; } /* * have to check this twice since my loop above exits early, which sucks * but it's not performance critical */ match = TestHeader(rtn_idx, test_node); /* * if it doesn't match any of the existing nodes, make a new node and * stick it at the end of the list */ if (!match) { #ifdef DEBUG printf("Building New Chain head node\n"); #endif head_count++; /* build a new node */ rtn_idx->right = (RuleTreeNode *) calloc(sizeof(RuleTreeNode), sizeof(char)); /* set the global ptr so we can play with this from anywhere */ rtn_tmp = rtn_idx->right; /* uh oh */ if (rtn_tmp == NULL) { FatalError("ERROR: Unable to allocate Rule Head Node!!\n"); } /* copy the prototype header info into the new header block */ XferHeader(test_node, rtn_tmp); rtn_tmp->head_node_number = head_count; rtn_tmp->down = NULL; /* initialize the function list for the new RTN */ SetupRTNFuncList(rtn_tmp); /* add link to parent listhead */ rtn_tmp->listhead = list; #ifdef DEBUG printf("New Chain head flags = 0x%X\n", rtn_tmp->flags); #endif } else { rtn_tmp = rtn_idx; #ifdef DEBUG printf("Chain head %d flags = 0x%X\n", count, rtn_tmp->flags); #endif #ifdef DEBUG printf("Adding options to chain head %d\n", count); #endif } } /**************************************************************************** * * Function: AddRuleFuncToList(int (*func)(), RuleTreeNode *) * * Purpose: Adds RuleTreeNode associated detection functions to the * current rule's function list * * Arguments: *func => function pointer to the detection function * rtn => pointer to the current rule * * Returns: void function * ***************************************************************************/ void AddRuleFuncToList(int (*func) (Packet *, struct _RuleTreeNode *, struct _RuleFpList *), RuleTreeNode * rtn) { RuleFpList *idx; #ifdef DEBUG printf("Adding new rule to list\n"); #endif idx = rtn->rule_func; if (idx == NULL) { rtn->rule_func = (RuleFpList *) calloc(sizeof(RuleFpList), sizeof(char)); rtn->rule_func->RuleHeadFunc = func; } else { while (idx->next != NULL) idx = idx->next; idx->next = (RuleFpList *) calloc(sizeof(RuleFpList), sizeof(char)); idx = idx->next; idx->RuleHeadFunc = func; } } /**************************************************************************** * * Function: SetupRTNFuncList(RuleTreeNode *) * * Purpose: Configures the function list for the rule header detection * functions (addrs and ports) * * Arguments: rtn => the pointer to the current rules list entry to attach to * * Returns: void function * ***************************************************************************/ void SetupRTNFuncList(RuleTreeNode * rtn) { #ifdef DEBUG printf("Initializing RTN function list!\n"); printf("Functions: "); #endif if (rtn->flags & BIDIRECTIONAL) { #ifdef DEBUG printf("CheckBidirectional->\n"); #endif AddRuleFuncToList(CheckBidirectional, rtn); } else { /* link in the proper IP address detection function */ /* * the in-line "if" determines whether or not the negation operator * has been set for this rule and tells the AddrToFunc call which * function it should be linking in */ AddrToFunc(rtn, rtn->sip, rtn->smask, (rtn->flags & EXCEPT_SRC_IP ? 1 : 0), SRC); /* last verse, same as the first (but for dest IP) ;) */ AddrToFunc(rtn, rtn->dip, rtn->dmask, (rtn->flags & EXCEPT_DST_IP ? 1 : 0), DST); /* Attach the proper port checking function to the function list */ /* * the in-line "if's" check to see if the "any" or "not" flags have * been set so the PortToFunc call can determine which port testing * function to attach to the list */ PortToFunc(rtn, (rtn->flags & ANY_SRC_PORT ? 1 : 0), (rtn->flags & EXCEPT_SRC_PORT ? 1 : 0), SRC); /* as above */ PortToFunc(rtn, (rtn->flags & ANY_DST_PORT ? 1 : 0), (rtn->flags & EXCEPT_DST_PORT ? 1 : 0), DST); } #ifdef DEBUG printf("RuleListEnd\n"); #endif /* tack the end (success) function to the list */ AddRuleFuncToList(RuleListEnd, rtn); } /**************************************************************************** * * Function: AddrToFunc(RuleTreeNode *, u_long, u_long, int, int) * * Purpose: Links the proper IP address testing function to the current RTN * based on the address, netmask, and addr flags * * Arguments: rtn => the pointer to the current rules list entry to attach to * ip => IP address of the current rule * mask => netmask of the current rule * exception_flag => indicates that a "!" has been set for this * address * mode => indicates whether this is a rule for the source * or destination IP for the rule * * Returns: void function * ***************************************************************************/ void AddrToFunc(RuleTreeNode * rtn, u_long ip, u_long mask, int exception_flag, int mode) { /* * if IP and mask are both 0, this is a "any" IP and we don't need to * check it */ if ((ip == 0) && (mask == 0)) return; /* if the exception flag is up, test with the exception function */ if (exception_flag) { switch (mode) { case SRC: #ifdef DEBUG printf("CheckSrcIPNotEq -> "); #endif AddRuleFuncToList(CheckSrcIPNotEq, rtn); break; case DST: #ifdef DEBUG printf("CheckDstIPNotEq -> "); #endif AddRuleFuncToList(CheckDstIPNotEq, rtn); break; } return; } switch (mode) { case SRC: #ifdef DEBUG printf("CheckSrcIPEqual -> "); #endif AddRuleFuncToList(CheckSrcIPEqual, rtn); break; case DST: #ifdef DEBUG printf("CheckDstIPEqual -> "); #endif AddRuleFuncToList(CheckDstIPEqual, rtn); break; } } /**************************************************************************** * * Function: PortToFunc(RuleTreeNode *, int, int, int) * * Purpose: Links in the port analysis function for the current rule * * Arguments: rtn => the pointer to the current rules list entry to attach to * any_flag => accept any port if set * except_flag => indicates negation (logical NOT) of the test * mode => indicates whether this is a rule for the source * or destination port for the rule * * Returns: void function * ***************************************************************************/ void PortToFunc(RuleTreeNode * rtn, int any_flag, int except_flag, int mode) { /* * if the any flag is set we don't need to perform any test to match on * this port */ if (any_flag) return; /* if the except_flag is up, test with the "NotEq" funcs */ if (except_flag) { switch (mode) { case SRC: #ifdef DEBUG printf("CheckSrcPortNotEq -> "); #endif AddRuleFuncToList(CheckSrcPortNotEq, rtn); break; case DST: #ifdef DEBUG printf("CheckDstPortNotEq -> "); #endif AddRuleFuncToList(CheckDstPortNotEq, rtn); break; } return; } /* default to setting the straight test function */ switch (mode) { case SRC: #ifdef DEBUG printf("CheckSrcPortEqual -> "); #endif AddRuleFuncToList(CheckSrcPortEqual, rtn); break; case DST: #ifdef DEBUG printf("CheckDstPortEqual -> "); #endif AddRuleFuncToList(CheckDstPortEqual, rtn); break; } return; } /**************************************************************************** * * Function: AddOptFuncToList(int (*func)(), OptTreeNode *) * * Purpose: Links the option detection module to the OTN * * Arguments: (*func)() => function pointer to the detection module * otn => pointer to the current OptTreeNode * * Returns: void function * ***************************************************************************/ void AddOptFuncToList(int (*func) (Packet *, struct _OptTreeNode *, struct _OptFpList *), OptTreeNode * otn) { OptFpList *idx; /* index pointer */ #ifdef DEBUG printf("Adding new rule to list\n"); #endif /* set the index pointer to the start of this OTN's function list */ idx = otn->opt_func; /* if there are no nodes on the function list... */ if (idx == NULL) { /* calloc the list head */ otn->opt_func = (OptFpList *) calloc(sizeof(OptFpList), sizeof(char)); if (otn->opt_func == NULL) { FatalError("ERROR => AddOptFuncToList new node calloc failed: %s\n", strerror(errno)); } /* set the head function */ otn->opt_func->OptTestFunc = func; } else { /* walk to the end of the list */ while (idx->next != NULL) { idx = idx->next; } /* allocate a new node on the end of the list */ idx->next = (OptFpList *) calloc(sizeof(OptFpList), sizeof(char)); if (idx->next == NULL) { FatalError("ERROR => AddOptFuncToList new node calloc failed: %s\n", strerror(errno)); } /* move up to the new node */ idx = idx->next; /* link the function to the new node */ idx->OptTestFunc = func; #ifdef DEBUG printf("Set OptTestFunc to %p\n", func); #endif } } /**************************************************************************** * * Function: ParsePreprocessor(char *) * * Purpose: Walks the preprocessor function list looking for the user provided * keyword. Once found, call the preprocessor's initialization * function. * * Arguments: rule => the preprocessor initialization string from the rules file * * Returns: void function * ***************************************************************************/ void ParsePreprocessor(char *rule) { char **toks; /* pointer to the tokenized array parsed from * the rules list */ char **pp_head; /* parsed keyword list, with preprocessor * keyword being the 2nd element */ char *funcname; /* the ptr to the actual preprocessor keyword */ char *pp_args = NULL; /* parsed list of arguments to the * preprocessor */ int num_toks; /* number of tokens returned by the mSplit * function */ int found = 0; /* flag var */ PreprocessKeywordList *pl_idx; /* index into the preprocessor * keyword/func list */ /* break out the arguments from the keywords */ toks = mSplit(rule, ":", 2, &num_toks, '\\'); if (num_toks >= 1) { #ifdef DEBUG printf("toks[1] = %s\n", toks[1]); #endif /* the args are everything after the ":" */ pp_args = toks[1]; } /* split the head section for the preprocessor keyword */ pp_head = mSplit(toks[0], " ", 2, &num_toks, '\\'); /* set a pointer to the actual keyword */ funcname = pp_head[1]; /* set the index to the head of the keyword list */ pl_idx = PreprocessKeywords; /* walk the keyword list */ while (pl_idx != NULL) { #ifdef DEBUG printf("comparing: \"%s\" => \"%s\"\n", funcname, pl_idx->entry.keyword); #endif /* compare the keyword against the current list element's keyword */ if (!strcasecmp(funcname, pl_idx->entry.keyword)) { pl_idx->entry.func(pp_args); found = 1; } if (!found) { pl_idx = pl_idx->next; } else break; } if (!found) printf("\n*WARNING*: unknown preprocessor \"%s\", ignoring!\n\n", funcname); } void AddFuncToPreprocList(void (*func) (Packet *)) { PreprocessFuncNode *idx; idx = PreprocessList; if (idx == NULL) { PreprocessList = (PreprocessFuncNode *) calloc(sizeof(PreprocessFuncNode), sizeof(char)); PreprocessList->func = func; } else { while (idx->next != NULL) idx = idx->next; idx->next = (PreprocessFuncNode *) calloc(sizeof(PreprocessFuncNode), sizeof(char)); idx = idx->next; idx->func = func; } return; } void ParseOutputPlugin(char *rule) { char **toks; char **pp_head; char *funcname; char *pp_args = NULL; int num_toks; int found = 0; OutputKeywordList *pl_idx; toks = mSplit(rule, ":", 2, &num_toks, '\\'); if (num_toks >= 1) { pp_args = toks[1]; } pp_head = mSplit(toks[0], " ", 2, &num_toks, '\\'); funcname = pp_head[1]; pl_idx = OutputKeywords; while (pl_idx != NULL) { #ifdef DEBUG printf("comparing: \"%s\" => \"%s\"\n", funcname, pl_idx->entry.keyword); #endif if (!strcasecmp(funcname, pl_idx->entry.keyword)) { switch (pl_idx->entry.node_type) { case NT_OUTPUT_ALERT: if (!pv.alert_cmd_override) { if (AlertFunc == NULL) { AlertFunc = CallAlertPlugins; } /* call the configuration function for the plugin */ pl_idx->entry.func(pp_args); } else { ErrorMessage("WARNING: command line overrides rules file alert plugin!\n"); } break; case NT_OUTPUT_LOG: if (!pv.log_cmd_override) { if (LogFunc == NULL) { LogFunc = CallLogPlugins; } /* call the configuration function for the plugin */ pl_idx->entry.func(pp_args); } else { ErrorMessage("WARNING: command line overrides rules file logging plugin!\n"); } break; } found = 1; } if (!found) { pl_idx = pl_idx->next; } else break; } if (!found) { printf("\n*WARNING*: unknown output plugin \"%s\", ignoring!\n\n", funcname); } } /* * frees the existing OutputList ands sets it a single node for the * function argument */ void SetOutputList(void (*func) (Packet *, char *, void *), char node_type, void *arg) { OutputFuncNode *idx; OutputFuncNode *prev; switch (node_type) { case NT_OUTPUT_ALERT: prev = AlertList; break; case NT_OUTPUT_LOG: prev = LogList; break; default: return; } while (prev != NULL) { idx = prev->next; free(prev); prev = idx; } return AddFuncToOutputList(func, node_type, arg); } void AddFuncToOutputList(void (*func) (Packet *, char *, void *), char node_type, void *arg) { switch (node_type) { case NT_OUTPUT_ALERT: if (head_tmp != NULL) head_tmp->AlertList = AppendOutputFuncList(func, arg, head_tmp->AlertList); else AlertList = AppendOutputFuncList(func, arg, AlertList); break; case NT_OUTPUT_LOG: if (head_tmp != NULL) head_tmp->LogList = AppendOutputFuncList(func, arg, head_tmp->LogList); else LogList = AppendOutputFuncList(func, arg, LogList); break; default: /* just to be error-prone */ FatalError("Unknown nodetype: %i. Possible bug. please report\n", node_type); } } OutputFuncNode *AppendOutputFuncList(void (*func) (Packet *, char *, void *), void *arg, OutputFuncNode * list) { OutputFuncNode *idx = list; if (idx == NULL) { idx = (OutputFuncNode *) calloc(sizeof(OutputFuncNode), sizeof(char)); idx->func = func; idx->arg = arg; list = idx; } else { while (idx->next != NULL) idx = idx->next; idx->next = (OutputFuncNode *) calloc(sizeof(OutputFuncNode), sizeof(char)); idx = idx->next; idx->func = func; idx->arg = arg; } idx->next = NULL; return list; } /**************************************************************************** * * Function: ParseRuleOptions(char *, int) * * Purpose: Process an individual rule's options and add it to the * appropriate rule chain * * Arguments: rule => rule string * rule_type => enumerated rule type (alert, pass, log) * * Returns: void function * ***************************************************************************/ void ParseRuleOptions(char *rule, int rule_type, int protocol) { char **toks = NULL; char **opts; char *idx; char *aux; int num_toks; int i; int num_opts; int found = 0; OptTreeNode *otn_idx; KeywordXlateList *kw_idx; /* set the OTN to the beginning of the list */ otn_idx = rtn_tmp->down; /* * make a new one and stick it either at the end of the list or hang it * off the RTN pointer */ if (otn_idx != NULL) { /* loop to the end of the list */ while (otn_idx->next != NULL) { otn_idx = otn_idx->next; } /* setup the new node */ otn_idx->next = (OptTreeNode *) calloc(sizeof(OptTreeNode), sizeof(char)); /* set the global temp ptr */ otn_tmp = otn_idx->next; if (otn_tmp == NULL) { FatalError("ERROR: Unable to alloc OTN: %s", strerror(errno)); } otn_tmp->next = NULL; opt_count++; } else { /* first entry on the chain, make a new node and attach it */ otn_idx = (OptTreeNode *) calloc(sizeof(OptTreeNode), sizeof(char)); bzero((char *) otn_idx, sizeof(OptTreeNode)); otn_tmp = otn_idx; if (otn_tmp == NULL) { FatalError("ERROR: Unable to alloc OTN!\n"); } otn_tmp->next = NULL; rtn_tmp->down = otn_tmp; opt_count++; } otn_tmp->chain_node_number = opt_count; otn_tmp->type = rule_type; /* add link to parent RuleTreeNode */ otn_tmp->rtn = rtn_tmp; /* find the start of the options block */ idx = index(rule, '('); i = 0; if (idx != NULL) { idx++; /* find the end of the options block */ aux = strrchr(idx, ')'); /* get rid of the trailing ")" */ if (aux == NULL) { FatalError("ERROR: problems parsing rule file.\n"); } *aux = 0; /* seperate all the options out, the seperation token is a semicolon */ /* * NOTE: if you want to include a semicolon in the content of your * rule, it must be preceeded with a '\' */ toks = mSplit(idx, ";", 10, &num_toks, '\\'); #ifdef DEBUG printf(" Got %d tokens\n", num_toks); #endif /* decrement the number of toks */ num_toks--; #ifdef DEBUG printf("Parsing options list: "); #endif while (num_toks) { #ifdef DEBUG printf(" option: %s\n", toks[i]); #endif /* break out the option name from its data */ opts = mSplit(toks[i], ":", 4, &num_opts, '\\'); #ifdef DEBUG printf(" option name: %s\n", opts[0]); printf(" option args: %s\n", opts[1]); #endif /* advance to the beginning of the data (past the whitespace) */ while (isspace((int) *opts[0])) opts[0]++; /* figure out which option tag we're looking at */ if (!strncasecmp(opts[0], "msg", 3)) { ParseMessage(opts[1]); } else if (!strncasecmp(opts[0], "logto", 5)) { ParseLogto(opts[1]); } else if (!strncasecmp(opts[0], "activates", 9)) { ParseActivates(opts[1]); dynamic_rules_present++; } else if (!strncasecmp(opts[0], "activated_by", 12)) { ParseActivatedBy(opts[1]); dynamic_rules_present++; } else if (!strncasecmp(opts[0], "count", 5)) { if (otn_tmp->type != RULE_DYNAMIC) FatalError("The \"count\" option may only be used with the dynamic rule type!\n"); ParseCount(opts[1]); } #ifdef ENABLE_RESPONSE else if (!strncasecmp(opts[0], "resp", 4)) { otn_tmp->response_flag = RESP_RST_SND; ParseResponse(opts[1]); } #endif else { kw_idx = KeywordList; found = 0; while (kw_idx != NULL) { #ifdef DEBUG printf("comparing: \"%s\" => \"%s\"\n", opts[0], kw_idx->entry.keyword); #endif if (!strcasecmp(opts[0], kw_idx->entry.keyword)) { kw_idx->entry.func(opts[1], otn_tmp, protocol); found = 1; #ifdef DEBUG printf("%s->", kw_idx->entry.keyword); #endif } if (!found) { kw_idx = kw_idx->next; } else break; } if (!found) { if (!strcasecmp(opts[0], "minfrag")) { FatalError("\nERROR: %s (%d) => Minfrag is no longer a rule option, it is a\npreprocessor (please remove it from your rules file). See RULES.SAMPLE or\nsnort-lib for examples of using the new preprocessors!\n", file_name, file_line); } else { FatalError("\nERROR: %s (%d) => Unknown keyword \"%s\" in rule!\n", file_name, file_line, opts[0]); } } } free(opts); --num_toks; i++; } #ifdef DEBUG printf("OptListEnd\n"); #endif AddOptFuncToList(OptListEnd, otn_tmp); } else { #ifdef DEBUG printf("OptListEnd\n"); #endif AddOptFuncToList(OptListEnd, otn_tmp); } free(toks); } /**************************************************************************** * * Function: RuleType(char *) * * Purpose: Determine what type of rule is being processed and return its * equivalent value * * Arguments: func => string containing the rule type * * Returns: The rule type designation * ***************************************************************************/ int RuleType(char *func) { if (func == NULL) { FatalError("ERROR line %s (%d) => Unknown rule type (%s)\n", file_name, file_line, func); } if (!strncasecmp(func, "log", 3)) return RULE_LOG; if (!strncasecmp(func, "alert", 5)) return RULE_ALERT; if (!strncasecmp(func, "pass", 4)) return RULE_PASS; if (!strncasecmp(func, "var", 3)) return RULE_VAR; if (!strncasecmp(func, "include", 7)) return RULE_INCLUDE; if (!strncasecmp(func, "preprocessor", 12)) return RULE_PREPROCESS; if (!strncasecmp(func, "output", 6)) return RULE_OUTPUT; if (!strncasecmp(func, "activate", 8)) return RULE_ACTIVATE; if (!strncasecmp(func, "dynamic", 7)) return RULE_DYNAMIC; if (!strncasecmp(func, "config", 6)) return RULE_CONFIG; if (!strncasecmp(func, "ruletype", 8)) return RULE_DECLARE; return RULE_UNKNOWN; } /**************************************************************************** * * Function: WhichProto(char *) * * Purpose: Figure out which protocol the current rule is talking about * * Arguments: proto_str => the protocol string * * Returns: The integer value of the protocol * ***************************************************************************/ int WhichProto(char *proto_str) { if (!strncasecmp(proto_str, "tcp", 3)) return IPPROTO_TCP; if (!strncasecmp(proto_str, "udp", 3)) return IPPROTO_UDP; if (!strncasecmp(proto_str, "icmp", 4)) return IPPROTO_ICMP; /* * if we've gotten here, we have a protocol string we din't recognize and * should exit */ FatalError("ERROR %s (%d) => Bad protocol: %s\n", file_name, file_line, proto_str); return 0; } /**************************************************************************** * * Function: ParseIP(char *, u_long *, u_long *) * * Purpose: Convert a supplied IP address to it's network order 32-bit long value. Also convert the CIDR block notation into a real * netmask. * * Arguments: addr => address string to convert * ip_addr => storage point for the converted ip address * netmask => storage point for the converted netmask * * Returns: 0 for normal addresses, 1 for an "any" address * ***************************************************************************/ int ParseIP(char *paddr, u_long * ip_addr, u_long * netmask) { char **toks; /* token dbl buffer */ int num_toks; /* number of tokens found by mSplit() */ int cidr = 1; /* is network expressed in CIDR format */ int nmask; /* netmask temporary storage */ char *addr; /* string to parse, eventually a * variable-contents */ struct hostent *host_info; /* various struct pointers for stuff */ struct sockaddr_in sin; /* addr struct */ /* check for variable */ if (!strncmp(paddr, "{1}quot;, 1)) { if ((addr = VarGet(paddr + 1)) == NULL) { FatalError("ERROR %s (%d) => Undefined variable %s\n", file_name, file_line, paddr); } } else addr = paddr; /* check for wildcards */ if (!strncasecmp(addr, "any", 3)) { *ip_addr = 0; *netmask = 0; return 1; } /* break out the CIDR notation from the IP address */ toks = mSplit(addr, "/", 2, &num_toks, 0); /* "/" was not used as a delimeter, try ":" */ if (num_toks == 1) toks = mSplit(addr, ":", 2, &num_toks, 0); /* * if we have a mask spec and it is more than two characters long, assume * it is netmask format */ if ((num_toks > 1) && strlen(toks[1]) > 2) cidr = 0; switch (num_toks) { case 1: *netmask = netmasks[32]; break; case 2: if (cidr) { /* convert the CIDR notation into a real live netmask */ nmask = atoi(toks[1]); /* it's pain to differ whether toks[1] is correct if netmask */ /* is /0, so we deploy some sort of evil hack with isdigit */ if (!isdigit((int) toks[1][0])) nmask = -1; if ((nmask > -1) && (nmask < 33)) { *netmask = netmasks[nmask]; } else { FatalError("ERROR %s (%d) => Invalid CIDR block for IP addr %s\n", file_name, file_line, addr); } } else { /* convert the netmask into its 32-bit value */ /* broadcast address fix from Steve Beaty <[email protected]> */ /* * * if the address is the (v4) broadcast address, inet_addr * * returns -1 which usually signifies an error, but in the * * broadcast address case, is correct. we'd use inet_aton() * * here, but it's less portable. */ if (!strncmp(toks[1], "255.255.255.255", 15)) { *netmask = INADDR_BROADCAST; } else if ((*netmask = inet_addr(toks[1])) == -1) { FatalError("ERROR %s (%d) => Rule netmask (%s) didn't x-late, WTF?\n", file_name, file_line, toks[1]); } } break; default: FatalError("ERROR %s (%d) => Unrecognized IP address/netmask %s\n", file_name, file_line, addr); break; } #ifndef WORDS_BIGENDIAN /* * since PC's store things the "wrong" way, shuffle the bytes into the * right order. Non-CIDR netmasks are already correct. */ if (cidr) { *netmask = htonl(*netmask); } #endif /* convert names to IP addrs */ if (isalpha((int) toks[0][0])) { /* get the hostname and fill in the host_info struct */ if ((host_info = gethostbyname(toks[0]))) { bcopy(host_info->h_addr, (char *) &sin.sin_addr, host_info->h_length); } else if ((sin.sin_addr.s_addr = inet_addr(toks[0])) == INADDR_NONE) { FatalError("ERROR %s (%d) => Couldn't resolve hostname %s\n", file_name, file_line, toks[0]); } *ip_addr = ((u_long) (sin.sin_addr.s_addr) & (*netmask)); return 1; } /* convert the IP addr into its 32-bit value */ /* broadcast address fix from Steve Beaty <[email protected]> */ /* * * if the address is the (v4) broadcast address, inet_addr returns -1 * * which usually signifies an error, but in the broadcast address case, * * is correct. we'd use inet_aton() here, but it's less portable. */ if (!strncmp(toks[0], "255.255.255.255", 15)) { *ip_addr = INADDR_BROADCAST; } else if ((*ip_addr = inet_addr(toks[0])) == -1) { FatalError("ERROR %s (%d) => Rule IP addr (%s) didn't x-late, WTF?\n", file_name, file_line, toks[0]); } else { /* set the final homenet address up */ *ip_addr = ((u_long) (*ip_addr) & (*netmask)); } free(toks); return 0; } /**************************************************************************** * * Function: ParsePort(char *, u_short *) * * Purpose: Convert the port string over to an integer value * * Arguments: prule_port => port rule string * port => converted integer value of the port * * Returns: 0 for a normal port number, 1 for an "any" port * ***************************************************************************/ int ParsePort(char *prule_port, u_short * hi_port, u_short * lo_port, char *proto, int *not_flag) { char **toks; /* token dbl buffer */ int num_toks; /* number of tokens found by mSplit() */ char *rule_port; /* port string */ *not_flag = 0; /* check for variable */ if (!strncmp(prule_port, "{1}quot;, 1)) { if ((rule_port = VarGet(prule_port + 1)) == NULL) { FatalError("ERROR %s (%d) => Undefined variable %s\n", file_name, file_line, prule_port); } } else rule_port = prule_port; /* check for wildcards */ if (!strncasecmp(rule_port, "any", 3)) { *hi_port = 0; *lo_port = 0; return 1; } if (rule_port[0] == '!') { *not_flag = 1; rule_port++; } if (rule_port[0] == ':') { *lo_port = 0; } toks = mSplit(rule_port, ":", 2, &num_toks, 0); switch (num_toks) { case 1: *hi_port = ConvPort(toks[0], proto); if (rule_port[0] == ':') { *lo_port = 0; } else { *lo_port = *hi_port; if (index(rule_port, ':') != NULL) { *hi_port = 65535; } } return 0; case 2: *lo_port = ConvPort(toks[0], proto); if (toks[1][0] == 0) *hi_port = 65535; else *hi_port = ConvPort(toks[1], proto); return 0; default: FatalError("ERROR %s (%d) => port conversion failed on \"%s\"\n", file_name, file_line, rule_port); } return 0; } /**************************************************************************** * * Function: ConvPort(char *, char *) * * Purpose: Convert the port string over to an integer value * * Arguments: port => port string * proto => converted integer value of the port * * Returns: the port number * ***************************************************************************/ int ConvPort(char *port, char *proto) { int conv; /* storage for the converted number */ struct servent *service_info; /* * convert a "word port" (http, ftp, imap, whatever) to its corresponding * numeric port value */ if (isalpha((int) port[0]) != 0) { service_info = getservbyname(port, proto); if (service_info != NULL) { conv = ntohs(service_info->s_port); return conv; } else { FatalError("ERROR %s (%d) => getservbyname() failed on \"%s\"\n", file_name, file_line, port); } } if (!isdigit((int) port[0])) { FatalError("ERROR %s (%d) => Invalid port: %s\n", file_name, file_line, port); } /* convert the value */ conv = atoi(port); /* make sure it's in bounds */ if ((conv >= 0) && (conv < 65536)) { return conv; } else { FatalError("ERROR %s (%d) => bad port number: %s", file_name, file_line, port); } return 0; } /**************************************************************************** * * Function: ParseMessage(char *) * * Purpose: Stuff the alert message onto the rule * * Arguments: msg => the msg string * * Returns: void function * ***************************************************************************/ void ParseMessage(char *msg) { char *ptr; char *end; int size; /* figure out where the message starts */ ptr = index(msg, '"'); if (ptr == NULL) { ptr = msg; } else ptr++; end = index(ptr, '"'); if (end != NULL) *end = 0; while (isspace((int) *ptr)) ptr++; /* find the end of the alert string */ size = strlen(msg) + 1; /* alloc space for the string and put it in the rule */ if (size > 0) { otn_tmp->message = (char *) calloc((sizeof(char) * size), sizeof(char)); strncpy(otn_tmp->message, ptr, size); otn_tmp->message[size - 1] = 0; #ifdef DEBUG printf("Rule message set to: %s\n", otn_tmp->message); #endif } else { ErrorMessage("ERROR %s (%d): bad alert message size %d\n", file_name, file_line, size); } } /**************************************************************************** * * Function: ParseLogto(char *) * * Purpose: stuff the special log filename onto the proper rule option * * Arguments: filename => the file name * * Returns: void function * ***************************************************************************/ void ParseLogto(char *filename) { char *sptr; char *eptr; /* grab everything between the starting " and the end one */ sptr = index(filename, '"'); eptr = strrchr(filename, '"'); if (sptr != NULL && eptr != NULL) { /* increment past the first quote */ sptr++; /* zero out the second one */ *eptr = 0; } else { sptr = filename; } /* malloc up a nice shiny clean buffer */ otn_tmp->logto = (char *) calloc(strlen(sptr) + 1, sizeof(char)); bzero((char *) otn_tmp->logto, strlen(sptr) + 1); strncpy(otn_tmp->logto, sptr, strlen(sptr)); } #ifdef ENABLE_RESPONSE /**************************************************************************** * * Function: ParseResponse(char *) * * Purpose: Figure out how to handle hostile connection attempts * * Arguments: type => string of comma-sepatared modifiers * * Returns: void function * ***************************************************************************/ void ParseResponse(char *type) { char *p; while (isspace((int) *type)) type++; if (!type || !(*type)) return; otn_tmp->response_flag = 0; p = strtok(type, ","); while (p) { if (!strncasecmp(p, "rst_snd", 7)) otn_tmp->response_flag |= RESP_RST_SND; else if (!strncasecmp(p, "rst_rcv", 7)) otn_tmp->response_flag |= RESP_RST_RCV; else if (!strncasecmp(p, "rst_all", 7)) otn_tmp->response_flag |= (RESP_RST_SND | RESP_RST_RCV); else if (!strncasecmp(p, "icmp_net", 8)) otn_tmp->response_flag |= RESP_BAD_NET; else if (!strncasecmp(p, "icmp_host", 9)) otn_tmp->response_flag |= RESP_BAD_HOST; else if (!strncasecmp(p, "icmp_port", 9)) otn_tmp->response_flag |= RESP_BAD_PORT; else if (!strncasecmp(p, "icmp_all", 9)) otn_tmp->response_flag |= (RESP_BAD_NET | RESP_BAD_HOST | RESP_BAD_PORT); else { FatalError("ERROR %s (%d): invalid response modifier: %s\n", file_name, file_line, p); } p = strtok(NULL, ","); } } #endif /**************************************************************************** * * Function: ParseActivates(char *) * * Purpose: Set an activation link record * * Arguments: act_num => rule number to be activated * * Returns: void function * ****************************************************************************/ void ParseActivates(char *act_num) { /* * allocate a new node on the RTN get rid of whitespace at the front of * the list */ while (!isdigit((int) *act_num)) act_num++; otn_tmp->activates = atoi(act_num); } /**************************************************************************** * * Function: ParseActivatedBy(char *) * * Purpose: Set an activation link record * * Arguments: act_by => rule number to be activated * * Returns: void function * ****************************************************************************/ void ParseActivatedBy(char *act_by) { ActivateList *al_ptr; al_ptr = rtn_tmp->activate_list; if (al_ptr == NULL) { rtn_tmp->activate_list = (ActivateList *) calloc(sizeof(ActivateList), sizeof(char)); if (rtn_tmp->activate_list == NULL) { FatalError("ERROR: ParseActivatedBy() calloc failed: %s\n", strerror(errno)); } al_ptr = rtn_tmp->activate_list; } else { while (al_ptr->next != NULL) { al_ptr = al_ptr->next; } al_ptr->next = (ActivateList *) calloc(sizeof(ActivateList), sizeof(char)); al_ptr = al_ptr->next; if (al_ptr == NULL) { FatalError("ERROR: ParseActivatedBy() calloc failed: %s\n", strerror(errno)); } } /* get rid of whitespace at the front of the list */ while (!isdigit((int) *act_by)) act_by++; /* set the RTN list node number */ al_ptr->activated_by = atoi(act_by); /* set the OTN list node number */ otn_tmp->activated_by = atoi(act_by); return; } void ParseCount(char *num) { while (!isdigit((int) *num)) num++; otn_tmp->activation_counter = atoi(num); #ifdef DEBUG printf("Set activation counter to %d\n", otn_tmp->activation_counter); #endif return; } /**************************************************************************** * * Function: XferHeader(RuleTreeNode *, RuleTreeNode *) * * Purpose: Transfer the rule block header data from point A to point B * * Arguments: rule => the place to xfer from * rtn => the place to xfer to * * Returns: void function * ***************************************************************************/ void XferHeader(RuleTreeNode * rule, RuleTreeNode * rtn) { rtn->type = rule->type; rtn->sip = rule->sip; rtn->dip = rule->dip; rtn->smask = rule->smask; rtn->dmask = rule->dmask; rtn->hsp = rule->hsp; rtn->lsp = rule->lsp; rtn->hdp = rule->hdp; rtn->ldp = rule->ldp; rtn->flags = rule->flags; } /**************************************************************************** * * Function: TestHeader(RuleTreeNode *, RuleTreeNode *) * * Purpose: Check to see if the two header blocks are identical * * Arguments: rule => uh * rtn => uuuuhhhhh.... * * Returns: 1 if they match, 0 if they don't * ***************************************************************************/ int TestHeader(RuleTreeNode * rule, RuleTreeNode * rtn) { if (rtn->sip == rule->sip) { if (rtn->dip == rule->dip) { if (rtn->dmask == rule->dmask) { if (rtn->smask == rule->smask) { if (rtn->hsp == rule->hsp) { if (rtn->lsp == rule->lsp) { if (rtn->hdp == rule->hdp) { if (rtn->ldp == rule->ldp) { if (rtn->flags == rule->flags) { return 1; } } } } } } } } } return 0; } /**************************************************************************** * * Function: VarAlloc() * * Purpose: allocates memory for a variable * * Arguments: none * * Returns: pointer to new VarEntry * ***************************************************************************/ struct VarEntry *VarAlloc() { struct VarEntry *new; if ((new = (struct VarEntry *) calloc(sizeof(struct VarEntry), sizeof(char))) == NULL) { FatalError("ERROR: cannot allocate memory for VarEntry."); } new->name = NULL; new->value = NULL; new->prev = NULL; new->next = NULL; return (new); } /**************************************************************************** * * Function: VarDefine(char *, char *) * * Purpose: define the contents of a variable * * Arguments: name => the name of the variable * value => the contents of the variable * * Returns: void function * ***************************************************************************/ void VarDefine(char *name, char *value) { struct VarEntry *p; int found = 0; if (!VarHead) { p = VarAlloc(); p->name = strdup(name); p->value = strdup(value); p->prev = p; p->next = p; VarHead = p; return; } p = VarHead; do { if (strcasecmp(p->name, name) == 0) { found = 1; break; } p = p->next; } while (p != VarHead); if (found) { if (p->value) free(p->value); p->value = strdup(value); } else { p = VarAlloc(); p->name = strdup(name); p->value = strdup(value); p->prev = VarHead; p->next = VarHead->next; p->next->prev = p; VarHead->next = p; } } /**************************************************************************** * * Function: VarDelete(char *) * * Purpose: deletes a defined variable * * Arguments: name => the name of the variable * * Returns: void function * ***************************************************************************/ void VarDelete(char *name) { struct VarEntry *p; if (!VarHead) return; p = VarHead; do { if (strcasecmp(p->name, name) == 0) { p->prev->next = p->next; p->next->prev = p->prev; if (VarHead == p) if ((VarHead = p->next) == p) VarHead = NULL; if (p->name) free(p->name); if (p->value) free(p->value); free(p); return; } p = p->next; } while (p != VarHead); } /**************************************************************************** * * Function: VarGet(char *) * * Purpose: get the contents of a variable * * Arguments: name => the name of the variable * * Returns: char * to contents of variable or NULL * ***************************************************************************/ char *VarGet(char *name) { struct VarEntry *p; if (!VarHead) return (NULL); p = VarHead; do { if (strcasecmp(p->name, name) == 0) return (p->value); p = p->next; } while (p != VarHead); return (NULL); }