/***************************************************************************/ /* */ /* Project: OpenSLP - OpenSource implementation of Service Location */ /* Protocol Version 2 */ /* */ /* File: slpd_database.c */ /* */ /* Abstract: This files contains an implementation of LDAPv3 search */ /* filters for SLP (as specified in RFC 2254). */ /* */ /*-------------------------------------------------------------------------*/ /* */ /* Please submit patches to http://www.openslp.org */ /* */ /*-------------------------------------------------------------------------*/ /* */ /* Copyright (C) 2000 Caldera Systems, Inc */ /* All rights reserved. */ /* */ /* Redistribution and use in source and binary forms, with or without */ /* modification, are permitted provided that the following conditions are */ /* met: */ /* */ /* Redistributions of source code must retain the above copyright */ /* notice, this list of conditions and the following disclaimer. */ /* */ /* Redistributions in binary form must reproduce the above copyright */ /* notice, this list of conditions and the following disclaimer in */ /* the documentation and/or other materials provided with the */ /* distribution. */ /* */ /* Neither the name of Caldera Systems nor the names of its */ /* contributors may be used to endorse or promote products derived */ /* from this software without specific prior written permission. */ /* */ /* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS */ /* `AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT */ /* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR */ /* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CALDERA */ /* SYSTEMS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, */ /* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT */ /* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, */ /* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON */ /* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ /* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE */ /* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* */ /***************************************************************************/ /*********/ /* TODO: The current implementation reparses the predicate string every */ /* time it is evaluated. This implementation should be refactored to */ /* parse the predicate string once, build some sort of data- */ /* structure out of that, and then compare the predicate DS with the */ /* attribute DS. */ /*********/ /********* * * Assumptions: * - If a tag specified in the query string does not exist, that's a FALSE * - The "Undefined" value mentioned in the LDAPv3 RFC (2251) is synonymous * with "false". * - The "Approx" operator matches as an equal operator. * - Variable matching on type is strict: ie, the right-hand-side of an * operator must evaluate to the same type as the tag referenced on the * left-hand-side. * * Known Bugs/TODO: * - If trash follows the last operand to a binary argument, it will be * ignored if the operand is not evaluated due to short circuiting: * ie, in "(&(first=*)(second<=3)trash)", "trash" will be silently * ignored _if_ "(first=*)" is true. * - Escaped '*' characters are treated as wildcards in the string equality * operator: ie, in "(string=abc\2axyz)" will match the literal string * "abc\2axyz" instead of "abc*xyz". * - No operations can be performed on opaque data types. *********/ #include #include #include #include "slpd_predicate.h" #include "../libslpattr/libslpattr.h" #include "../libslpattr/libslpattr_internal.h" /* The character that is a wildcard. */ #define WILDCARD ('*') #define BRACKET_OPEN '(' #define BRACKET_CLOSE ')' #ifndef MIN #define MIN(x,y) (x < y ? x : y) #endif /************************* ***********************/ /* Tests a character to see if it reserved (as defined in RFC 2608, p11). */ #define IS_RESERVED(x) (((x) == '(' || (x) == ')' || (x) == ',' || (x) == '\\' || (x) == '!' || (x) == '<' || (x) == '=' || (x) == '>' || (x) == '~') || ((((char)0x01 <= (x)) && ((char)0x1F >= (x))) || ((x) == (char)0x7F))) #define IS_INVALID_VALUE_CHAR(x) IS_RESERVED(x) #define IS_INVALID_TAG_CHAR(x) (IS_RESERVED(x) || ((x) == '*') || ((x) == (char)0x0D) || ((x) == (char)0x0A) || ((x) == (char)0x09) || ((x) == '_')) #define IS_VALID_TAG_CHAR(x) (!IS_INVALID_TAG_CHAR(x)) /************************* ***********************/ /*--------------------------------------------------------------------------*/ const char *substr(const char *haystack, const char *needle, int needle_len) /* Does a case insensitive substring match for needle in haystack. */ /* */ /* Returns pointer to the start of the substring. NULL if the substring is */ /* not found. */ /* FIXME This implementation isn't exactly blazingly fast... */ /*--------------------------------------------------------------------------*/ { const char *hs_cur; for(hs_cur = haystack; *hs_cur != 0; hs_cur++) { if(strncasecmp(hs_cur, needle, needle_len) == 0) { return hs_cur; } } return NULL; } /*--------------------------------------------------------------------------*/ typedef enum { FR_UNSET /* Placeholder. Used to detect errors. */, FR_INTERNAL_SYSTEM_ERROR /* Internal error. */, FR_PARSE_ERROR /* Parse error detected. */, FR_MEMORY_ALLOC_FAILED /* Ran out of memory. */, FR_EVAL_TRUE /* Expression successfully evaluated to true. */, FR_EVAL_FALSE /* Expression successfully evaluated to false. */ } FilterResult; /*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/ int escaped_verify(char *escaped, int len, int *punescaped_len) /* Verifies and finds the length of an escaped string */ /* */ /* Params: */ /* escaped -- (IN) string of escaped characters */ /* len -- (IN) length of escaped */ /* unescaped_len -- (OUT) pointer to location to write the unescaped */ /* length of escaped to */ /* */ /* Returns: */ /* 1 if valid string, 0 if invalid. If 0, then punescaped_count will not */ /* be set */ /*--------------------------------------------------------------------------*/ { int i; int unescaped_len; int seq_pos; /* Position in the current escape sequence. Set to zero when not in escape seq. */ seq_pos = 0; for(i = unescaped_len = 0; i < len; i++) { /* Verify escape sequences. */ if(seq_pos == 1 || seq_pos == 2) { if(!isxdigit((int) escaped[i])) { return 0; } if(seq_pos == 2) { seq_pos = 0; } else { seq_pos++; } } else { unescaped_len++; } /* Check for escape sequences. */ if(escaped[i] == '\\') { seq_pos = 1; } } if(punescaped_len) { *punescaped_len = unescaped_len; } return 1; } /*--------------------------------------------------------------------------*/ int unescape_check(char d1, char d2, char *val) /* Unescape the character represented by the two given hex values. */ /* */ /* Params: */ /* d1 -- (IN) First hex char */ /* d1 -- (IN) second hex char */ /* val -- (OUT) the value of the character is written to here */ /* */ /* Returns: */ /* 1 on successful conversion, 0 if one or more of the hex digits are */ /* invalid */ /*--------------------------------------------------------------------------*/ { if(!isxdigit((int) d1) || !isxdigit((int) d2)) { return 0; } if((d1 >= 'A') && (d1 <= 'F')) d1 = d1 - 'A' + 0x0A; else d1 = d1 - '0'; if((d2 >= 'A') && (d2 <= 'F')) d2 = d2 - 'A' + 0x0A; else d2 = d2 - '0'; *val = d2 + (d1 * 0x10); return 1; } /*--------------------------------------------------------------------------*/ FilterResult unescape_cmp(const char *escaped, int escaped_len, const char *verbatim, int verbatim_len, SLPBoolean strict_len, int *punescaped_count) /* Compares two strings that (potentially) contain escaped characters */ /* */ /* Params: */ /* escaped -- escaped string to compare */ /* escaped_len -- length of the escaped string (including esc chars) */ /* verbatim -- string to compare against (not escaped) */ /* verbatim_len -- length of the verbatim string */ /* strict_len -- if TRUE, the number of characters in verbatim that match */ /* must be _exactly_ verbatim_len. If FALSE, at most verbatim_len*/ /* characters may match */ /* unescaped_count -- (OUT) the number of unescaped characters. Can be */ /* NULL */ /* */ /*--------------------------------------------------------------------------*/ { char unesc; /* Holder for unescaped characters. */ int esc_i; /* Index into escaped. */ int ver_i; /* Index into verbatim. */ int unescaped_count; unescaped_count = esc_i = ver_i = 0; /***** Compare every pair of characters. *****/ while(1) { /**** Check for string end. ****/ if(esc_i >= escaped_len) { if(punescaped_count != NULL) { *punescaped_count = unescaped_count; } if(strict_len == SLP_TRUE) { if(ver_i >= verbatim_len) { return FR_EVAL_TRUE; } else { return FR_EVAL_FALSE; } } return FR_EVAL_TRUE; } /**** Check for escaping ****/ if(escaped[esc_i] == '\\') { /*** Check for truncated escape values. ***/ if(esc_i + 2 >= escaped_len) { return FR_PARSE_ERROR; } /*** Unescape. ***/ if(!unescape_check(escaped[esc_i+1], escaped[esc_i+2], &unesc)) { return FR_PARSE_ERROR; } /*** Increment. ***/ esc_i += 2; } else { unesc = escaped[esc_i]; } if(unesc != verbatim[ver_i]) /* quick check for equality*/ { if(! isascii(unesc) /* case insensitive check */ || ! isalpha(unesc) || ! isalpha(verbatim[ver_i]) || tolower(unesc) != tolower(verbatim[ver_i])) { return FR_EVAL_FALSE; } } unescaped_count++; esc_i++; ver_i++; } } /*--------------------------------------------------------------------------*/ void *my_memmem(char *haystack, int haystack_len, char *needle, int needle_len, int *punescaped_len) /* locate an (escaped) substring */ /* */ /* Params: */ /* haystack -- (IN) unescaped memory to look in */ /* haystack_len -- (IN) length of haystack */ /* needle -- (IN) escaped memory to search for */ /* needle_len -- (IN) length of needle */ /* punescaped_len -- (OUT) the size of the unescaped needle. invalid if */ /* NULL is returned */ /* */ /* Returns: */ /* pointer to substring. NULL if there is no substring */ /*--------------------------------------------------------------------------*/ { int offset; int search_len; if(escaped_verify(haystack, haystack_len, &search_len) == 0) { return NULL; } for(offset = 0; offset <= search_len; offset++) { FilterResult err; err = unescape_cmp(needle, needle_len, haystack + offset, haystack_len - offset, SLP_FALSE, punescaped_len); if(err == FR_EVAL_TRUE) { return(void *)(haystack + offset); } else if(err != FR_EVAL_FALSE) { return NULL; } } return NULL; } /*--------------------------------------------------------------------------*/ FilterResult wildcard_wc_str(char *pattern, int pattern_len, char *str, int str_len) /* */ /* Params: */ /* pattern -- the pattern to evaluate */ /* pattern_len -- the length of the pattern (in bytes) */ /* str -- the string to test the pattern on */ /* str_len -- the length of the string */ /* */ /* Returns: */ /* -1 error, 0 success, 1 failure */ /*--------------------------------------------------------------------------*/ { char *text_start; /* Pointer to the text following the WC(s) */ int text_len; /* Length of the text. */ char *found; /* The start of a string found in a search. */ char *rem_start; /* Start of the remaining characters being searched for. */ int rem_len; /* Length of the remaining charcters being searched for. */ if(pattern_len == 0 && str_len == 0) { return FR_EVAL_TRUE; } if(pattern_len == 0) { return FR_EVAL_FALSE; } /***** Find text following wildcard *****/ /**** Find end of WCs ****/ text_start = pattern; text_len = 0; while(*text_start == WILDCARD) { text_start++; if(text_start == pattern + pattern_len) { /* No following text. Therefore we match. */ return FR_EVAL_TRUE; } } /**** Find end of text. ****/ found = memchr(text_start, WILDCARD, pattern_len - text_len); if(found == NULL) { /* No trailing WC. Set to end. */ found = pattern + pattern_len; } text_len = found - text_start; /***** Look for the text in the source string *****/ rem_start = str; rem_len = str_len; while(1) { int result; int match_len; found = my_memmem(rem_start, rem_len, text_start, text_len, &match_len); assert(found == NULL || ((found >= rem_start) && (found <= rem_start + rem_len))); if(found == NULL) { /* Desired text is not in the string. */ return FR_EVAL_FALSE; } rem_len = str_len - (found - str); rem_start = found + 1; /**** Make recursive call. ****/ result = wildcard_wc_str(text_start + text_len, (pattern + pattern_len) - (text_start + text_len), found + text_len, rem_len - match_len); if(result != FR_EVAL_FALSE) { return result; } } /*NOTREACHED*/ assert(0); } /*--------------------------------------------------------------------------*/ FilterResult wildcard(const char *pattern, int pattern_len, const char *str, int str_len) /* Check the pattern against the given string. Public interface to wildcard */ /* functionality. */ /* */ /* Params: */ /* pattern -- the pattern to evaluate */ /* pattern_len -- the length of the pattern (in bytes) */ /* str -- the string to test the pattern on */ /* str_len -- the length of the string */ /* */ /* Returns: */ /* -1 error, 0 success, 1 failure */ /* */ /* Implementation: */ /* This is the interface to wildcard finding. It actually just prepares */ /* for repeated calls to wildcard_wc_str() */ /*--------------------------------------------------------------------------*/ { char *first_wc; /* Position of the first WC in the pattern. */ int first_wc_len; /* Position of first_wc in pattern. */ int unescaped_first_wc_len; /* Position of first_wc in unescaped version of pattern. */ /***** Check text up to first WC *****/ /**** Get text ****/ first_wc = memchr(pattern, WILDCARD, pattern_len); if(first_wc == NULL) { /* No wc */ return unescape_cmp(pattern, pattern_len, str, str_len, SLP_TRUE, NULL); } else { first_wc_len = first_wc - pattern; } /**** Compare. ****/ unescaped_first_wc_len = first_wc_len; if(first_wc_len > 0) { FilterResult err; err = unescape_cmp(pattern, first_wc_len, str, str_len, SLP_FALSE, &unescaped_first_wc_len); if(err != FR_EVAL_TRUE) { /* The leading text is different. */ return err; } } /***** Start recursive call. *****/ return wildcard_wc_str(first_wc, pattern_len - first_wc_len, (char *)str + unescaped_first_wc_len, str_len - unescaped_first_wc_len); } /*--------------------------------------------------------------------------*/ int is_bool_string(const char *str, int str_len, SLPBoolean *val) /* Tests a string to see if it is a boolean. */ /*--------------------------------------------------------------------------*/ { if(str_len == 4 && memcmp(str, "true", 4) == 0) /* 4 -> length of string "true" */ { *val = SLP_TRUE; return 1; } if(str_len == 5 && memcmp(str, "false", 5) == 0) /* 5 -> len of "false" */ { *val = SLP_FALSE; return 1; } return 0; } /*--------------------------------------------------------------------------*/ typedef enum { EQUAL, APPROX, GREATER, LESS, PRESENT } Operation; /*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/ FilterResult int_op(SLPAttributes slp_attr, char *tag, int tag_len, char *rhs, Operation op) /* Perform an integer operation. */ /*--------------------------------------------------------------------------*/ { int rhs_val; /* The converted value of rhs. */ char *end; /* Pointer to the end of op. */ var_t *var; /* Pointer to the variable named by tag. */ value_t *value; /* A value in var. */ FilterResult result; /* Value to return. */ assert(op != PRESENT); result = FR_UNSET; /* For verification. */ /* TODO Only do this in debug. */ /***** Verify and convert rhs. *****/ rhs_val = strtol(rhs, &end, 10); if(*end != 0 && *end != BRACKET_CLOSE) { /* Trying to compare an int with a non-int. */ return FR_EVAL_FALSE; } /***** Get variable. *****/ var = attr_val_find_str((struct xx_SLPAttributes *)slp_attr, tag, tag_len); if(var == NULL) { return FR_EVAL_FALSE; } /**** Check type. ****/ if(var->type != SLP_INTEGER) { return FR_EVAL_FALSE; } /***** Compare. *****/ value = var->list; assert(value != NULL); assert(op != PRESENT); switch(op) { case(EQUAL): result = FR_EVAL_FALSE; for(; value; value = value->next) { if(value->data.va_int == rhs_val) { result = FR_EVAL_TRUE; break; } } break; case(APPROX): assert(0); /* TODO: Figure out how this works later. */ result = FR_EVAL_FALSE; break; case(GREATER): result = FR_EVAL_FALSE; for(; value; value = value->next) { if(value->data.va_int >= rhs_val) { result = FR_EVAL_TRUE; break; } } break; case(LESS): result = FR_EVAL_FALSE; for(; value; value = value->next) { if(value->data.va_int <= rhs_val) { result = FR_EVAL_TRUE; break; } } break; default: assert(0); } assert(result != FR_UNSET); return result; } /*--------------------------------------------------------------------------*/ FilterResult keyw_op(SLPAttributes slp_attr, char *tag, char *rhs, Operation op) /* Perform a keyword operation. */ /*--------------------------------------------------------------------------*/ { /* Note that the only test keywords are allowed to undergo is PRESENT, * also note that PRESENT is handled by our calling function. * * We are therefore quite justified in saying: */ assert(op != PRESENT); return FR_EVAL_FALSE; } /*--------------------------------------------------------------------------*/ FilterResult bool_op(SLPAttributes slp_attr, char *tag, int tag_len, char *rhs, int rhs_len, Operation op) /* Perform a boolean operation. */ /*--------------------------------------------------------------------------*/ { SLPBoolean rhs_val; /* The value of the rhs. */ var_t *var; FilterResult result; assert(op != PRESENT); result = FR_UNSET; /* Note that the only valid non-PRESENT operator is EQUAL. */ if(op != EQUAL) { return FR_EVAL_FALSE; } /***** Get and check the rhs value. *****/ if(!is_bool_string(rhs, rhs_len, &rhs_val)) { return FR_EVAL_FALSE; } /***** Get the tag value. *****/ var = attr_val_find_str((struct xx_SLPAttributes *)slp_attr, tag, tag_len); if(var == NULL) { return FR_EVAL_FALSE; } /**** Check type. ****/ if(var->type != SLP_BOOLEAN) { return FR_EVAL_FALSE; } /***** Compare. *****/ if(var->list->data.va_bool == rhs_val) { result = FR_EVAL_TRUE; } else { result = FR_EVAL_FALSE; } assert(result != FR_UNSET); return result; } /*--------------------------------------------------------------------------*/ FilterResult str_op(SLPAttributes slp_attr, char *tag, int tag_len, char *rhs, int rhs_len, Operation op) /* Perform a string operation. */ /*--------------------------------------------------------------------------*/ { char *str_val; /* Converted value of rhs. */ int str_len; /* Length of converted value. */ var_t *var; assert(op != PRESENT); /***** Verify rhs. *****/ str_val = rhs; str_len = rhs_len; /***** Get tag value. *****/ var = attr_val_find_str((struct xx_SLPAttributes *)slp_attr, tag, tag_len); if(var == NULL) { return FR_EVAL_FALSE; } /**** Check type. ****/ if(var->type != SLP_STRING) { return FR_EVAL_FALSE; } /***** Compare. *****/ assert(op != PRESENT); if(op == APPROX) { assert(0); /* TODO: Figure out how this works later. */ } else if(op == EQUAL) { int result; value_t *value; for(value = var->list; value; value = value->next) { result = wildcard(str_val, str_len, value->data.va_str, value->unescaped_len); /* We only keep going if the test fails. Let caller handle other problems. */ if(result != FR_EVAL_FALSE) { return result; } } } else { value_t *value; /* We know that the op must be comparative. */ assert(op == LESS || op == GREATER); for(value = var->list; value; value = value->next) { int result; result = memcmp(value->data.va_str, str_val, MIN(str_len, value->unescaped_len)); if( (result <= 0 && op == LESS) || (result >= 0 && op == GREATER) ) { return FR_EVAL_TRUE; } } } return FR_EVAL_FALSE; } /*--------------------------------------------------------------------------*/ FilterResult opaque_op(SLPAttributes slp_attr, char *tag, int tag_len, char *rhs, int rhs_len, Operation op) /* Perform an opaque operation. */ /*--------------------------------------------------------------------------*/ { char *str_val; /* Converted value of rhs. */ int str_len; /* Length of converted value. */ var_t *var; assert(op != PRESENT); /***** Verify and convert rhs. *****/ str_val = rhs; str_len = rhs_len; /***** Get tag value. *****/ var = attr_val_find_str((struct xx_SLPAttributes *)slp_attr, tag, tag_len); if(var == NULL) { return FR_EVAL_FALSE; } /**** Check type. ****/ if(var->type != SLP_OPAQUE) { return FR_EVAL_FALSE; } /***** Compare. *****/ assert(op != PRESENT); if(op == APPROX) { assert(0); /* TODO: Figure out how this works later. */ } else if(op == EQUAL) { int result; value_t *value; for(value = var->list; value; value = value->next) { result = wildcard(str_val, str_len, value->data.va_str, value->unescaped_len); /* We only keep going if the test fails. Let caller handle other problems. */ if(result != FR_EVAL_FALSE) { return result; } } } else { value_t *value; /* We know that the op must be comparative. */ assert(op == LESS || op == GREATER); for(value = var->list; value; value = value->next) { int result; result = memcmp(value->data.va_str, str_val, MIN(str_len, value->unescaped_len)); if( (result <= 0 && op == LESS) || (result >= 0 && op == GREATER) ) { return FR_EVAL_TRUE; } } } return FR_EVAL_FALSE; } /*--------------------------------------------------------------------------*/ int is_int_string(const char *str) /* Tests a string to see if it consists wholly of numeric characters. */ /*--------------------------------------------------------------------------*/ { int i; for(i=0; str[i] != '\0'; i++) { if(!((!isdigit((int)str[i])) || str[i] == '-')) { return 0; } } return 1; } /*--------------------------------------------------------------------------*/ char *find_substr(char *src, int src_len, char *to_find) /* Returns ptr to start of substring, or null. */ /*--------------------------------------------------------------------------*/ { int i; for(i = 0; i < src_len; i++) { if(src[i] == *to_find) { int old_i = i; /* Save the old start. */ int find_index; for(find_index = 0; (to_find[find_index] != '\0') && (i < src_len) && (to_find[find_index] == src[i]); to_find++, i++) { } if(to_find[find_index] == '\0') { return src + i; } i = old_i; /* Restor the old start. */ } } return 0; } /*--------------------------------------------------------------------------*/ char *find_bracket_end(const char *str) /* Finds a bracket matched to this one. Returns 0 if there isn't one. */ /*--------------------------------------------------------------------------*/ { int open_count; const char *cur; if(*str != BRACKET_OPEN) { return 0; } open_count = 0; /***** Count brackets. *****/ for(cur = str; *cur != '\0'; cur++) { assert(open_count >= 0); /**** Check current character. ****/ if(*cur == BRACKET_OPEN) { open_count++; } else if(*cur == BRACKET_CLOSE) { open_count--; } /**** Check if we've found bracket end. ****/ if(open_count == 0) { return(char *)cur; } } return 0; } /*--------------------------------------------------------------------------*/ struct pair /* Represents a string and its length. */ { char *start; char *end; /* Stop reading _BEFORE_ end. ie, at end-1. */ }; /*--------------------------------------------------------------------------*/ #if defined(ENABLE_SLPv1) /*--------------------------------------------------------------------------*/ FilterResult filterv1(const char *start, const char **end, SLPAttributes slp_attr, int recursion_depth) /* Parameters: */ /* start -- (IN) The start of the string to work in. */ /* end -- (OUT) The end of the string processed. After successful processing*/ /* (ie, no PARSE_ERRORs), this will be pointed at the character*/ /* following the last char in this level of the expression. */ /* slp_attr -- (IN) The attributes handle to compare on. */ /* return_value -- (OUT) The result of the search. Only valid if SLP_OK was */ /* returned. */ /* */ /* Returns: SLP_OK on successful search (ie, the search was do-able). */ /* SLP_PARSE_ERROR on search error. The end of the expression is */ /* returned through end. */ /* NOTE: This attempt only implements one comparision operator (==) */ /*--------------------------------------------------------------------------*/ { char *operator; /* Pointer to the operator substring. */ const char *cur; /* Current working character. */ const char *last_char; /* The last character in the working string. */ FilterResult err = FR_UNSET; /* The result of an evaluation. */ FilterResult stop_condition = FR_UNSET; if(recursion_depth <= 0) { return FR_PARSE_ERROR; } recursion_depth--; if(*start == 0) { *end = start; return FR_EVAL_TRUE; } if(*start != BRACKET_OPEN) { return FR_PARSE_ERROR; } /***** Get the current expression. *****/ last_char = *end = find_bracket_end(start); if(*end == 0) { return FR_PARSE_ERROR; } (*end)++; /* Move the end pointer past the closing bracket. */ /**** * Check for the three legal characters that can follow an expression, * if the following character isn't one of those, the following character * is trash. ****/ if(!(**end == BRACKET_OPEN || **end == BRACKET_CLOSE || **end == '\0')) { return FR_PARSE_ERROR; } /***** check for boolean op. *****/ cur = start; cur++; switch(*cur) { case('&'): /***** And. *****/ case('|'): /***** Or. *****/ { if(*cur == '&') { stop_condition = FR_EVAL_FALSE; } else if(*cur == '|') { stop_condition = FR_EVAL_TRUE; } cur++; /* Move past operator. */ /*** Ensure that we have at least one operator. ***/ if(*cur != BRACKET_OPEN || cur >= last_char) { return FR_PARSE_ERROR; } /*** Evaluate each operand. ***/ /* NOTE: Due to the condition on the above "if", we are guarenteed that the first iteration of the loop is valid. */ do { err = filterv1(cur, &cur, slp_attr, recursion_depth); /*** Propagate errors. ***/ if(err != FR_EVAL_TRUE && err != FR_EVAL_FALSE) { return err; } /*** Short circuit. ***/ if(err == stop_condition) { return stop_condition; } } while(*cur == BRACKET_OPEN && cur < last_char); /*** If we ever get here, it means we've evaluated every operand without short circuiting -- meaning that the operation failed. ***/ return(stop_condition == FR_EVAL_TRUE ? FR_EVAL_FALSE : FR_EVAL_TRUE); } case('!'): /***** Not. *****/ /**** Child. ****/ cur++; err = filterv1(cur, &cur, slp_attr, recursion_depth); /*** Return errors. ***/ if(err != FR_EVAL_TRUE && err != FR_EVAL_FALSE) { return err; } /*** Perform "not". ***/ return(err == FR_EVAL_TRUE ? FR_EVAL_FALSE : FR_EVAL_TRUE); default: /***** Unknown operator. *****/ ; /* We don't do anything here because this will catch the first character of every leaf predicate. */ } /***** Check for leaf operator. *****/ if(IS_VALID_TAG_CHAR(*cur)) { Operation op; char *lhs, *rhs; /* The two operands. */ char *val_start; /* The character after the operator. ie, the start of the rhs. */ int lhs_len, rhs_len; /* Length of the lhs/rhs. */ SLPType type; SLPError slp_err; /**** Demux leaf op. ****/ /* Support only "==", we look for the equals * sign, and then poke around on either side of that for the real * value. */ operator = (char *)memchr(cur, '=', last_char - cur); if(operator == 0 || *(operator + 1) != '=') { /**** No search operator. ****/ return FR_PARSE_ERROR; } /* The rhs always follows the operator. (This doesn't really make sense for PRESENT ops, but ignore that). */ val_start = operator + 2; if(operator == cur) {/* Check that we can poke back one char. */ return FR_PARSE_ERROR; } op = EQUAL; /***** Get operands. *****/ /**** Left. ****/ lhs_len = operator - cur; lhs = (char *)cur; /**** Right ****/ rhs_len = last_char - val_start; rhs = val_start; /***** Do leaf operation. *****/ /**** Check that tag exists. ****/ slp_err = SLPAttrGetType_len(slp_attr, lhs, lhs_len, &type); if(slp_err == SLP_TAG_ERROR) { /* Tag doesn't exist. */ return FR_EVAL_FALSE; } else if(slp_err == SLP_OK) { /* Tag exists. */ /**** Do operation. *****/ if(op == PRESENT) { /*** Since the PRESENT operation is the same for all types, do that now. ***/ return FR_EVAL_TRUE; } else { /*** A type-specific operation. ***/ switch(type) { case(SLP_BOOLEAN): err = bool_op(slp_attr, lhs, lhs_len, rhs, rhs_len, op); break; case(SLP_INTEGER): err = int_op(slp_attr, lhs, lhs_len, rhs, op); break; case(SLP_KEYWORD): err = keyw_op(slp_attr, lhs, rhs, op); break; case(SLP_STRING): err = str_op(slp_attr, lhs, lhs_len, rhs, rhs_len, op); break; case(SLP_OPAQUE): assert(0); /* Opaque is not yet supported. */ } } } else { /* Some other tag-related error. */ err = FR_INTERNAL_SYSTEM_ERROR; } assert(err != FR_UNSET); return err; } /***** No operator. *****/ return FR_PARSE_ERROR; } #endif /*--------------------------------------------------------------------------*/ FilterResult filter(const char *start, const char **end, SLPAttributes slp_attr, int recursion_depth) /* Parameters: */ /* start -- (IN) The start of the string to work in. */ /* end -- (OUT) The end of the string processed. After successful processing*/ /* (ie, no PARSE_ERRORs), this will be pointed at the character*/ /* following the last char in this level of the expression. */ /* slp_attr -- (IN) The attributes handle to compare on. */ /* return_value -- (OUT) The result of the search. Only valid if SLP_OK was */ /* returned. */ /* */ /* Returns: SLP_OK on successful search (ie, the search was do-able). */ /* SLP_PARSE_ERROR on search error. The end of the expression is */ /* returned through end. */ /*--------------------------------------------------------------------------*/ { char *operator; /* Pointer to the operator substring. */ const char *cur; /* Current working character. */ const char *last_char; /* The last character in the working string. */ FilterResult err = FR_UNSET; /* The result of an evaluation. */ FilterResult stop_condition = FR_UNSET; if(recursion_depth <= 0) { return FR_PARSE_ERROR; } recursion_depth--; if(*start != BRACKET_OPEN) { return FR_PARSE_ERROR; } /***** Get the current expression. *****/ last_char = *end = find_bracket_end(start); if(*end == 0) { return FR_PARSE_ERROR; } (*end)++; /* Move the end pointer past the closing bracket. */ /**** * Check for the three legal characters that can follow an expression, * if the following character isn't one of those, the following character * is trash. ****/ if(!(**end == BRACKET_OPEN || **end == BRACKET_CLOSE || **end == '\0')) { return FR_PARSE_ERROR; } /***** check for boolean op. *****/ cur = start; cur++; switch(*cur) { case('&'): /***** And. *****/ case('|'): /***** Or. *****/ { if(*cur == '&') { stop_condition = FR_EVAL_FALSE; } else if(*cur == '|') { stop_condition = FR_EVAL_TRUE; } cur++; /* Move past operator. */ /*** Ensure that we have at least one operator. ***/ if(*cur != BRACKET_OPEN || cur >= last_char) { return FR_PARSE_ERROR; } /*** Evaluate each operand. ***/ /* NOTE: Due to the condition on the above "if", we are guarenteed that the first iteration of the loop is valid. */ do { err = filter(cur, &cur, slp_attr, recursion_depth); /*** Propagate errors. ***/ if(err != FR_EVAL_TRUE && err != FR_EVAL_FALSE) { return err; } /*** Short circuit. ***/ if(err == stop_condition) { return stop_condition; } } while(*cur == BRACKET_OPEN && cur < last_char); /*** If we ever get here, it means we've evaluated every operand without short circuiting -- meaning that the operation failed. ***/ return(stop_condition == FR_EVAL_TRUE ? FR_EVAL_FALSE : FR_EVAL_TRUE); } case('!'): /***** Not. *****/ /**** Child. ****/ cur++; err = filter(cur, &cur, slp_attr, recursion_depth); /*** Return errors. ***/ if(err != FR_EVAL_TRUE && err != FR_EVAL_FALSE) { return err; } /*** Perform "not". ***/ return(err == FR_EVAL_TRUE ? FR_EVAL_FALSE : FR_EVAL_TRUE); default: /***** Unknown operator. *****/ ; /* We don't do anything here because this will catch the first character of every leaf predicate. */ } /***** Check for leaf operator. *****/ if(IS_VALID_TAG_CHAR(*cur)) { Operation op; char *lhs, *rhs; /* The two operands. */ char *val_start; /* The character after the operator. ie, the start of the rhs. */ int lhs_len, rhs_len; /* Length of the lhs/rhs. */ SLPType type; SLPError slp_err; /**** Demux leaf op. ****/ /* Since all search operators contain a "=", we look for the equals * sign, and then poke around on either side of that for the real * value. */ operator = (char *)memchr(cur, '=', last_char - cur); if(operator == 0) { /**** No search operator. ****/ return FR_PARSE_ERROR; } /* The rhs always follows the operator. (This doesn't really make sense for PRESENT ops, but ignore that). */ val_start = operator + 1; /* Check for APPROX, GREATER, or LESS. Note that we shuffle the operator pointer back to point at the start of the op. */ if(operator == cur) {/* Check that we can poke back one char. */ return FR_PARSE_ERROR; } switch(*(operator - 1)) { case('~'): op = EQUAL; /* See Assumptions. */ operator--; break; case('>'): op = GREATER; operator--; break; case('<'): op = LESS; operator--; break; default: /* No prefix to the '='. */ /**** Check for PRESENT. ****/ if((operator == last_char - 2) && (*(operator+1) == '*')) { op = PRESENT; } /**** It's none of the above: therefore it's EQUAL. ****/ else { op = EQUAL; } } /***** Get operands. *****/ /**** Left. ****/ lhs_len = operator - cur; lhs = (char *)cur; /**** Right ****/ rhs_len = last_char - val_start; rhs = val_start; /***** Do leaf operation. *****/ /**** Check that tag exists. ****/ slp_err = SLPAttrGetType_len(slp_attr, lhs, lhs_len, &type); if(slp_err == SLP_TAG_ERROR) { /* Tag doesn't exist. */ return FR_EVAL_FALSE; } else if(slp_err == SLP_OK) { /* Tag exists. */ /**** Do operation. *****/ if(op == PRESENT) { /*** Since the PRESENT operation is the same for all types, do that now. ***/ return FR_EVAL_TRUE; } else { /*** A type-specific operation. ***/ switch(type) { case(SLP_BOOLEAN): err = bool_op(slp_attr, lhs, lhs_len, rhs, rhs_len, op); break; case(SLP_INTEGER): err = int_op(slp_attr, lhs, lhs_len, rhs, op); break; case(SLP_KEYWORD): err = keyw_op(slp_attr, lhs, rhs, op); break; case(SLP_STRING): err = str_op(slp_attr, lhs, lhs_len, rhs, rhs_len, op); break; case(SLP_OPAQUE): assert(0); /* Opaque is not yet supported. */ } } } else { /* Some other tag-related error. */ err = FR_INTERNAL_SYSTEM_ERROR; } assert(err != FR_UNSET); return err; } /***** No operator. *****/ return FR_PARSE_ERROR; } /*=========================================================================*/ int SLPDPredicateTest(int version, int attrlistlen, const char* attrlist, int predicatelen, const char* predicate) /* Determine whether the specified attribute list satisfies */ /* the specified predicate */ /* */ /* version (IN) SLP version of the predicate string (should always be */ /* 2 since we don't handle SLPv1 predicates yet) */ /* */ /* attrlistlen (IN) length of attrlist */ /* */ /* attr (IN) attribute list to test */ /* */ /* predicatelen (IN) length of the predicate string */ /* */ /* predicate (IN) the predicate string */ /* */ /* Returns: Boolean value. Zero of test fails. Non-zero if test fails */ /* or if there is a parse error in the predicate string */ /*=========================================================================*/ { SLPAttributes attr; const char *end; /* Pointer to the end of the parsed attribute string. */ FilterResult err; char attrnull; char prednull; int result = 0; /* An NULL or empty string is always true. */ if(predicate == 0 || *(char *)predicate == 0) { return 1; } /* Attempt v1 too. */ if(version != 2 #if defined(ENABLE_SLPv1) && version != 1 #endif ) { return 1; } /* TRICKY: Temporarily NULL terminate the attribute list */ /* and the predicate string. We can do this because */ /* there is room in the corresponding SLPv2 SRVREG */ /* and SRVRQST messages. Basically we are squashing */ /* the authcount and the spi string length. Don't */ /* worry, we fix things up later and it is MUCH */ /* faster than a malloc() for a new buffer 1 byte */ /* longer! */ prednull = predicate[predicatelen]; ((char*)predicate)[predicatelen] = 0; attrnull = attrlist[attrlistlen]; ((char*)attrlist)[attrlistlen] = 0; /* Generate an SLPAttr from the comma delimited list */ if(SLPAttrAlloc("en", NULL, SLP_FALSE, &attr) == 0) { if(SLPAttrFreshen(attr, attrlist) == 0) { switch(version) { #if defined(ENABLE_SLPv1) case 1: err=filterv1(predicate, &end, attr, SLPD_ATTR_RECURSION_DEPTH); break; #endif default: err=filter(predicate, &end, attr, SLPD_ATTR_RECURSION_DEPTH); break; } /* Check for trailing trash data. */ if((err == FR_EVAL_TRUE || err == FR_EVAL_FALSE) && *end != 0) { result = 0; } else if(err == FR_EVAL_TRUE) { result = 1; } } SLPAttrFree(attr); } /* Un null terminate */ ((char*)predicate)[predicatelen] = prednull; ((char*)attrlist)[attrlistlen] = attrnull; return result; } /*=========================================================================*/ int SLPDFilterAttributes(int attrlistlen, const char* attrlist, int taglistlen, const char* taglist, int* resultlen, char** result) /* Copies attributes from the specified attribute list to a result string */ /* according to the taglist as described by section 10.4. of RFC 2608 */ /* */ /* version (IN) SLP version of the predicate string (should always be */ /* 2 since we don't handle SLPv1 predicates yet) */ /* */ /* attrlistlen (IN) length of attrlist */ /* */ /* attr (IN) attribute list to test */ /* */ /* predicatelen (IN) length of the predicate string */ /* */ /* predicate (IN) the predicate string */ /* */ /* Returns: Zero on success. Nonzero on failure */ /*=========================================================================*/ { SLPAttributes attr; FilterResult err; char attrnull; char tagnull; *result = 0; *resultlen = 0; /* TRICKY: Temporarily NULL terminate the attribute list */ /* and the tag string. We can do this because */ /* there is room in the corresponding SLPv2 SRVREG */ /* and ATTRRQST messages. Basically we are squashing */ /* the authcount and the spi string length. Don't */ /* worry, we fix things up later and it is MUCH */ /* faster than a malloc() for a new buffer 1 byte */ /* longer! */ tagnull = taglist[taglistlen]; ((char*)taglist)[taglistlen] = 0; attrnull = attrlist[attrlistlen]; ((char*)attrlist)[attrlistlen] = 0; /* Generate an SLPAttr from the comma delimited list */ err = 1; if(SLPAttrAlloc("en", NULL, SLP_FALSE, &attr) == 0) { if(SLPAttrFreshen(attr, attrlist) == 0) { err = SLPAttrSerialize(attr, taglist, result, *resultlen, resultlen, SLP_FALSE); } SLPAttrFree(attr); } /* SLPAttrSerialize counts the NULL terminator which we don't care about*/ if(*resultlen) { *resultlen -= 1; } /* Un null terminate */ ((char*)taglist)[taglistlen] = tagnull; ((char*)attrlist)[attrlistlen] = attrnull; return(*resultlen == 0); }