1 /*
2  * DisMan Event MIB:
3  *     Core implementation of the trigger handling behaviour
4  */
5 
6 #include <net-snmp/net-snmp-config.h>
7 #include <net-snmp/net-snmp-features.h>
8 #include <net-snmp/net-snmp-includes.h>
9 #include <net-snmp/agent/net-snmp-agent-includes.h>
10 #include "agent_global_vars.h"
11 #include "disman/event/mteTrigger.h"
12 #include "disman/event/mteEvent.h"
13 
14 netsnmp_feature_child_of(disman_debugging, libnetsnmpmibs);
15 netsnmp_feature_child_of(mtetrigger, libnetsnmpmibs);
16 netsnmp_feature_child_of(mtetrigger_removeentry, mtetrigger);
17 
18 netsnmp_tdata *trigger_table_data;
19 
20 oid    _sysUpTime_instance[] = { 1, 3, 6, 1, 2, 1, 1, 3, 0 };
21 size_t _sysUpTime_inst_len   = OID_LENGTH(_sysUpTime_instance);
22 
23 long mteTriggerFailures;
24 
25     /*
26      * Initialize the container for the (combined) mteTrigger*Table,
27      * regardless of which table initialisation routine is called first.
28      */
29 
30 void
init_trigger_table_data(void)31 init_trigger_table_data(void)
32 {
33     DEBUGMSGTL(("disman:event:init", "init trigger container\n"));
34     if (!trigger_table_data) {
35         trigger_table_data = netsnmp_tdata_create_table("mteTriggerTable", 0);
36         if (!trigger_table_data) {
37             snmp_log(LOG_ERR, "failed to create mteTriggerTable");
38             return;
39         }
40         DEBUGMSGTL(("disman:event:init", "create trigger container (%p)\n",
41                                           trigger_table_data));
42     }
43     mteTriggerFailures = 0;
44 }
45 
46 
47 /** Initializes the mteTrigger module */
48 void
init_mteTrigger(void)49 init_mteTrigger(void)
50 {
51     init_trigger_table_data();
52 
53 }
54 
55     /* ===================================================
56      *
57      * APIs for maintaining the contents of the (combined)
58      *    mteTrigger*Table container.
59      *
60      * =================================================== */
61 
62 #ifndef NETSNMP_FEATURE_REMOVE_DISMAN_DEBUGGING
63 void
_mteTrigger_dump(void)64 _mteTrigger_dump(void)
65 {
66     struct mteTrigger *entry;
67     netsnmp_tdata_row *row;
68     int i = 0;
69 
70     for (row = netsnmp_tdata_row_first(trigger_table_data);
71          row;
72          row = netsnmp_tdata_row_next(trigger_table_data, row)) {
73         entry = (struct mteTrigger *)row->data;
74         DEBUGMSGTL(("disman:event:dump", "TriggerTable entry %d: ", i));
75         DEBUGMSGOID(("disman:event:dump", row->oid_index.oids, row->oid_index.len));
76         DEBUGMSG(("disman:event:dump", "(%s, %s)",
77                                          row->indexes->val.string,
78                                          row->indexes->next_variable->val.string));
79         DEBUGMSG(("disman:event:dump", ": %p, %p\n", row, entry));
80         i++;
81     }
82     DEBUGMSGTL(("disman:event:dump", "TriggerTable %d entries\n", i));
83 }
84 #endif /* NETSNMP_FEATURE_REMOVE_DISMAN_DEBUGGING */
85 
86 /*
87  * Create a new row in the trigger table
88  */
89 netsnmp_tdata_row *
mteTrigger_createEntry(const char * mteOwner,char * mteTName,int fixed)90 mteTrigger_createEntry(const char *mteOwner, char *mteTName, int fixed)
91 {
92     struct mteTrigger *entry;
93     netsnmp_tdata_row *row;
94     size_t mteOwner_len = (mteOwner) ? strlen(mteOwner) : 0;
95     size_t mteTName_len = (mteTName) ? strlen(mteTName) : 0;
96 
97     DEBUGMSGTL(("disman:event:table", "Create trigger entry (%s, %s)\n",
98                                        mteOwner, mteTName));
99     /*
100      * Create the mteTrigger entry, and the
101      * (table-independent) row wrapper structure...
102      */
103     entry = SNMP_MALLOC_TYPEDEF(struct mteTrigger);
104     if (!entry)
105         return NULL;
106 
107     row = netsnmp_tdata_create_row();
108     if (!row) {
109         SNMP_FREE(entry);
110         return NULL;
111     }
112     row->data = entry;
113 
114     /*
115      * ... initialize this row with the indexes supplied
116      *     and the default values for the row...
117      */
118     if (mteOwner)
119         memcpy(entry->mteOwner, mteOwner, mteOwner_len);
120     netsnmp_table_row_add_index(row, ASN_OCTET_STR,
121                                 entry->mteOwner, mteOwner_len);
122     if (mteTName)
123         memcpy(entry->mteTName, mteTName, mteTName_len);
124     netsnmp_table_row_add_index(row, ASN_PRIV_IMPLIED_OCTET_STR,
125                                 entry->mteTName, mteTName_len);
126 
127   /* entry->mteTriggerTest         = MTE_TRIGGER_BOOLEAN; */
128     entry->mteTriggerValueID_len  = 2;  /* .0.0 */
129     entry->mteTriggerFrequency    = 600;
130     memcpy(entry->mteDeltaDiscontID, _sysUpTime_instance,
131                               sizeof(_sysUpTime_instance));
132     entry->mteDeltaDiscontID_len  =  _sysUpTime_inst_len;
133     entry->mteDeltaDiscontIDType  = MTE_DELTAD_TTICKS;
134     entry->flags                 |= MTE_TRIGGER_FLAG_SYSUPT;
135     entry->mteTExTest             = (MTE_EXIST_PRESENT | MTE_EXIST_ABSENT);
136     entry->mteTExStartup          = (MTE_EXIST_PRESENT | MTE_EXIST_ABSENT);
137     entry->mteTBoolComparison     = MTE_BOOL_UNEQUAL;
138     entry->flags                 |= MTE_TRIGGER_FLAG_BSTART;
139     entry->mteTThStartup          = MTE_THRESH_START_RISEFALL;
140 
141     if (fixed)
142         entry->flags |= MTE_TRIGGER_FLAG_FIXED;
143 
144     /*
145      * ... and insert the row into the (common) table container
146      */
147     netsnmp_tdata_add_row(trigger_table_data, row);
148     DEBUGMSGTL(("disman:event:table", "Trigger entry created\n"));
149     return row;
150 }
151 
152 #ifndef NETSNMP_FEATURE_REMOVE_MTETRIGGER_REMOVEENTRY
153 /*
154  * Remove a row from the trigger table
155  */
156 void
mteTrigger_removeEntry(netsnmp_tdata_row * row)157 mteTrigger_removeEntry(netsnmp_tdata_row *row)
158 {
159     struct mteTrigger *entry;
160 
161     if (!row)
162         return;                 /* Nothing to remove */
163     entry = (struct mteTrigger *)
164         netsnmp_tdata_remove_and_delete_row(trigger_table_data, row);
165     if (entry) {
166         mteTrigger_disable( entry );
167         SNMP_FREE(entry);
168     }
169 }
170 #endif /* NETSNMP_FEATURE_REMOVE_MTETRIGGER_REMOVEENTRY */
171 
172     /* ===================================================
173      *
174      * APIs for evaluating a trigger,
175      *   and firing the appropriate event
176      *
177      * =================================================== */
178 const char *_ops[] = { "",
179                 "!=", /* MTE_BOOL_UNEQUAL      */
180                 "==", /* MTE_BOOL_EQUAL        */
181                 "<",  /* MTE_BOOL_LESS         */
182                 "<=", /* MTE_BOOL_LESSEQUAL    */
183                 ">",  /* MTE_BOOL_GREATER      */
184                 ">="  /* MTE_BOOL_GREATEREQUAL */  };
185 
186 void
_mteTrigger_failure(const char * msg)187 _mteTrigger_failure( /* int error, */ const char *msg )
188 {
189     /*
190      * XXX - Send an mteTriggerFailure trap
191      *           (if configured to do so)
192      */
193     mteTriggerFailures++;
194     snmp_log(LOG_ERR, "%s\n", msg );
195     return;
196 }
197 
198 void
mteTrigger_run(unsigned int reg,void * clientarg)199 mteTrigger_run( unsigned int reg, void *clientarg)
200 {
201     struct mteTrigger *entry = (struct mteTrigger *)clientarg;
202     netsnmp_variable_list *var, *vtmp;
203     netsnmp_variable_list *vp1, *vp1_prev;
204     netsnmp_variable_list *vp2, *vp2_prev;
205     netsnmp_variable_list *dvar = NULL;
206     netsnmp_variable_list *dv1  = NULL, *dv2 = NULL;
207     netsnmp_variable_list sysUT_var;
208     int  cmp = 0, n, n2;
209     long value;
210     const char *reason;
211 
212     if (!entry) {
213         snmp_alarm_unregister( reg );
214         return;
215     }
216     if (!(entry->flags & MTE_TRIGGER_FLAG_ENABLED ) ||
217         !(entry->flags & MTE_TRIGGER_FLAG_ACTIVE  ) ||
218         !(entry->flags & MTE_TRIGGER_FLAG_VALID  )) {
219         return;
220     }
221 
222     {
223 	if (netsnmp_processing_set) {
224 	    /*
225 	     * netsnmp_handle_request will not be responsive to our efforts to
226 	     *	Retrieve the requested MIB value(s)...
227 	     * so we will skip it.
228 	     * https://sourceforge.net/tracker/
229 	     *	index.php?func=detail&aid=1557406&group_id=12694&atid=112694
230 	     */
231 	    DEBUGMSGTL(("disman:event:trigger:monitor",
232 		"Skipping trigger (%s) while netsnmp_processing_set\n",
233 		entry->mteTName));
234 	    return;
235 	}
236     }
237 
238     /*
239      * Retrieve the requested MIB value(s)...
240      */
241     DEBUGMSGTL(( "disman:event:trigger:monitor", "Running trigger (%s)\n", entry->mteTName));
242     var = (netsnmp_variable_list *)SNMP_MALLOC_TYPEDEF( netsnmp_variable_list );
243     if (!var) {
244         _mteTrigger_failure("failed to create mteTrigger query varbind");
245         return;
246     }
247     snmp_set_var_objid( var, entry->mteTriggerValueID,
248                              entry->mteTriggerValueID_len );
249     if ( entry->flags & MTE_TRIGGER_FLAG_VWILD ) {
250         n = netsnmp_query_walk( var, entry->session );
251     } else {
252         n = netsnmp_query_get(  var, entry->session );
253     }
254     if ( n != SNMP_ERR_NOERROR ) {
255         DEBUGMSGTL(( "disman:event:trigger:monitor", "Trigger query (%s) failed: %d\n",
256                            (( entry->flags & MTE_TRIGGER_FLAG_VWILD ) ? "walk" : "get"), n));
257         _mteTrigger_failure( "failed to run mteTrigger query" );
258         snmp_free_varbind(var);
259         return;
260     }
261 
262     /*
263      * ... canonicalise the results (to simplify later comparisons)...
264      */
265 
266     vp1 = var;                vp1_prev = NULL;
267     vp2 = entry->old_results; vp2_prev = NULL;
268     entry->count=0;
269     while (vp1) {
270            /*
271             * Flatten various missing values/exceptions into a single form
272             */
273 	switch (vp1->type) {
274         case SNMP_NOSUCHINSTANCE:
275         case SNMP_NOSUCHOBJECT:
276         case ASN_PRIV_RETRY:   /* Internal only ? */
277             vp1->type = ASN_NULL;
278         }
279            /*
280             * Keep track of how many entries have been retrieved.
281             */
282            entry->count++;
283 
284            /*
285             * Ensure previous and current result match
286             *  (with corresponding entries in both lists)
287             * and set the flags indicating which triggers are armed
288             */
289         if (vp2) {
290             cmp = snmp_oid_compare(vp1->name, vp1->name_length,
291                                    vp2->name, vp2->name_length);
292             if ( cmp < 0 ) {
293                 /*
294                  * If a new value has appeared, insert a matching
295                  * dummy entry into the previous result list.
296                  *
297                  * XXX - check how this is best done.
298                  */
299                 vtmp = SNMP_MALLOC_TYPEDEF( netsnmp_variable_list );
300                 if (!vtmp) {
301                     _mteTrigger_failure(
302                           "failed to create mteTrigger temp varbind");
303                     snmp_free_varbind(var);
304                     return;
305                 }
306                 vtmp->type = ASN_NULL;
307                 snmp_set_var_objid( vtmp, vp1->name, vp1->name_length );
308                 vtmp->next_variable = vp2;
309                 if (vp2_prev) {
310                     vp2_prev->next_variable = vtmp;
311                 } else {
312                     entry->old_results      = vtmp;
313                 }
314                 vp2_prev   = vtmp;
315                 vp1->index = MTE_ARMED_ALL;	/* XXX - plus a new flag */
316                 vp1_prev   = vp1;
317                 vp1        = vp1->next_variable;
318             }
319             else if ( cmp == 0 ) {
320                 /*
321                  * If it's a continuing entry, just copy across the armed flags
322                  */
323                 vp1->index = vp2->index;
324                 vp1_prev   = vp1;
325                 vp1        = vp1->next_variable;
326                 vp2_prev   = vp2;
327                 vp2        = vp2->next_variable;
328             } else {
329                 /*
330                  * If a value has just disappeared, insert a
331                  * matching dummy entry into the current result list.
332                  *
333                  * XXX - check how this is best done.
334                  *
335                  */
336                 if ( vp2->type != ASN_NULL ) {
337                     vtmp = SNMP_MALLOC_TYPEDEF( netsnmp_variable_list );
338                     if (!vtmp) {
339                         _mteTrigger_failure(
340                                  "failed to create mteTrigger temp varbind");
341                         snmp_free_varbind(var);
342                         return;
343                     }
344                     vtmp->type = ASN_NULL;
345                     snmp_set_var_objid( vtmp, vp2->name, vp2->name_length );
346                     vtmp->next_variable = vp1;
347                     if (vp1_prev) {
348                         vp1_prev->next_variable = vtmp;
349                     } else {
350                         var                     = vtmp;
351                     }
352                     vp1_prev = vtmp;
353                     vp2_prev = vp2;
354                     vp2      = vp2->next_variable;
355                 } else {
356                     /*
357                      * But only if this entry has *just* disappeared.  If the
358                      * entry from the last run was a dummy too, then remove it.
359                      *   (leaving vp2_prev unchanged)
360                      */
361                     vtmp = vp2;
362                     if (vp2_prev) {
363                         vp2_prev->next_variable = vp2->next_variable;
364                     } else {
365                         entry->old_results      = vp2->next_variable;
366                     }
367                     vp2  = vp2->next_variable;
368                     vtmp->next_variable = NULL;
369                     snmp_free_varbind( vtmp );
370                 }
371             }
372         } else {
373             /*
374              * No more old results to compare.
375              * Either all remaining values have only just been created ...
376              *   (and we need to create dummy 'old' entries for them)
377              */
378             if ( vp2_prev ) {
379                 vtmp = SNMP_MALLOC_TYPEDEF( netsnmp_variable_list );
380                 if (!vtmp) {
381                     _mteTrigger_failure(
382                              "failed to create mteTrigger temp varbind");
383                     snmp_free_varbind(var);
384                     return;
385                 }
386                 vtmp->type = ASN_NULL;
387                 snmp_set_var_objid( vtmp, vp1->name, vp1->name_length );
388                 vtmp->next_variable     = vp2_prev->next_variable;
389                 vp2_prev->next_variable = vtmp;
390                 vp2_prev                = vtmp;
391             }
392             /*
393              * ... or this is the first run through
394              *   (and there were no old results at all)
395              *
396              * In either case, mark the current entry as armed and new.
397              * Note that we no longer need to maintain 'vp1_prev'
398              */
399             vp1->index = MTE_ARMED_ALL;	/* XXX - plus a new flag */
400             vp1        = vp1->next_variable;
401         }
402     }
403 
404     /*
405      * ... and then work through these result(s), deciding
406      *     whether or not to trigger the corresponding event.
407      *
408      *  Note that there's no point in evaluating Existence or
409      *    Boolean tests if there's no corresponding event.
410      *   (Even if the trigger matched, nothing would be done anyway).
411      */
412     if ((entry->mteTriggerTest & MTE_TRIGGER_EXISTENCE) &&
413         (entry->mteTExEvent[0] != '\0' )) {
414         /*
415          * If we don't have a record of previous results,
416          * this must be the first time through, so consider
417          * the mteTriggerExistenceStartup tests.
418          */
419         if ( !entry->old_results ) {
420             /*
421              * With the 'present(0)' test, the trigger should fire
422              *   for each value in the varbind list returned
423              *   (whether the monitored value is wildcarded or not).
424              */
425             if (entry->mteTExTest & entry->mteTExStartup & MTE_EXIST_PRESENT) {
426                 for (vp1 = var; vp1; vp1=vp1->next_variable) {
427                     DEBUGMSGTL(( "disman:event:trigger:fire",
428                                  "Firing initial existence test: "));
429                     DEBUGMSGOID(("disman:event:trigger:fire",
430                                  vp1->name, vp1->name_length));
431                     DEBUGMSG((   "disman:event:trigger:fire",
432                                  " (present)\n"));
433                     entry->mteTriggerXOwner   = entry->mteTExObjOwner;
434                     entry->mteTriggerXObjects = entry->mteTExObjects;
435                     entry->mteTriggerFired    = vp1;
436                     n = entry->mteTriggerValueID_len;
437                     mteEvent_fire(entry->mteTExEvOwner, entry->mteTExEvent,
438                                   entry, vp1->name+n, vp1->name_length-n);
439                 }
440             }
441             /*
442              * An initial 'absent(1)' test only makes sense when
443              *   monitoring a non-wildcarded OID (how would we know
444              *   which rows of the table "ought" to exist, but don't?)
445              */
446             if (entry->mteTExTest & entry->mteTExStartup & MTE_EXIST_ABSENT) {
447                 if (!(entry->flags & MTE_TRIGGER_FLAG_VWILD) &&
448                     var->type == ASN_NULL ) {
449                     DEBUGMSGTL(( "disman:event:trigger:fire",
450                                  "Firing initial existence test: "));
451                     DEBUGMSGOID(("disman:event:trigger:fire",
452                                  var->name, var->name_length));
453                     DEBUGMSG((   "disman:event:trigger:fire",
454                                  " (absent)\n"));
455                     entry->mteTriggerXOwner   = entry->mteTExObjOwner;
456                     entry->mteTriggerXObjects = entry->mteTExObjects;
457                     /*
458                      * It's unclear what value the 'mteHotValue' payload
459                      *  should take when a monitored instance does not
460                      *  exist on startup. The only sensible option is
461                      *  to report a NULL value, but this clashes with
462                      * the syntax of the mteHotValue MIB object.
463                      */
464                     entry->mteTriggerFired    = var;
465                     n = entry->mteTriggerValueID_len;
466                     mteEvent_fire(entry->mteTExEvOwner, entry->mteTExEvent,
467                                   entry, var->name+n, var->name_length-n);
468                 }
469             }
470         } /* !old_results */
471             /*
472              * Otherwise, compare the current set of results with
473              * the previous ones, looking for changes.  We can
474              * assume that the two lists match (see above).
475              */
476         else {
477             for (vp1 = var, vp2 = entry->old_results;
478                  vp1;
479                  vp1=vp1->next_variable, vp2=vp2->next_variable) {
480 
481                 /* Use this field to indicate that the trigger should fire */
482                 entry->mteTriggerFired = NULL;
483                 reason                 = NULL;
484 
485                 if ((entry->mteTExTest & MTE_EXIST_PRESENT) &&
486                     (vp1->type != ASN_NULL) &&
487                     (vp2->type == ASN_NULL)) {
488                     /* A new instance has appeared */
489                     entry->mteTriggerFired = vp1;
490                     reason = "(present)";
491 
492                 } else if ((entry->mteTExTest & MTE_EXIST_ABSENT) &&
493                     (vp1->type == ASN_NULL) &&
494                     (vp2->type != ASN_NULL)) {
495 
496                     /*
497                      * A previous instance has disappeared.
498                      *
499                      * It's unclear what value the 'mteHotValue' payload
500                      *  should take when this happens - the previous
501                      *  value (vp2), or a NULL value (vp1) ?
502                      * NULL makes more sense logically, but clashes
503                      *  with the syntax of the mteHotValue MIB object.
504                      */
505                     entry->mteTriggerFired = vp2;
506                     reason = "(absent)";
507 
508                 } else if ((entry->mteTExTest & MTE_EXIST_CHANGED) &&
509                     ((vp1->val_len != vp2->val_len) ||
510                      (memcmp( vp1->val.string, vp2->val.string,
511                               vp1->val_len) != 0 ))) {
512                     /*
513                      * This comparison detects changes in *any* type
514                      *  of value, numeric or string (or even OID).
515                      *
516                      * Unfortunately, the default 'mteTriggerFired'
517                      *  notification payload can't report non-numeric
518                      *  changes properly (see syntax of 'mteHotValue')
519                      */
520                     entry->mteTriggerFired = vp1;
521                     reason = "(changed)";
522                 }
523                 if ( entry->mteTriggerFired ) {
524                     /*
525                      * One of the above tests has matched,
526                      *   so fire the trigger.
527                      */
528                     DEBUGMSGTL(( "disman:event:trigger:fire",
529                                  "Firing existence test: "));
530                     DEBUGMSGOID(("disman:event:trigger:fire",
531                                  vp1->name, vp1->name_length));
532                     DEBUGMSG((   "disman:event:trigger:fire",
533                                  " %s\n", reason));
534                     entry->mteTriggerXOwner   = entry->mteTExObjOwner;
535                     entry->mteTriggerXObjects = entry->mteTExObjects;
536                     n = entry->mteTriggerValueID_len;
537                     mteEvent_fire(entry->mteTExEvOwner, entry->mteTExEvent,
538                                   entry, vp1->name+n, vp1->name_length-n);
539                 }
540             }
541         } /* !old_results - end of else block */
542     } /* MTE_TRIGGER_EXISTENCE */
543 
544     /*
545      * We'll need sysUpTime.0 regardless...
546      */
547     DEBUGMSGTL(("disman:event:delta", "retrieve sysUpTime.0\n"));
548     memset( &sysUT_var, 0, sizeof( netsnmp_variable_list ));
549     snmp_set_var_objid( &sysUT_var, _sysUpTime_instance, _sysUpTime_inst_len );
550     netsnmp_query_get(  &sysUT_var, entry->session );
551 
552     if (( entry->mteTriggerTest & MTE_TRIGGER_BOOLEAN   ) ||
553         ( entry->mteTriggerTest & MTE_TRIGGER_THRESHOLD )) {
554         /*
555          * Although Existence tests can work with any syntax values,
556          * Boolean and Threshold tests are integer-only.  Ensure that
557          * the returned value(s) are appropriate.
558          *
559          * Note that we only need to check the first value, since all
560          *  instances of a given object should have the same syntax.
561          */
562         switch (var->type) {
563         case ASN_INTEGER:
564         case ASN_COUNTER:
565         case ASN_GAUGE:
566         case ASN_TIMETICKS:
567         case ASN_UINTEGER:
568         case ASN_COUNTER64:
569 #ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES
570         case ASN_OPAQUE_COUNTER64:
571         case ASN_OPAQUE_U64:
572         case ASN_OPAQUE_I64:
573 #endif
574             /* OK */
575             break;
576         default:
577             /*
578              * Other syntax values can't be used for Boolean/Theshold
579              * tests. Report this as an error, and then rotate the
580              * results ready for the next run, (which will presumably
581              * also detect this as an error once again!)
582              */
583             DEBUGMSGTL(( "disman:event:trigger:fire",
584                          "Returned non-integer result(s): "));
585             DEBUGMSGOID(("disman:event:trigger:fire",
586                          var->name, var->name_length));
587             DEBUGMSG((   "disman:event:trigger:fire",
588                          " (boolean/threshold) %d\n", var->type));
589             snmp_free_varbind( entry->old_results );
590             entry->old_results = var;
591             return;
592         }
593 
594 
595         /*
596          * Retrieve the discontinuity markers for delta-valued samples.
597          * (including sysUpTime.0 if not specified explicitly).
598          */
599         if ( entry->flags & MTE_TRIGGER_FLAG_DELTA ) {
600 
601             if (!(entry->flags & MTE_TRIGGER_FLAG_SYSUPT)) {
602                 /*
603                  * ... but only retrieve the configured discontinuity
604                  *      marker(s) if they refer to something different.
605                  */
606                 DEBUGMSGTL(( "disman:event:delta",
607                              "retrieve discontinuity marker(s): "));
608                 DEBUGMSGOID(("disman:event:delta", entry->mteDeltaDiscontID,
609                                                entry->mteDeltaDiscontID_len ));
610                 DEBUGMSG((   "disman:event:delta", " %s\n",
611                      (entry->flags & MTE_TRIGGER_FLAG_DWILD ? " (wild)" : "")));
612 
613                 dvar = (netsnmp_variable_list *)
614                                 SNMP_MALLOC_TYPEDEF( netsnmp_variable_list );
615                 if (!dvar) {
616                     _mteTrigger_failure(
617                             "failed to create mteTrigger delta query varbind");
618                     return;
619                 }
620                 snmp_set_var_objid( dvar, entry->mteDeltaDiscontID,
621                                           entry->mteDeltaDiscontID_len );
622                 if ( entry->flags & MTE_TRIGGER_FLAG_DWILD ) {
623                     n = netsnmp_query_walk( dvar, entry->session );
624                 } else {
625                     n = netsnmp_query_get(  dvar, entry->session );
626                 }
627                 if ( n != SNMP_ERR_NOERROR ) {
628                     _mteTrigger_failure( "failed to run mteTrigger delta query" );
629                     snmp_free_varbind( dvar );
630                     return;
631                 }
632             }
633 
634             /*
635              * We can't calculate delta values the first time through,
636              *  so there's no point in evaluating the remaining tests.
637              *
638              * Save the results (and discontinuity markers),
639              *   ready for the next run.
640              */
641             if ( !entry->old_results ) {
642                 entry->old_results =  var;
643                 entry->old_deltaDs = dvar;
644                 entry->sysUpTime   = *sysUT_var.val.integer;
645                 return;
646             }
647             /*
648              * If the sysUpTime marker has been reset (or strictly,
649              *   has advanced by less than the monitor frequency),
650              *  there's no point in trying the remaining tests.
651              */
652 
653             if (*sysUT_var.val.integer < entry->sysUpTime) {
654                 DEBUGMSGTL(( "disman:event:delta",
655                              "single discontinuity: (sysUT)\n"));
656                 snmp_free_varbind( entry->old_results );
657                 snmp_free_varbind( entry->old_deltaDs );
658                 entry->old_results =  var;
659                 entry->old_deltaDs = dvar;
660                 entry->sysUpTime   = *sysUT_var.val.integer;
661                 return;
662             }
663             /*
664              * Similarly if a separate (non-wildcarded) discontinuity
665              *  marker has changed, then there's no
666              *  point in trying to evaluate these tests either.
667              */
668             if (!(entry->flags & MTE_TRIGGER_FLAG_DWILD)  &&
669                 !(entry->flags & MTE_TRIGGER_FLAG_SYSUPT) &&
670                   (!entry->old_deltaDs ||
671                    (entry->old_deltaDs->val.integer != dvar->val.integer))) {
672                 DEBUGMSGTL((  "disman:event:delta", "single discontinuity: ("));
673                 DEBUGMSGOID(( "disman:event:delta", entry->mteDeltaDiscontID,
674                                            entry->mteDeltaDiscontID_len));
675                 DEBUGMSG((    "disman:event:delta", ")\n"));
676                 snmp_free_varbind( entry->old_results );
677                 snmp_free_varbind( entry->old_deltaDs );
678                 entry->old_results =  var;
679                 entry->old_deltaDs = dvar;
680                 entry->sysUpTime   = *sysUT_var.val.integer;
681                 return;
682             }
683 
684             /*
685              * Ensure that the list of (wildcarded) discontinuity
686              *  markers matches the list of monitored values
687              *  (inserting/removing discontinuity varbinds as needed)
688              *
689              * XXX - An alternative approach would be to use the list
690              *    of monitored values (instance subidentifiers) to build
691              *    the exact list of delta markers to retrieve earlier.
692              */
693             if (entry->flags & MTE_TRIGGER_FLAG_DWILD) {
694                 vp1      =  var;
695                 vp2      = dvar;
696                 vp2_prev = NULL;
697                 n  = entry->mteTriggerValueID_len;
698                 n2 = entry->mteDeltaDiscontID_len;
699                 while (vp1) {
700                     /*
701                      * For each monitored instance, check whether
702                      *   there's a matching discontinuity entry.
703                      */
704                     cmp = snmp_oid_compare(vp1->name+n,  vp1->name_length-n,
705                                            vp2->name+n2, vp2->name_length-n2 );
706                     if ( cmp < 0 ) {
707                         /*
708                          * If a discontinuity entry is missing,
709                          *   insert a (dummy) varbind.
710                          * The corresponding delta calculation will
711                          *   fail, but this simplifies the later code.
712                          */
713                         vtmp = (netsnmp_variable_list *)
714                                 SNMP_MALLOC_TYPEDEF( netsnmp_variable_list );
715                         if (!vtmp) {
716                             _mteTrigger_failure(
717                                   "failed to create mteTrigger discontinuity varbind");
718                             snmp_free_varbind(dvar);
719                             return;
720                         }
721                         snmp_set_var_objid(vtmp, entry->mteDeltaDiscontID,
722                                                  entry->mteDeltaDiscontID_len);
723                             /* XXX - append instance subids */
724                         vtmp->next_variable     = vp2;
725                         vp2_prev->next_variable = vtmp;
726                         vp2_prev                = vtmp;
727                         vp1 = vp1->next_variable;
728                     } else if ( cmp == 0 ) {
729                         /*
730                          * Matching discontinuity entry -  all OK.
731                          */
732                         vp2_prev = vp2;
733                         vp2      = vp2->next_variable;
734                         vp1      = vp1->next_variable;
735                     } else {
736                         /*
737                          * Remove unneeded discontinuity entry
738                          */
739                         vtmp = vp2;
740                         vp2_prev->next_variable = vp2->next_variable;
741                         vp2                     = vp2->next_variable;
742                         vtmp->next_variable = NULL;
743                         snmp_free_varbind( vtmp );
744                     }
745                 }
746                 /*
747                  * XXX - Now need to ensure that the old list of
748                  *   delta discontinuity markers matches as well.
749                  */
750             }
751         } /* delta samples */
752     } /* Boolean/Threshold test checks */
753 
754 
755 
756     /*
757      * Only run the Boolean tests if there's an event to be triggered
758      */
759     if ((entry->mteTriggerTest & MTE_TRIGGER_BOOLEAN) &&
760         (entry->mteTBoolEvent[0] != '\0' )) {
761 
762         if (entry->flags & MTE_TRIGGER_FLAG_DELTA) {
763             vp2 = entry->old_results;
764             if (entry->flags & MTE_TRIGGER_FLAG_DWILD) {
765                 dv1 = dvar;
766                 dv2 = entry->old_deltaDs;
767             }
768         }
769         for ( vp1 = var; vp1; vp1=vp1->next_variable ) {
770             /*
771              * Determine the value to be monitored...
772              */
773             if ( !vp1->val.integer ) {  /* No value */
774                 if ( vp2 )
775                     vp2 = vp2->next_variable;
776                 continue;
777             }
778             if (entry->flags & MTE_TRIGGER_FLAG_DELTA) {
779                 if (entry->flags & MTE_TRIGGER_FLAG_DWILD) {
780                     /*
781                      * We've already checked any non-wildcarded
782                      *   discontinuity markers (inc. sysUpTime.0).
783                      * Validate this particular sample against
784                      *   the relevant wildcarded marker...
785                      */
786                     if ((dv1->type == ASN_NULL)  ||
787                         (dv1->type != dv2->type) ||
788                         (*dv1->val.integer != *dv2->val.integer)) {
789                         /*
790                          * Bogus or changed discontinuity marker.
791                          * Need to skip this sample.
792                          */
793     DEBUGMSGTL(( "disman:event:delta", "discontinuity occurred: "));
794     DEBUGMSGOID(("disman:event:delta", vp1->name,
795                                        vp1->name_length ));
796     DEBUGMSG((   "disman:event:delta", " \n" ));
797                         vp2 = vp2->next_variable;
798                         continue;
799                     }
800                 }
801                 /*
802                  * ... and check there is a previous sample to calculate
803                  *   the delta value against (regardless of whether the
804                  *   discontinuity marker was wildcarded or not).
805                  */
806                 if (vp2->type == ASN_NULL) {
807     DEBUGMSGTL(( "disman:event:delta", "missing sample: "));
808     DEBUGMSGOID(("disman:event:delta", vp1->name,
809                                        vp1->name_length ));
810     DEBUGMSG((   "disman:event:delta", " \n" ));
811                     vp2 = vp2->next_variable;
812                     continue;
813                 }
814                 value = (*vp1->val.integer - *vp2->val.integer);
815     DEBUGMSGTL(( "disman:event:delta", "delta sample: "));
816     DEBUGMSGOID(("disman:event:delta", vp1->name,
817                                        vp1->name_length ));
818     DEBUGMSG((   "disman:event:delta", " (%ld - %ld) = %ld\n",
819                 *vp1->val.integer,  *vp2->val.integer, value));
820                 vp2 = vp2->next_variable;
821             } else {
822                 value = *vp1->val.integer;
823             }
824 
825             /*
826              * ... evaluate the comparison ...
827              */
828             switch (entry->mteTBoolComparison) {
829             case MTE_BOOL_UNEQUAL:
830                 cmp = ( value != entry->mteTBoolValue );
831                 break;
832             case MTE_BOOL_EQUAL:
833                 cmp = ( value == entry->mteTBoolValue );
834                 break;
835             case MTE_BOOL_LESS:
836                 cmp = ( value <  entry->mteTBoolValue );
837                 break;
838             case MTE_BOOL_LESSEQUAL:
839                 cmp = ( value <= entry->mteTBoolValue );
840                 break;
841             case MTE_BOOL_GREATER:
842                 cmp = ( value >  entry->mteTBoolValue );
843                 break;
844             case MTE_BOOL_GREATEREQUAL:
845                 cmp = ( value >= entry->mteTBoolValue );
846                 break;
847             }
848     DEBUGMSGTL(( "disman:event:delta", "Bool comparison: (%ld %s %ld) %d\n",
849                           value, _ops[entry->mteTBoolComparison],
850                           entry->mteTBoolValue, cmp));
851 
852             /*
853              * ... and decide whether to trigger the event.
854              *    (using the 'index' field of the varbind structure
855              *     to remember whether the trigger has already fired)
856              */
857             if ( cmp ) {
858               if (vp1->index & MTE_ARMED_BOOLEAN ) {
859                 vp1->index &= ~MTE_ARMED_BOOLEAN;
860                 /*
861                  * NB: Clear the trigger armed flag even if the
862                  *   (starting) event dosn't actually fire.
863                  *   Otherwise initially true (but suppressed)
864                  *   triggers will fire on the *second* probe.
865                  */
866                 if ( entry->old_results ||
867                     (entry->flags & MTE_TRIGGER_FLAG_BSTART)) {
868                     DEBUGMSGTL(( "disman:event:trigger:fire",
869                                  "Firing boolean test: "));
870                     DEBUGMSGOID(("disman:event:trigger:fire",
871                                  vp1->name, vp1->name_length));
872                     DEBUGMSG((   "disman:event:trigger:fire", "%s\n",
873                                   (entry->old_results ? "" : " (startup)")));
874                     entry->mteTriggerXOwner   = entry->mteTBoolObjOwner;
875                     entry->mteTriggerXObjects = entry->mteTBoolObjects;
876                     /*
877                      * XXX - when firing a delta-based trigger, should
878                      *   'mteHotValue' report the actual value sampled
879                      *   (as here), or the delta that triggered the event ?
880                      */
881                     entry->mteTriggerFired    = vp1;
882                     n = entry->mteTriggerValueID_len;
883                     mteEvent_fire(entry->mteTBoolEvOwner, entry->mteTBoolEvent,
884                                   entry, vp1->name+n, vp1->name_length-n);
885                 }
886               }
887             } else {
888                 vp1->index |= MTE_ARMED_BOOLEAN;
889             }
890         }
891     }
892 
893 
894     /*
895      * Only run the basic threshold tests if there's an event to
896      *    be triggered.  (Either rising or falling will do)
897      */
898     if (( entry->mteTriggerTest & MTE_TRIGGER_THRESHOLD ) &&
899         ((entry->mteTThRiseEvent[0] != '\0' ) ||
900          (entry->mteTThFallEvent[0] != '\0' ))) {
901 
902         /*
903          * The same delta-sample validation from Boolean
904          *   tests also applies here too.
905          */
906         if (entry->flags & MTE_TRIGGER_FLAG_DELTA) {
907             vp2 = entry->old_results;
908             if (entry->flags & MTE_TRIGGER_FLAG_DWILD) {
909                 dv1 = dvar;
910                 dv2 = entry->old_deltaDs;
911             }
912         }
913         for ( vp1 = var; vp1; vp1=vp1->next_variable ) {
914             /*
915              * Determine the value to be monitored...
916              */
917             if ( !vp1->val.integer ) {  /* No value */
918                 if ( vp2 )
919                     vp2 = vp2->next_variable;
920                 continue;
921             }
922             if (entry->flags & MTE_TRIGGER_FLAG_DELTA) {
923                 if (entry->flags & MTE_TRIGGER_FLAG_DWILD) {
924                     /*
925                      * We've already checked any non-wildcarded
926                      *   discontinuity markers (inc. sysUpTime.0).
927                      * Validate this particular sample against
928                      *   the relevant wildcarded marker...
929                      */
930                     if ((dv1->type == ASN_NULL)  ||
931                         (dv1->type != dv2->type) ||
932                         (*dv1->val.integer != *dv2->val.integer)) {
933                         /*
934                          * Bogus or changed discontinuity marker.
935                          * Need to skip this sample.
936                          */
937                         vp2 = vp2->next_variable;
938                         continue;
939                     }
940                 }
941                 /*
942                  * ... and check there is a previous sample to calculate
943                  *   the delta value against (regardless of whether the
944                  *   discontinuity marker was wildcarded or not).
945                  */
946                 if (vp2->type == ASN_NULL) {
947                     vp2 = vp2->next_variable;
948                     continue;
949                 }
950                 value = (*vp1->val.integer - *vp2->val.integer);
951                 vp2 = vp2->next_variable;
952             } else {
953                 value = *vp1->val.integer;
954             }
955 
956             /*
957              * ... evaluate the single-value comparisons,
958              *     and decide whether to trigger the event.
959              */
960             cmp = vp1->index;   /* working copy of 'armed' flags */
961             if ( value >= entry->mteTThRiseValue ) {
962               if (cmp & MTE_ARMED_TH_RISE ) {
963                 cmp &= ~MTE_ARMED_TH_RISE;
964                 cmp |=  MTE_ARMED_TH_FALL;
965                 /*
966                  * NB: Clear the trigger armed flag even if the
967                  *   (starting) event dosn't actually fire.
968                  *   Otherwise initially true (but suppressed)
969                  *   triggers will fire on the *second* probe.
970                  * Similarly for falling thresholds (see below).
971                  */
972                 if ( entry->old_results ||
973                     (entry->mteTThStartup & MTE_THRESH_START_RISE)) {
974                     DEBUGMSGTL(( "disman:event:trigger:fire",
975                                  "Firing rising threshold test: "));
976                     DEBUGMSGOID(("disman:event:trigger:fire",
977                                  vp1->name, vp1->name_length));
978                     DEBUGMSG((   "disman:event:trigger:fire", "%s\n",
979                                  (entry->old_results ? "" : " (startup)")));
980                     /*
981                      * If no riseEvent is configured, we need still to
982                      *  set the armed flags appropriately, but there's
983                      *  no point in trying to fire the (missing) event.
984                      */
985                     if (entry->mteTThRiseEvent[0] != '\0' ) {
986                         entry->mteTriggerXOwner   = entry->mteTThObjOwner;
987                         entry->mteTriggerXObjects = entry->mteTThObjects;
988                         entry->mteTriggerFired    = vp1;
989                         n = entry->mteTriggerValueID_len;
990                         mteEvent_fire(entry->mteTThRiseOwner,
991                                       entry->mteTThRiseEvent,
992                                       entry, vp1->name+n, vp1->name_length-n);
993                     }
994                 }
995               }
996             }
997 
998             if ( value <= entry->mteTThFallValue ) {
999               if (cmp & MTE_ARMED_TH_FALL ) {
1000                 cmp &= ~MTE_ARMED_TH_FALL;
1001                 cmp |=  MTE_ARMED_TH_RISE;
1002                 /* Clear the trigger armed flag (see above) */
1003                 if ( entry->old_results ||
1004                     (entry->mteTThStartup & MTE_THRESH_START_FALL)) {
1005                     DEBUGMSGTL(( "disman:event:trigger:fire",
1006                                  "Firing falling threshold test: "));
1007                     DEBUGMSGOID(("disman:event:trigger:fire",
1008                                  vp1->name, vp1->name_length));
1009                     DEBUGMSG((   "disman:event:trigger:fire", "%s\n",
1010                                  (entry->old_results ? "" : " (startup)")));
1011                     /*
1012                      * Similarly, if no fallEvent is configured,
1013                      *  there's no point in trying to fire it either.
1014                      */
1015                     if (entry->mteTThRiseEvent[0] != '\0' ) {
1016                         entry->mteTriggerXOwner   = entry->mteTThObjOwner;
1017                         entry->mteTriggerXObjects = entry->mteTThObjects;
1018                         entry->mteTriggerFired    = vp1;
1019                         n = entry->mteTriggerValueID_len;
1020                         mteEvent_fire(entry->mteTThFallOwner,
1021                                       entry->mteTThFallEvent,
1022                                       entry, vp1->name+n, vp1->name_length-n);
1023                     }
1024                 }
1025               }
1026             }
1027             vp1->index = cmp;
1028         }
1029     }
1030 
1031     /*
1032      * The same processing also works for delta-threshold tests (if configured)
1033      */
1034     if (( entry->mteTriggerTest & MTE_TRIGGER_THRESHOLD ) &&
1035         ((entry->mteTThDRiseEvent[0] != '\0' ) ||
1036          (entry->mteTThDFallEvent[0] != '\0' ))) {
1037 
1038         /*
1039          * Delta-threshold tests can only be used with
1040          *   absolute valued samples.
1041          */
1042         vp2 = entry->old_results;
1043         if (entry->flags & MTE_TRIGGER_FLAG_DELTA) {
1044             DEBUGMSGTL(( "disman:event:trigger",
1045                          "Delta-threshold on delta-sample\n"));
1046         } else if ( vp2 != NULL ) {
1047           for ( vp1 = var; vp1; vp1=vp1->next_variable ) {
1048             /*
1049              * Determine the value to be monitored...
1050              *  (similar to previous delta-sample processing,
1051              *   but without the discontinuity marker checks)
1052              */
1053             if (!vp2) {
1054                 break;   /* Run out of 'old' values */
1055             }
1056             if (( !vp1->val.integer ) ||
1057                 (vp2->type == ASN_NULL)) {
1058                 vp2 = vp2->next_variable;
1059                 continue;
1060             }
1061             value = (*vp1->val.integer - *vp2->val.integer);
1062             vp2 = vp2->next_variable;
1063 
1064             /*
1065              * ... evaluate the single-value comparisons,
1066              *     and decide whether to trigger the event.
1067              */
1068             cmp = vp1->index;   /* working copy of 'armed' flags */
1069             if ( value >= entry->mteTThDRiseValue ) {
1070                 if (vp1->index & MTE_ARMED_TH_DRISE ) {
1071                     DEBUGMSGTL(( "disman:event:trigger:fire",
1072                                  "Firing rising delta threshold test: "));
1073                     DEBUGMSGOID(("disman:event:trigger:fire",
1074                                  vp1->name, vp1->name_length));
1075                     DEBUGMSG((   "disman:event:trigger:fire", "\n"));
1076                     cmp &= ~MTE_ARMED_TH_DRISE;
1077                     cmp |=  MTE_ARMED_TH_DFALL;
1078                     /*
1079                      * If no riseEvent is configured, we need still to
1080                      *  set the armed flags appropriately, but there's
1081                      *  no point in trying to fire the (missing) event.
1082                      */
1083                     if (entry->mteTThDRiseEvent[0] != '\0' ) {
1084                         entry->mteTriggerXOwner   = entry->mteTThObjOwner;
1085                         entry->mteTriggerXObjects = entry->mteTThObjects;
1086                         entry->mteTriggerFired    = vp1;
1087                         n = entry->mteTriggerValueID_len;
1088                         mteEvent_fire(entry->mteTThDRiseOwner,
1089                                       entry->mteTThDRiseEvent,
1090                                       entry, vp1->name+n, vp1->name_length-n);
1091                     }
1092                 }
1093             }
1094 
1095             if ( value <= entry->mteTThDFallValue ) {
1096                 if (vp1->index & MTE_ARMED_TH_DFALL ) {
1097                     DEBUGMSGTL(( "disman:event:trigger:fire",
1098                                  "Firing falling delta threshold test: "));
1099                     DEBUGMSGOID(("disman:event:trigger:fire",
1100                                  vp1->name, vp1->name_length));
1101                     DEBUGMSG((   "disman:event:trigger:fire", "\n"));
1102                     cmp &= ~MTE_ARMED_TH_DFALL;
1103                     cmp |=  MTE_ARMED_TH_DRISE;
1104                     /*
1105                      * Similarly, if no fallEvent is configured,
1106                      *  there's no point in trying to fire it either.
1107                      */
1108                     if (entry->mteTThDRiseEvent[0] != '\0' ) {
1109                         entry->mteTriggerXOwner   = entry->mteTThObjOwner;
1110                         entry->mteTriggerXObjects = entry->mteTThObjects;
1111                         entry->mteTriggerFired    = vp1;
1112                         n = entry->mteTriggerValueID_len;
1113                         mteEvent_fire(entry->mteTThDFallOwner,
1114                                       entry->mteTThDFallEvent,
1115                                       entry, vp1->name+n, vp1->name_length-n);
1116                     }
1117                 }
1118             }
1119             vp1->index = cmp;
1120           }
1121         }
1122     }
1123 
1124     /*
1125      * Finally, rotate the results - ready for the next run.
1126      */
1127     snmp_free_varbind( entry->old_results );
1128     entry->old_results = var;
1129     if ( entry->flags & MTE_TRIGGER_FLAG_DELTA ) {
1130         snmp_free_varbind( entry->old_deltaDs );
1131         entry->old_deltaDs = dvar;
1132         entry->sysUpTime   = *sysUT_var.val.integer;
1133     }
1134 }
1135 
1136 void
mteTrigger_enable(struct mteTrigger * entry)1137 mteTrigger_enable( struct mteTrigger *entry )
1138 {
1139     if (!entry)
1140         return;
1141 
1142     if (entry->alarm) {
1143         /* XXX - or explicitly call mteTrigger_disable ?? */
1144         snmp_alarm_unregister( entry->alarm );
1145         entry->alarm = 0;
1146     }
1147 
1148     if (entry->mteTriggerFrequency) {
1149         /*
1150          * register once to run ASAP, and another to run
1151          * at the trigger frequency
1152          */
1153         snmp_alarm_register(0, 0, mteTrigger_run, entry );
1154         entry->alarm = snmp_alarm_register(
1155                            entry->mteTriggerFrequency, SA_REPEAT,
1156                            mteTrigger_run, entry );
1157     }
1158 }
1159 
1160 void
mteTrigger_disable(struct mteTrigger * entry)1161 mteTrigger_disable( struct mteTrigger *entry )
1162 {
1163     if (!entry)
1164         return;
1165 
1166     if (entry->alarm) {
1167         snmp_alarm_unregister( entry->alarm );
1168         entry->alarm = 0;
1169         /* XXX - perhaps release any previous results */
1170     }
1171 }
1172 
1173 long _mteTrigger_MaxCount = 0;
_mteTrigger_countEntries(void)1174 long _mteTrigger_countEntries(void)
1175 {
1176     struct mteTrigger *entry;
1177     netsnmp_tdata_row *row;
1178     long count = 0;
1179 
1180     for (row = netsnmp_tdata_row_first(trigger_table_data);
1181          row;
1182          row = netsnmp_tdata_row_next(trigger_table_data, row)) {
1183         entry  = (struct mteTrigger *)row->data;
1184         count += entry->count;
1185     }
1186 
1187     return count;
1188 }
1189 
mteTrigger_getNumEntries(int max)1190 long mteTrigger_getNumEntries(int max)
1191 {
1192     long count;
1193     /* XXX - implement some form of caching ??? */
1194     count = _mteTrigger_countEntries();
1195     if ( count > _mteTrigger_MaxCount )
1196         _mteTrigger_MaxCount = count;
1197 
1198     return ( max ?  _mteTrigger_MaxCount : count);
1199 }
1200