1 /*
2  * callback.c: A generic callback mechanism
3  */
4 /* Portions of this file are subject to the following copyright(s).  See
5  * the Net-SNMP's COPYING file for more details and other copyrights
6  * that may apply:
7  */
8 /*
9  * Portions of this file are copyrighted by:
10  * Copyright � 2003 Sun Microsystems, Inc. All rights reserved.
11  * Use is subject to license terms specified in the COPYING file
12  * distributed with the Net-SNMP package.
13  */
14 /** @defgroup callback A generic callback mechanism
15  *  @ingroup library
16  *
17  *  @{
18  */
19 #include <net-snmp/net-snmp-config.h>
20 #include <net-snmp/net-snmp-features.h>
21 #include <sys/types.h>
22 #include <stdio.h>
23 #if HAVE_STDLIB_H
24 #include <stdlib.h>
25 #endif
26 #if HAVE_NETINET_IN_H
27 #include <netinet/in.h>
28 #endif
29 #if HAVE_STRING_H
30 #include <string.h>
31 #else
32 #include <strings.h>
33 #endif
34 
35 #if HAVE_UNISTD_H
36 #include <unistd.h>
37 #endif
38 
39 #if HAVE_SYS_SOCKET_H
40 #include <sys/socket.h>
41 #endif
42 #if !defined(mingw32) && defined(HAVE_SYS_TIME_H)
43 #include <sys/time.h>
44 #endif
45 
46 #include <net-snmp/types.h>
47 #include <net-snmp/output_api.h>
48 #include <net-snmp/utilities.h>
49 
50 #include <net-snmp/library/callback.h>
51 #include <net-snmp/library/snmp_api.h>
52 
53 netsnmp_feature_child_of(callbacks_all, libnetsnmp);
54 
55 netsnmp_feature_child_of(callback_count, callbacks_all);
56 netsnmp_feature_child_of(callback_list, callbacks_all);
57 
58 /*
59  * the inline callback methods use major/minor to index into arrays.
60  * all users in this function do range checking before calling these
61  * functions, so it is redundant for them to check again. But if you
62  * want to be paranoid, define this var, and additional range checks
63  * will be performed.
64  * #define NETSNMP_PARANOID_LEVEL_HIGH 1
65  */
66 
67 static int _callback_need_init = 1;
68 static struct snmp_gen_callback
69                *thecallbacks[MAX_CALLBACK_IDS][MAX_CALLBACK_SUBIDS];
70 
71 #define CALLBACK_NAME_LOGGING 1
72 #ifdef CALLBACK_NAME_LOGGING
73 static const char *types[MAX_CALLBACK_IDS] = { "LIB", "APP" };
74 static const char *lib[MAX_CALLBACK_SUBIDS] = {
75     "POST_READ_CONFIG", /* 0 */
76     "STORE_DATA", /* 1 */
77     "SHUTDOWN", /* 2 */
78     "POST_PREMIB_READ_CONFIG", /* 3 */
79     "LOGGING", /* 4 */
80     "SESSION_INIT", /* 5 */
81     NULL, /* 6 */
82     NULL, /* 7 */
83     NULL, /* 8 */
84     NULL, /* 9 */
85     NULL, /* 10 */
86     NULL, /* 11 */
87     NULL, /* 12 */
88     NULL, /* 13 */
89     NULL, /* 14 */
90     NULL /* 15 */
91 };
92 #endif
93 
94 /*
95  * extremely simplistic locking, just to find problems were the
96  * callback list is modified while being traversed. Not intended
97  * to do any real protection, or in any way imply that this code
98  * has been evaluated for use in a multi-threaded environment.
99  * In 5.2, it was a single lock. For 5.3, it has been updated to
100  * a lock per callback, since a particular callback may trigger
101  * registration/unregistration of other callbacks (eg AgentX
102  * subagents do this).
103  */
104 #define LOCK_PER_CALLBACK_SUBID 1
105 #ifdef LOCK_PER_CALLBACK_SUBID
106 static int _locks[MAX_CALLBACK_IDS][MAX_CALLBACK_SUBIDS];
107 #define CALLBACK_LOCK(maj,min) ++_locks[maj][min]
108 #define CALLBACK_UNLOCK(maj,min) --_locks[maj][min]
109 #define CALLBACK_LOCK_COUNT(maj,min) _locks[maj][min]
110 #else
111 static int _lock;
112 #define CALLBACK_LOCK(maj,min) ++_lock
113 #define CALLBACK_UNLOCK(maj,min) --_lock
114 #define CALLBACK_LOCK_COUNT(maj,min) _lock
115 #endif
116 
117 NETSNMP_STATIC_INLINE int
_callback_lock(int major,int minor,const char * warn,int do_assert)118 _callback_lock(int major, int minor, const char* warn, int do_assert)
119 {
120     int lock_holded=0;
121     NETSNMP_SELECT_TIMEVAL lock_time = { 0, 1000 };
122 
123 #ifdef NETSNMP_PARANOID_LEVEL_HIGH
124     if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) {
125         netsnmp_assert("bad callback id");
126         return 1;
127     }
128 #endif
129 
130 #ifdef CALLBACK_NAME_LOGGING
131     DEBUGMSGTL(("9:callback:lock", "locked (%s,%s)\n",
132                 types[major], (SNMP_CALLBACK_LIBRARY == major) ?
133                 SNMP_STRORNULL(lib[minor]) : "null"));
134 #endif
135     while (CALLBACK_LOCK_COUNT(major,minor) >= 1 && ++lock_holded < 100)
136 	select(0, NULL, NULL, NULL, &lock_time);
137 
138     if(lock_holded >= 100) {
139         if (NULL != warn)
140             snmp_log(LOG_WARNING,
141                      "lock in _callback_lock sleeps more than 100 milliseconds in %s\n", warn);
142         if (do_assert)
143             netsnmp_assert(lock_holded < 100);
144 
145         return 1;
146     }
147 
148     CALLBACK_LOCK(major,minor);
149     return 0;
150 }
151 
152 NETSNMP_STATIC_INLINE void
_callback_unlock(int major,int minor)153 _callback_unlock(int major, int minor)
154 {
155 #ifdef NETSNMP_PARANOID_LEVEL_HIGH
156     if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) {
157         netsnmp_assert("bad callback id");
158         return;
159     }
160 #endif
161 
162     CALLBACK_UNLOCK(major,minor);
163 
164 #ifdef CALLBACK_NAME_LOGGING
165     DEBUGMSGTL(("9:callback:lock", "unlocked (%s,%s)\n",
166                 types[major], (SNMP_CALLBACK_LIBRARY == major) ?
167                 SNMP_STRORNULL(lib[minor]) : "null"));
168 #endif
169 }
170 
171 
172 /*
173  * the chicken. or the egg.  You pick.
174  */
175 void
init_callbacks(void)176 init_callbacks(void)
177 {
178     /*
179      * (poses a problem if you put init_callbacks() inside of
180      * init_snmp() and then want the app to register a callback before
181      * init_snmp() is called in the first place.  -- Wes
182      */
183     if (0 == _callback_need_init)
184         return;
185 
186     _callback_need_init = 0;
187 
188     memset(thecallbacks, 0, sizeof(thecallbacks));
189 #ifdef LOCK_PER_CALLBACK_SUBID
190     memset(_locks, 0, sizeof(_locks));
191 #else
192     _lock = 0;
193 #endif
194 
195     DEBUGMSGTL(("callback", "initialized\n"));
196 }
197 
198 /**
199  * This function registers a generic callback function.  The major and
200  * minor values are used to set the new_callback function into a global
201  * static multi-dimensional array of type struct snmp_gen_callback.
202  * The function makes sure to append this callback function at the end
203  * of the link list, snmp_gen_callback->next.
204  *
205  * @param major is the SNMP callback major type used
206  * 		- SNMP_CALLBACK_LIBRARY
207  *              - SNMP_CALLBACK_APPLICATION
208  *
209  * @param minor is the SNMP callback minor type used
210  *		- SNMP_CALLBACK_POST_READ_CONFIG
211  *		- SNMP_CALLBACK_STORE_DATA
212  *		- SNMP_CALLBACK_SHUTDOWN
213  *		- SNMP_CALLBACK_POST_PREMIB_READ_CONFIG
214  *		- SNMP_CALLBACK_LOGGING
215  *		- SNMP_CALLBACK_SESSION_INIT
216  *
217  * @param new_callback is the callback function that is registered.
218  *
219  * @param arg when not NULL is a void pointer used whenever new_callback
220  *	function is exercised. Ownership is transferred to the twodimensional
221  *      thecallbacks[][] array. The function clear_callback() will deallocate
222  *      the memory pointed at by calling free().
223  *
224  * @return
225  *	Returns SNMPERR_GENERR if major is >= MAX_CALLBACK_IDS or minor is >=
226  *	MAX_CALLBACK_SUBIDS or a snmp_gen_callback pointer could not be
227  *	allocated, otherwise SNMPERR_SUCCESS is returned.
228  * 	- \#define MAX_CALLBACK_IDS    2
229  *	- \#define MAX_CALLBACK_SUBIDS 16
230  *
231  * @see snmp_call_callbacks
232  * @see snmp_unregister_callback
233  */
234 int
snmp_register_callback(int major,int minor,SNMPCallback * new_callback,void * arg)235 snmp_register_callback(int major, int minor, SNMPCallback * new_callback,
236                        void *arg)
237 {
238     return netsnmp_register_callback( major, minor, new_callback, arg,
239                                       NETSNMP_CALLBACK_DEFAULT_PRIORITY);
240 }
241 
242 /**
243  * Register a callback function.
244  *
245  * @param major        Major callback event type.
246  * @param minor        Minor callback event type.
247  * @param new_callback Callback function being registered.
248  * @param arg          Argument that will be passed to the callback function.
249  * @param priority     Handler invocation priority. When multiple handlers have
250  *   been registered for the same (major, minor) callback event type, handlers
251  *   with the numerically lowest priority will be invoked first. Handlers with
252  *   identical priority are invoked in the order they have been registered.
253  *
254  * @see snmp_register_callback
255  */
256 int
netsnmp_register_callback(int major,int minor,SNMPCallback * new_callback,void * arg,int priority)257 netsnmp_register_callback(int major, int minor, SNMPCallback * new_callback,
258                           void *arg, int priority)
259 {
260     struct snmp_gen_callback *newscp = NULL, *scp = NULL;
261     struct snmp_gen_callback **prevNext = &(thecallbacks[major][minor]);
262 
263     if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) {
264         return SNMPERR_GENERR;
265     }
266 
267     if (_callback_need_init)
268         init_callbacks();
269 
270     _callback_lock(major,minor, "netsnmp_register_callback", 1);
271 
272     if ((newscp = SNMP_MALLOC_STRUCT(snmp_gen_callback)) == NULL) {
273         _callback_unlock(major,minor);
274         return SNMPERR_GENERR;
275     } else {
276         newscp->priority = priority;
277         newscp->sc_client_arg = arg;
278         newscp->sc_callback = new_callback;
279         newscp->next = NULL;
280 
281         for (scp = thecallbacks[major][minor]; scp != NULL;
282              scp = scp->next) {
283             if (newscp->priority < scp->priority) {
284                 newscp->next = scp;
285                 break;
286             }
287             prevNext = &(scp->next);
288         }
289 
290         *prevNext = newscp;
291 
292         DEBUGMSGTL(("callback", "registered (%d,%d) at %p with priority %d\n",
293                     major, minor, newscp, priority));
294         _callback_unlock(major,minor);
295         return SNMPERR_SUCCESS;
296     }
297 }
298 
299 /**
300  * This function calls the callback function for each registered callback of
301  * type major and minor.
302  *
303  * @param major is the SNMP callback major type used
304  *
305  * @param minor is the SNMP callback minor type used
306  *
307  * @param caller_arg is a void pointer which is sent in as the callback's
308  *	serverarg parameter, if needed.
309  *
310  * @return Returns SNMPERR_GENERR if major is >= MAX_CALLBACK_IDS or
311  * minor is >= MAX_CALLBACK_SUBIDS, otherwise SNMPERR_SUCCESS is returned.
312  *
313  * @see snmp_register_callback
314  * @see snmp_unregister_callback
315  */
316 int
snmp_call_callbacks(int major,int minor,void * caller_arg)317 snmp_call_callbacks(int major, int minor, void *caller_arg)
318 {
319     struct snmp_gen_callback *scp;
320     unsigned int    count = 0;
321 
322     if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) {
323         return SNMPERR_GENERR;
324     }
325 
326     if (_callback_need_init)
327         init_callbacks();
328 
329 #ifdef LOCK_PER_CALLBACK_SUBID
330     _callback_lock(major,minor,"snmp_call_callbacks", 1);
331 #else
332     /*
333      * Notes:
334      * - this gets hit the first time a trap is sent after a new trap
335      *   destination has been added (session init cb during send trap cb)
336      */
337     _callback_lock(major,minor, NULL, 0);
338 #endif
339 
340     DEBUGMSGTL(("callback", "START calling callbacks for maj=%d min=%d\n",
341                 major, minor));
342 
343     /*
344      * for each registered callback of type major and minor
345      */
346     for (scp = thecallbacks[major][minor]; scp != NULL; scp = scp->next) {
347 
348         /*
349          * skip unregistered callbacks
350          */
351         if(NULL == scp->sc_callback)
352             continue;
353 
354         DEBUGMSGTL(("callback", "calling a callback for maj=%d min=%d\n",
355                     major, minor));
356 
357         /*
358          * call them
359          */
360         (*(scp->sc_callback)) (major, minor, caller_arg,
361                                scp->sc_client_arg);
362         count++;
363     }
364 
365     DEBUGMSGTL(("callback",
366                 "END calling callbacks for maj=%d min=%d (%d called)\n",
367                 major, minor, count));
368 
369     _callback_unlock(major,minor);
370     return SNMPERR_SUCCESS;
371 }
372 
373 #ifndef NETSNMP_FEATURE_REMOVE_CALLBACK_COUNT
374 int
snmp_count_callbacks(int major,int minor)375 snmp_count_callbacks(int major, int minor)
376 {
377     int             count = 0;
378     struct snmp_gen_callback *scp;
379 
380     if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) {
381         return SNMPERR_GENERR;
382     }
383 
384     if (_callback_need_init)
385         init_callbacks();
386 
387     for (scp = thecallbacks[major][minor]; scp != NULL; scp = scp->next) {
388         count++;
389     }
390 
391     return count;
392 }
393 #endif /* NETSNMP_FEATURE_REMOVE_CALLBACK_COUNT */
394 
395 int
snmp_callback_available(int major,int minor)396 snmp_callback_available(int major, int minor)
397 {
398     if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS) {
399         return SNMPERR_GENERR;
400     }
401 
402     if (_callback_need_init)
403         init_callbacks();
404 
405     if (thecallbacks[major][minor] != NULL) {
406         return SNMPERR_SUCCESS;
407     }
408 
409     return SNMPERR_GENERR;
410 }
411 
412 /**
413  * This function unregisters a specified callback function given a major
414  * and minor type.
415  *
416  * Note: no bound checking on major and minor.
417  *
418  * @param major is the SNMP callback major type used
419  *
420  * @param minor is the SNMP callback minor type used
421  *
422  * @param target is the callback function that will be unregistered.
423  *
424  * @param arg is a void pointer used for comparison against the registered
425  *	callback's sc_client_arg variable.
426  *
427  * @param matchargs is an integer used to bypass the comparison of arg and the
428  *	callback's sc_client_arg variable only when matchargs is set to 0.
429  *
430  *
431  * @return
432  *        Returns the number of callbacks that were unregistered.
433  *
434  * @see snmp_register_callback
435  * @see snmp_call_callbacks
436  */
437 
438 int
snmp_unregister_callback(int major,int minor,SNMPCallback * target,void * arg,int matchargs)439 snmp_unregister_callback(int major, int minor, SNMPCallback * target,
440                          void *arg, int matchargs)
441 {
442     struct snmp_gen_callback *scp;
443     struct snmp_gen_callback **prevNext;
444     int             count = 0;
445 
446     if (major >= MAX_CALLBACK_IDS || minor >= MAX_CALLBACK_SUBIDS)
447         return SNMPERR_GENERR;
448 
449     scp = thecallbacks[major][minor];
450     prevNext = &(thecallbacks[major][minor]);
451 
452     if (_callback_need_init)
453         init_callbacks();
454 
455 #ifdef LOCK_PER_CALLBACK_SUBID
456     _callback_lock(major,minor,"snmp_unregister_callback", 1);
457 #else
458     /*
459      * Notes;
460      * - this gets hit at shutdown, during cleanup. No easy fix.
461      */
462     _callback_lock(major,minor,"snmp_unregister_callback", 0);
463 #endif
464 
465     while (scp != NULL) {
466         if ((scp->sc_callback == target) &&
467             (!matchargs || (scp->sc_client_arg == arg))) {
468             DEBUGMSGTL(("callback", "unregistering (%d,%d) at %p\n", major,
469                         minor, scp));
470             if(1 == CALLBACK_LOCK_COUNT(major,minor)) {
471                 *prevNext = scp->next;
472                 SNMP_FREE(scp);
473                 scp = *prevNext;
474             }
475             else {
476                 scp->sc_callback = NULL;
477                 /** set cleanup flag? */
478             }
479             count++;
480         } else {
481             prevNext = &(scp->next);
482             scp = scp->next;
483         }
484     }
485 
486     _callback_unlock(major,minor);
487     return count;
488 }
489 
490 /**
491  * find and clear client args that match ptr
492  *
493  * @param ptr  pointer to search for
494  * @param i    callback id to start at
495  * @param j    callback subid to start at
496  */
497 int
netsnmp_callback_clear_client_arg(void * ptr,int i,int j)498 netsnmp_callback_clear_client_arg(void *ptr, int i, int j)
499 {
500     struct snmp_gen_callback *scp = NULL;
501     int rc = 0;
502 
503     /*
504      * don't init i and j before loop, since the caller specified
505      * the starting point explicitly. But *after* the i loop has
506      * finished executing once, init j to 0 for the next pass
507      * through the subids.
508      */
509     for (; i < MAX_CALLBACK_IDS; i++,j=0) {
510         for (; j < MAX_CALLBACK_SUBIDS; j++) {
511             scp = thecallbacks[i][j];
512             while (scp != NULL) {
513                 if ((NULL != scp->sc_callback) &&
514                     (scp->sc_client_arg != NULL) &&
515                     (scp->sc_client_arg == ptr)) {
516                     DEBUGMSGTL(("9:callback", "  clearing %p at [%d,%d]\n", ptr, i, j));
517                     scp->sc_client_arg = NULL;
518                     ++rc;
519                 }
520                 scp = scp->next;
521             }
522         }
523     }
524 
525     if (0 != rc) {
526         DEBUGMSGTL(("callback", "removed %d client args\n", rc));
527     }
528 
529     return rc;
530 }
531 
532 void
clear_callback(void)533 clear_callback(void)
534 {
535     unsigned int i = 0, j = 0;
536     struct snmp_gen_callback *scp = NULL;
537 
538     if (_callback_need_init)
539         init_callbacks();
540 
541     DEBUGMSGTL(("callback", "clear callback\n"));
542     for (i = 0; i < MAX_CALLBACK_IDS; i++) {
543         for (j = 0; j < MAX_CALLBACK_SUBIDS; j++) {
544             _callback_lock(i,j, "clear_callback", 1);
545             scp = thecallbacks[i][j];
546             while (scp != NULL) {
547                 thecallbacks[i][j] = scp->next;
548                 /*
549                  * if there is a client arg, check for duplicates
550                  * and then free it.
551                  */
552                 if ((NULL != scp->sc_callback) &&
553                     (scp->sc_client_arg != NULL)) {
554                     void *tmp_arg;
555                     /*
556                      * save the client arg, then set it to null so that it
557                      * won't look like a duplicate, then check for duplicates
558                      * starting at the current i,j (earlier dups should have
559                      * already been found) and free the pointer.
560                      */
561                     tmp_arg = scp->sc_client_arg;
562                     scp->sc_client_arg = NULL;
563                     DEBUGMSGTL(("9:callback", "  freeing %p at [%d,%d]\n", tmp_arg, i, j));
564                     (void)netsnmp_callback_clear_client_arg(tmp_arg, i, j);
565                     free(tmp_arg);
566                 }
567                 SNMP_FREE(scp);
568                 scp = thecallbacks[i][j];
569             }
570             _callback_unlock(i,j);
571         }
572     }
573 }
574 
575 #ifndef NETSNMP_FEATURE_REMOVE_CALLBACK_LIST
576 struct snmp_gen_callback *
snmp_callback_list(int major,int minor)577 snmp_callback_list(int major, int minor)
578 {
579     if (_callback_need_init)
580         init_callbacks();
581 
582     return (thecallbacks[major][minor]);
583 }
584 #endif /* NETSNMP_FEATURE_REMOVE_CALLBACK_LIST */
585 /**  @} */
586