/* * DisMan Expression MIB: * Core implementation of expression evaluation */ #ifdef HAVE_INTTYPES_H #include #endif #include #include #include #include "disman/expr/expExpression.h" #include "disman/expr/expObject.h" #include "disman/expr/expValue.h" #include void _expValue_setError( struct expExpression *exp, int reason, oid *suffix, size_t suffix_len, netsnmp_variable_list *var); #define ASN_PRIV_OPERATOR (ASN_PRIVATE | 0x0f) #define ASN_PRIV_FUNCTION (ASN_PRIVATE | 0x0e) int ops[128]; /* mapping from operator characters to numeric tokens (ordered by priority). */ void init_expValue(void) { DEBUGMSGTL(("disman:expr:eval", "Init expValue")); /* Single-character operators */ ops['+'] = EXP_OPERATOR_ADD; ops['-'] = EXP_OPERATOR_SUBTRACT; ops['*'] = EXP_OPERATOR_MULTIPLY; ops['/'] = EXP_OPERATOR_DIVIDE; ops['%'] = EXP_OPERATOR_REMAINDER; ops['^'] = EXP_OPERATOR_BITXOR; ops['~'] = EXP_OPERATOR_BITNEGATE; ops['|'] = EXP_OPERATOR_BITOR; ops['&'] = EXP_OPERATOR_BITAND; ops['!'] = EXP_OPERATOR_NOT; ops['<'] = EXP_OPERATOR_LESS; ops['>'] = EXP_OPERATOR_GREAT; /* * Arbitrary offsets, chosen so * the three blocks don't overlap. */ /* "X=" operators */ ops['='+20] = EXP_OPERATOR_EQUAL; ops['!'+20] = EXP_OPERATOR_NOTEQ; ops['<'+20] = EXP_OPERATOR_LESSEQ; ops['>'+20] = EXP_OPERATOR_GREATEQ; /* "XX" operators */ ops['|'-30] = EXP_OPERATOR_OR; ops['&'-30] = EXP_OPERATOR_AND; ops['<'-30] = EXP_OPERATOR_LSHIFT; ops['>'-30] = EXP_OPERATOR_RSHIFT; } /* * Insert the value of the specified object parameter, * using the instance 'suffix' for wildcarded objects. */ netsnmp_variable_list * _expValue_evalParam( netsnmp_variable_list *expIdx, int param, oid *suffix, size_t suffix_len ) { netsnmp_variable_list *var = SNMP_MALLOC_TYPEDEF( netsnmp_variable_list ); struct expObject *obj; netsnmp_variable_list *val_var = NULL, *oval_var = NULL; /* values */ netsnmp_variable_list *dd_var = NULL, *odd_var = NULL; /* deltaDs */ netsnmp_variable_list *cond_var = NULL; /* conditionals */ int n; /* * Retrieve the expObject entry for the requested parameter. */ if ( !var || !expIdx || !expIdx->next_variable || !expIdx->next_variable->next_variable ) return NULL; *expIdx->next_variable->next_variable->val.integer = param; obj = (struct expObject *) netsnmp_tdata_row_entry( netsnmp_tdata_row_get_byidx( expObject_table_data, expIdx )); if (!obj) { /* * No such parameter configured for this expression */ snmp_set_var_typed_integer( var, ASN_INTEGER, EXPERRCODE_INDEX ); var->type = ASN_NULL; return var; } if ( obj->expObjectSampleType != EXPSAMPLETYPE_ABSOLUTE && obj->old_vars == NULL ) { /* * Can't calculate delta values until the second pass */ snmp_set_var_typed_integer( var, ASN_INTEGER, EXPERRCODE_RESOURCE ); var->type = ASN_NULL; return var; } /* * For a wildcarded object, search for the matching suffix. */ val_var = obj->vars; if ( obj->flags & EXP_OBJ_FLAG_OWILD ) { if ( !suffix ) { /* * If there's no suffix to match against, throw an error. * An exact expression with a wildcarded object is invalid. * XXX - Or just use first entry? */ snmp_set_var_typed_integer( var, ASN_INTEGER, EXPERRCODE_INDEX ); var->type = ASN_NULL; return var; } /* * Otherwise, we'll walk *all* wildcarded values in parallel. * This relies on the various varbind lists being set up with * exactly the same entries. A little extra preparation * during the data gathering simplifies things significantly! */ if ( obj->expObjectSampleType != EXPSAMPLETYPE_ABSOLUTE ) oval_var = obj->old_vars; if ( obj->flags & EXP_OBJ_FLAG_DWILD ) { dd_var = obj->dvars; odd_var = obj->old_dvars; } if ( obj->flags & EXP_OBJ_FLAG_CWILD ) cond_var = obj->cvars; n = obj->expObjectID_len; while ( val_var ) { if ( snmp_oid_compare( val_var->name+n, val_var->name_length-n, suffix, suffix_len )) break; val_var = val_var->next_variable; if (oval_var) oval_var = oval_var->next_variable; if (dd_var) { dd_var = dd_var->next_variable; odd_var = odd_var->next_variable; } if (cond_var) cond_var = cond_var->next_variable; } } if (!val_var) { /* * No matching entry */ snmp_set_var_typed_integer( var, ASN_INTEGER, EXPERRCODE_INDEX ); var->type = ASN_NULL; return var; } /* * Set up any non-wildcarded values - some * of which may be null. That's fine. */ if (!oval_var) oval_var = obj->old_vars; if (!dd_var) { dd_var = obj->dvars; odd_var = obj->old_dvars; } if (!cond_var) cond_var = obj->cvars; /* * ... and return the appropriate value. */ if (obj->expObjCond_len && (!cond_var || *cond_var->val.integer == 0)) { /* * expObjectConditional says no */ snmp_set_var_typed_integer( var, ASN_INTEGER, EXPERRCODE_INDEX ); var->type = ASN_NULL; return var; } if (dd_var && odd_var && *dd_var->val.integer != *odd_var->val.integer) { /* * expObjectDeltaD says no */ snmp_set_var_typed_integer( var, ASN_INTEGER, EXPERRCODE_INDEX ); var->type = ASN_NULL; return var; } /* * XXX - May need to check sysUpTime discontinuities * (unless this is handled earlier....) */ switch ( obj->expObjectSampleType ) { case EXPSAMPLETYPE_ABSOLUTE: snmp_clone_var( val_var, var ); break; case EXPSAMPLETYPE_DELTA: snmp_set_var_typed_integer( var, ASN_INTEGER /* or UNSIGNED? */, *val_var->val.integer - *oval_var->val.integer ); break; case EXPSAMPLETYPE_CHANGED: if ( val_var->val_len != oval_var->val_len ) n = 1; else if (memcmp( val_var->val.string, oval_var->val.string, val_var->val_len ) != 0 ) n = 1; else n = 0; snmp_set_var_typed_integer( var, ASN_UNSIGNED, n ); } return var; } /* * Utility routine to parse (and skip over) an integer constant */ int _expParse_integer( char *start, char **end ) { int n; char *cp; n = atoi(start); for (cp=start; *cp; cp++) if (!isdigit(*cp & 0xFF)) break; *end = cp; return n; } netsnmp_variable_list * _expValue_evalOperator(netsnmp_variable_list *left, netsnmp_variable_list *op, netsnmp_variable_list *right) { int n; if (left->val.integer == NULL || right->val.integer == NULL) { return right; } switch( *op->val.integer ) { case EXP_OPERATOR_ADD: n = *left->val.integer + *right->val.integer; break; case EXP_OPERATOR_SUBTRACT: n = *left->val.integer - *right->val.integer; break; case EXP_OPERATOR_MULTIPLY: n = *left->val.integer * *right->val.integer; break; case EXP_OPERATOR_DIVIDE: n = *left->val.integer / *right->val.integer; break; case EXP_OPERATOR_REMAINDER: n = *left->val.integer % *right->val.integer; break; case EXP_OPERATOR_BITXOR: n = *left->val.integer ^ *right->val.integer; break; case EXP_OPERATOR_BITNEGATE: n = 99; /* *left->val.integer ~ *right->val.integer; */ break; case EXP_OPERATOR_BITOR: n = *left->val.integer | *right->val.integer; break; case EXP_OPERATOR_BITAND: n = *left->val.integer & *right->val.integer; break; case EXP_OPERATOR_NOT: n = 99; /* *left->val.integer ! *right->val.integer; */ break; case EXP_OPERATOR_LESS: n = *left->val.integer < *right->val.integer; break; case EXP_OPERATOR_GREAT: n = *left->val.integer > *right->val.integer; break; case EXP_OPERATOR_EQUAL: n = *left->val.integer == *right->val.integer; break; case EXP_OPERATOR_NOTEQ: n = *left->val.integer != *right->val.integer; break; case EXP_OPERATOR_LESSEQ: n = *left->val.integer <= *right->val.integer; break; case EXP_OPERATOR_GREATEQ: n = *left->val.integer >= *right->val.integer; break; case EXP_OPERATOR_OR: n = *left->val.integer || *right->val.integer; break; case EXP_OPERATOR_AND: n = *left->val.integer && *right->val.integer; break; case EXP_OPERATOR_LSHIFT: n = *left->val.integer << *right->val.integer; break; case EXP_OPERATOR_RSHIFT: n = *left->val.integer >> *right->val.integer; break; break; default: left->next_variable = NULL; snmp_free_var(left); right->next_variable = NULL; snmp_free_var(right); snmp_set_var_typed_integer( op, ASN_INTEGER, EXPERRCODE_OPERATOR ); op->type = ASN_NULL; return op; } /* XXX */ left->next_variable = NULL; snmp_free_var(left); op->next_variable = NULL; snmp_free_var(op); snmp_set_var_typed_integer( right, ASN_INTEGER, n ); return right; } netsnmp_variable_list * _expValue_evalFunction(netsnmp_variable_list *func) { netsnmp_variable_list *params = func->next_variable; /* XXX */ params->next_variable = NULL; snmp_free_var(params); snmp_set_var_typed_integer( func, ASN_INTEGER, 99 ); return func; } netsnmp_variable_list *_expValue_evalExpr2(netsnmp_variable_list *exprAlDente); netsnmp_variable_list * _expValue_evalExpr( netsnmp_variable_list *expIdx, char *exprRaw, char **exprEnd, oid *suffix, size_t suffix_len ) { netsnmp_variable_list *exprAlDente = NULL; netsnmp_variable_list *vtail = NULL; char *cp1, *cp2; netsnmp_variable_list *var = NULL; int i, n; int neg = 0; oid oid_buf[MAX_OID_LEN]; DEBUGMSGTL(("disman:expr:eval1", "Evaluating '%s'\n", exprRaw)); if (!expIdx || !exprRaw) return NULL; /* * The expression is evaluated in two stages. * First, we simplify ("parboil") the raw expression, * tokenizing it into a sequence of varbind values, inserting * object parameters, and (recursively) evaluating any * parenthesised sub-expressions or function arguments. */ for (cp1=exprRaw; cp1 && *cp1; ) { switch (*cp1) { case '$': /* * Locate the appropriate instance of the specified * parameter, and insert the corresponding value. */ n = _expParse_integer( cp1+1, &cp1 ); var = _expValue_evalParam( expIdx, n, suffix, suffix_len ); if ( var && var->type == ASN_NULL ) { DEBUGMSGTL(("disman:expr:eval", "Invalid parameter '%d'\n", n)); /* Note position of failure in expression */ var->data = (void *)(cp1 - exprRaw); snmp_free_var(exprAlDente); *exprEnd = cp1; return var; } else { if (vtail) vtail->next_variable = var; else exprAlDente = var; vtail = var; var = NULL; } break; case '(': /* * Recursively evaluate the sub-expression */ var = _expValue_evalExpr( expIdx, cp1+1, &cp2, suffix, suffix_len ); if ( var && var->type == ASN_NULL ) { /* Adjust position of failure */ var->data = (void *)(cp1 - exprRaw + (uintptr_t)var->data); return var; } else if (*cp2 != ')') { snmp_free_var(exprAlDente); DEBUGMSGTL(("disman:expr:eval", "Unbalanced parenthesis\n")); snmp_set_var_typed_integer( var, ASN_INTEGER, EXPERRCODE_PARENTHESIS ); var->type = ASN_NULL; var->data = (void *)(cp2 - exprRaw); *exprEnd = cp1; return var; } else { if (vtail) vtail->next_variable = var; else exprAlDente = var; vtail = var; var = NULL; cp1 = cp2+1; /* Skip to end of sub-expression */ } break; case ')': case ',': /* * End of current (sub-)expression * Note the end-position, and evaluate * the parboiled list of tokens. */ *exprEnd = cp1; var = _expValue_evalExpr2( exprAlDente ); return var; /* === Constants === */ case '.': /* OID */ i = 0; memset( oid_buf, 0, sizeof(oid_buf)); while ( cp1 && *cp1 == '.' ) { n = _expParse_integer( cp1+1, &cp2 ); OID: oid_buf[i++] = n; cp1 = cp2; } var = snmp_varlist_add_variable( &exprAlDente, NULL, 0, ASN_OBJECT_ID, (u_char*)oid_buf, i*sizeof(oid)); break; DIGIT: case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': n = _expParse_integer( cp1, &cp2 ); if ( cp2 && *cp2 == '.' ) { i = 0; memset( oid_buf, 0, sizeof(oid_buf)); goto OID; } if ( neg ) n = -n; var = snmp_varlist_add_variable( &exprAlDente, NULL, 0, ASN_INTEGER, (u_char*)&n, sizeof(n)); vtail = var; var = NULL; neg = 0; cp1 = cp2; break; case '"': /* String Constant */ for ( cp2 = cp1+1; *cp2; cp2++ ) { if ( *cp2 == '"' ) break; if ( *cp2 == '\\' && *(cp2+1) == '"' ) cp2++; } if ( *cp2 != '"' ) { /* * Unterminated string */ DEBUGMSGTL(("disman:expr:eval", "Unterminated string\n")); snmp_set_var_typed_integer( var, ASN_INTEGER, EXPERRCODE_SYNTAX ); var->type = ASN_NULL; var->data = (void *)(cp2 - exprRaw); return var; } n = cp2 - cp1; var = snmp_varlist_add_variable( &exprAlDente, NULL, 0, ASN_OCTET_STR, (u_char*)cp1+1, n); vtail = var; var = NULL; break; /* === Operators === */ case '-': /* * Could be a unary minus.... */ if (!vtail || vtail->type == ASN_PRIV_OPERATOR) { neg = 1; goto DIGIT; } /* * ... or a (single-character) binary operator. */ /* Fallthrough */ case '+': case '*': case '/': case '%': case '^': case '~': if ( !vtail ) { /* * Can't start an expression with a binary operator */ DEBUGMSGTL(("disman:expr:eval", "Initial binary operator\n")); snmp_set_var_typed_integer( var, ASN_INTEGER, EXPERRCODE_SYNTAX ); var->type = ASN_NULL; var->data = (void *)(cp1 - exprRaw); *exprEnd = cp1; return var; } n = ops[ *cp1 & 0xFF ]; DEBUGMSGTL(("disman:expr:eval", "Binary operator %c (%d)\n", *cp1, n)); var = snmp_varlist_add_variable( &exprAlDente, NULL, 0, ASN_INTEGER, (u_char*)&n, sizeof(n)); var->type = ASN_PRIV_OPERATOR; vtail = var; cp1++; break; /* * Multi-character binary operators */ case '&': case '|': case '!': case '>': case '<': case '=': if ( !vtail ) { /* * Can't start an expression with a binary operator */ DEBUGMSGTL(("disman:expr:eval", "Initial binary operator\n")); snmp_set_var_typed_integer( var, ASN_INTEGER, EXPERRCODE_SYNTAX ); var->type = ASN_NULL; var->data = (void *)(cp1 - exprRaw); *exprEnd = cp1; return var; } if ( *(cp1+1) == '=' ) n = ops[ *cp1++ + 20]; else if ( *(cp1+1) == *cp1 ) n = ops[ *cp1++ - 30]; else n = ops[ *cp1 & 0xFF ]; var = snmp_varlist_add_variable( &exprAlDente, NULL, 0, ASN_INTEGER, (u_char*)&n, sizeof(n)); var->type = ASN_PRIV_OPERATOR; vtail = var; cp1++; break; /* === Functions === */ case 'a': /* average/arraySection */ case 'c': /* counter32/counter64 */ case 'e': /* exists */ case 'm': /* maximum/minimum */ case 'o': /* oidBegins/Ends/Contains */ case 's': /* sum / string{B,E,C} */ var = snmp_varlist_add_variable( &exprAlDente, NULL, 0, ASN_OCTET_STR, (u_char*)cp1, 3 ); /* XXX */ var->type = ASN_PRIV_FUNCTION; vtail = var; while (*cp1 >= 'a' && *cp1 <= 'z') cp1++; break; default: if (isalpha( *cp1 & 0xFF )) { /* * Unrecognised function call ? */ DEBUGMSGTL(("disman:expr:eval", "Unrecognised function '%s'\n", cp1)); snmp_set_var_typed_integer( var, ASN_INTEGER, EXPERRCODE_FUNCTION ); var->type = ASN_NULL; var->data = (void *)(cp1 - exprRaw); *exprEnd = cp1; return var; } else if (!isspace( *cp1 & 0xFF )) { /* * Unrecognised operator ? */ DEBUGMSGTL(("disman:expr:eval", "Unrecognised operator '%c'\n", *cp1)); snmp_set_var_typed_integer( var, ASN_INTEGER, EXPERRCODE_OPERATOR ); var->type = ASN_NULL; var->data = (void *)(cp1 - exprRaw); *exprEnd = cp1; return var; } cp1++; break; } } /* * ... then we evaluate the resulting simplified ("al dente") * expression, in the usual manner. */ *exprEnd = cp1; var = _expValue_evalExpr2( exprAlDente ); DEBUGMSGTL(( "disman:expr:eval1", "Evaluated to ")); DEBUGMSGVAR(("disman:expr:eval1", var)); DEBUGMSG(( "disman:expr:eval1", "\n")); /* snmp_free_var(exprAlDente); XXX - Crashes */ return var; } netsnmp_variable_list * _expValue_evalExpr2( netsnmp_variable_list *exprAlD ) { netsnmp_variable_list *stack = NULL; netsnmp_variable_list *lval, *rval, *op; netsnmp_variable_list *vp, *vp1 = NULL; DEBUGIF(( "disman:expr:eval2")) { for (vp = exprAlD; vp; vp=vp->next_variable) { if ( vp->type == ASN_PRIV_OPERATOR ) DEBUGMSGTL(( "disman:expr:eval2", "Operator %ld\n", *vp->val.integer)); else if ( vp->type == ASN_PRIV_FUNCTION ) DEBUGMSGTL(( "disman:expr:eval2", "Function %s\n", vp->val.string)); else { DEBUGMSGTL(( "disman:expr:eval2", "Operand ")); DEBUGMSGVAR(("disman:expr:eval2", vp)); DEBUGMSG(( "disman:expr:eval2", "\n")); } } } for (vp = exprAlD; vp; vp=vp1) { vp1 = vp->next_variable; if ( vp->type == ASN_PRIV_OPERATOR ) { /* Must *always* follow a value */ if ( !stack || stack->type == ASN_PRIV_OPERATOR ) { snmp_set_var_typed_integer( vp, ASN_INTEGER, EXPERRCODE_SYNTAX ); vp->type = ASN_NULL; snmp_free_var( stack ); return vp; } /* * Evaluate any higher priority operators * already on the stack.... */ while ( stack->next_variable && stack->next_variable->type == ASN_PRIV_OPERATOR && (*stack->next_variable->val.integer > *vp->val.integer)) { rval = stack; op = stack->next_variable; lval = op->next_variable; stack = lval->next_variable; rval = _expValue_evalOperator( lval, op, rval ); rval->next_variable = stack; stack = rval; } /* * ... and then push this operator onto the stack. */ vp->next_variable = stack; stack = vp; } else if ( vp->type == ASN_PRIV_FUNCTION ) { /* Must be first, or follow an operator */ if ( stack && stack->type != ASN_PRIV_OPERATOR ) { snmp_set_var_typed_integer( vp, ASN_INTEGER, EXPERRCODE_SYNTAX ); vp->type = ASN_NULL; snmp_free_var( stack ); return vp; } /* * Evaluate this function (consuming the * appropriate parameters from the token * list), and push the result onto the stack. */ vp = _expValue_evalFunction( vp ); vp1 = vp->next_variable; vp->next_variable = stack; stack = vp; } else { /* Must be first, or follow an operator */ if ( stack && stack->type != ASN_PRIV_OPERATOR ) { snmp_set_var_typed_integer( vp, ASN_INTEGER, EXPERRCODE_SYNTAX ); vp->type = ASN_NULL; snmp_free_var( stack ); return vp; } /* * Push this value onto the stack */ vp->next_variable = stack; stack = vp; } } /* * Now evaluate whatever's left on the stack * and return the final result. */ while ( stack && stack->next_variable ) { rval = stack; op = stack->next_variable; lval = op->next_variable; stack = lval->next_variable; rval = _expValue_evalOperator( lval, op, rval ); rval->next_variable = stack; stack = rval; } return stack; } /* ============= * Main API * ============= */ netsnmp_variable_list * expValue_evaluateExpression( struct expExpression *exp, oid *suffix, size_t suffix_len ) { char exprRaw[ EXP_STR3_LEN+1 ]; netsnmp_variable_list *var; netsnmp_variable_list owner_var, name_var, param_var; int n; char *cp; if (!exp) return NULL; /* * Gather data for evaluating expressions with no regular delta-value * sampling, i.e. expressions with sampling/delta interval of 0 */ expExpression_getData(0, exp); /* * Set up a varbind list containing the various index values * (including a placeholder for expObjectIndex). * * This saves having to construct the same index list repeatedly */ memset(&owner_var, 0, sizeof(netsnmp_variable_list)); memset(&name_var, 0, sizeof(netsnmp_variable_list)); memset(¶m_var, 0, sizeof(netsnmp_variable_list)); snmp_set_var_typed_value( &owner_var, ASN_OCTET_STR, (u_char*)exp->expOwner, strlen(exp->expOwner)); snmp_set_var_typed_value( &name_var, ASN_OCTET_STR, (u_char*)exp->expName, strlen(exp->expName)); n = 99; /* dummy value */ snmp_set_var_typed_value( ¶m_var, ASN_INTEGER, (u_char*)&n, sizeof(n)); owner_var.next_variable = &name_var; name_var.next_variable = ¶m_var; /* * Make a working copy of the expression, and evaluate it. */ memset(exprRaw, 0, sizeof(exprRaw)); memcpy(exprRaw, exp->expExpression, sizeof(exprRaw)); var = _expValue_evalExpr( &owner_var, exprRaw, &cp, suffix, suffix_len ); /* * Check for any problems, and record the appropriate error */ if (!cp || *cp != '\0') { /* * When we had finished, there was a lot * of bricks^Wcharacters left over.... */ _expValue_setError( exp, EXPERRCODE_SYNTAX, suffix, suffix_len, var ); return NULL; } if (!var) { /* Shouldn't happen */ _expValue_setError( exp, EXPERRCODE_RESOURCE, suffix, suffix_len, NULL ); return NULL; } if (var->type == ASN_NULL) { /* * Error explicitly reported from the evaluation routines. */ _expValue_setError( exp, *(var->val.integer), suffix, suffix_len, var ); return NULL; } if (0 /* COMPARE var->type WITH exp->expValueType */ ) { /* * XXX - Check to see whether the returned type (ASN_XXX) * is compatible with the requested type (an enum) */ /* If not, throw an error */ _expValue_setError( exp, EXPERRCODE_TYPE, suffix, suffix_len, var ); return NULL; } return var; } void _expValue_setError( struct expExpression *exp, int reason, oid *suffix, size_t suffix_len, netsnmp_variable_list *var) { if (!exp) return; exp->expErrorCount++; /* exp->expErrorTime = NOW; */ exp->expErrorIndex = ( var && var->data ? (uintptr_t)var->data : 0 ); exp->expErrorCode = reason; memset( exp->expErrorInstance, 0, sizeof(exp->expErrorInstance)); memcpy( exp->expErrorInstance, suffix, suffix_len * sizeof(oid)); exp->expErrorInst_len = suffix_len; if (var) var->data = NULL; snmp_free_var( var ); }