1 /* Portions of this file are subject to the following copyright(s).  See
2  * the Net-SNMP's COPYING file for more details and other copyrights
3  * that may apply:
4  */
5 /*
6  * Portions of this file are copyrighted by:
7  * Copyright (C) 2007 Apple, Inc. All rights reserved.
8  * Use is subject to license terms specified in the COPYING file
9  * distributed with the Net-SNMP package.
10  *
11  * Portions of this file are copyrighted by:
12  * Copyright (c) 2016 VMware, Inc. All rights reserved.
13  * Use is subject to license terms specified in the COPYING file
14  * distributed with the Net-SNMP package.
15  */
16 #include <net-snmp/net-snmp-config.h>
17 #include <net-snmp/net-snmp-features.h>
18 
19 #if HAVE_STRING_H
20 #include <string.h>
21 #else
22 #include <strings.h>
23 #endif
24 
25 #include <net-snmp/net-snmp-includes.h>
26 #include <net-snmp/agent/net-snmp-agent-includes.h>
27 
28 #include <net-snmp/agent/cache_handler.h>
29 
30 netsnmp_feature_child_of(cache_handler, mib_helpers);
31 
32 netsnmp_feature_child_of(cache_find_by_oid, cache_handler);
33 netsnmp_feature_child_of(cache_get_head, cache_handler);
34 
35 static netsnmp_cache  *cache_head = NULL;
36 static int             cache_outstanding_valid = 0;
37 static int             _cache_load( netsnmp_cache *cache );
38 
39 #define CACHE_RELEASE_FREQUENCY 60      /* Check for expired caches every 60s */
40 
41 void            release_cached_resources(unsigned int regNo,
42                                          void *clientargs);
43 
44 /** @defgroup cache_handler cache_handler
45  *  Maintains a cache of data for use by lower level handlers.
46  *  @ingroup utilities
47  *  This helper checks to see whether the data has been loaded "recently"
48  *  (according to the timeout for that particular cache) and calls the
49  *  registered "load_cache" routine if necessary.
50  *  The lower handlers can then work with this local cached data.
51  *
52  *  A timeout value of -1 will cause netsnmp_cache_check_expired() to
53  *  always return true, and thus the cache will be reloaded for every
54  *  request.
55  *
56  *  To minimze resource use by the agent, a periodic callback checks for
57  *  expired caches, and will call the free_cache function for any expired
58  *  cache.
59  *
60  *  The load_cache routine should return a negative number if the cache
61  *  was not successfully loaded. 0 or any positive number indicates successs.
62  *
63  *
64  *  Several flags can be set to affect the operations on the cache.
65  *
66  *  If NETSNMP_CACHE_DONT_INVALIDATE_ON_SET is set, the free_cache method
67  *  will not be called after a set request has processed. It is assumed that
68  *  the lower mib handler using the cache has maintained cache consistency.
69  *
70  *  If NETSNMP_CACHE_DONT_FREE_BEFORE_LOAD is set, the free_cache method
71  *  will not be called before the load_cache method is called. It is assumed
72  *  that the load_cache routine will properly deal with being called with a
73  *  valid cache.
74  *
75  *  If NETSNMP_CACHE_DONT_FREE_EXPIRED is set, the free_cache method will
76  *  not be called with the cache expires. The expired flag will be set, but
77  *  the valid flag will not be cleared. It is assumed that the load_cache
78  *  routine will properly deal with being called with a valid cache.
79  *
80  *  If NETSNMP_CACHE_PRELOAD is set when a the cache handler is created,
81  *  the cache load routine will be called immediately.
82  *
83  *  If NETSNMP_CACHE_DONT_AUTO_RELEASE is set, the periodic callback that
84  *  checks for expired caches will skip the cache. The cache will only be
85  *  checked for expiration when a request triggers the cache handler. This
86  *  is useful if the cache has it's own periodic callback to keep the cache
87  *  fresh.
88  *
89  *  If NETSNMP_CACHE_AUTO_RELOAD is set, a timer will be set up to reload
90  *  the cache when it expires. This is useful for keeping the cache fresh,
91  *  even in the absence of incoming snmp requests.
92  *
93  *  If NETSNMP_CACHE_RESET_TIMER_ON_USE is set, the expiry timer will be
94  *  reset on each cache access. In practice the 'timeout' becomes a timer
95  *  which triggers when the cache is no longer needed. This is useful
96  *  if the cache is automatically kept synchronized: e.g. by receiving
97  *  change notifications from Netlink, inotify or similar. This should
98  *  not be used if cache is not synchronized automatically as it would
99  *  result in stale cache information when if polling happens too fast.
100  *
101  *
102  *  Here are some suggestions for some common situations.
103  *
104  *  Cached File:
105  *      If your table is based on a file that may periodically change,
106  *      you can test the modification date to see if the file has
107  *      changed since the last cache load. To get the cache helper to call
108  *      the load function for every request, set the timeout to -1, which
109  *      will cause the cache to always report that it is expired. This means
110  *      that you will want to prevent the agent from flushing the cache when
111  *      it has expired, and you will have to flush it manually if you
112  *      detect that the file has changed. To accomplish this, set the
113  *      following flags:
114  *
115  *          NETSNMP_CACHE_DONT_FREE_EXPIRED
116  *          NETSNMP_CACHE_DONT_AUTO_RELEASE
117  *
118  *
119  *  Constant (periodic) reload:
120  *      If you want the cache kept up to date regularly, even if no requests
121  *      for the table are received, you can have your cache load routine
122  *      called periodically. This is very useful if you need to monitor the
123  *      data for changes (eg a <i>LastChanged</i> object). You will need to
124  *      prevent the agent from flushing the cache when it expires. Set the
125  *      cache timeout to the frequency, in seconds, that you wish to
126  *      reload your cache, and set the following flags:
127  *
128  *          NETSNMP_CACHE_DONT_FREE_EXPIRED
129  *          NETSNMP_CACHE_DONT_AUTO_RELEASE
130  *          NETSNMP_CACHE_AUTO_RELOAD
131  *
132  *  Dynamically updated, unloaded after timeout:
133  *      If the cache is kept up to date dynamically by listening for
134  *      change notifications somehow, but it should not be in memory
135  *      if it's not needed. Set the following flag:
136  *
137  *          NETSNMP_CACHE_RESET_TIMER_ON_USE
138  *
139  *  @{
140  */
141 
142 static void
143 _cache_free( netsnmp_cache *cache );
144 
145 #ifndef NETSNMP_FEATURE_REMOVE_CACHE_GET_HEAD
146 /** get cache head
147  * @internal
148  * unadvertised function to get cache head. You really should not
149  * do this, since the internal storage mechanism might change.
150  */
151 netsnmp_cache *
netsnmp_cache_get_head(void)152 netsnmp_cache_get_head(void)
153 {
154     return cache_head;
155 }
156 #endif /* NETSNMP_FEATURE_REMOVE_CACHE_GET_HEAD */
157 
158 #ifndef NETSNMP_FEATURE_REMOVE_CACHE_FIND_BY_OID
159 /** find existing cache
160  */
161 netsnmp_cache *
netsnmp_cache_find_by_oid(const oid * rootoid,int rootoid_len)162 netsnmp_cache_find_by_oid(const oid * rootoid, int rootoid_len)
163 {
164     netsnmp_cache  *cache;
165 
166     for (cache = cache_head; cache; cache = cache->next) {
167         if (0 == netsnmp_oid_equals(cache->rootoid, cache->rootoid_len,
168                                     rootoid, rootoid_len))
169             return cache;
170     }
171 
172     return NULL;
173 }
174 #endif /* NETSNMP_FEATURE_REMOVE_CACHE_FIND_BY_OID */
175 
176 /** returns a cache
177  */
178 netsnmp_cache *
netsnmp_cache_create(int timeout,NetsnmpCacheLoad * load_hook,NetsnmpCacheFree * free_hook,const oid * rootoid,int rootoid_len)179 netsnmp_cache_create(int timeout, NetsnmpCacheLoad * load_hook,
180                      NetsnmpCacheFree * free_hook,
181                      const oid * rootoid, int rootoid_len)
182 {
183     netsnmp_cache  *cache = NULL;
184 
185     cache = SNMP_MALLOC_TYPEDEF(netsnmp_cache);
186     if (NULL == cache) {
187         snmp_log(LOG_ERR,"malloc error in netsnmp_cache_create\n");
188         return NULL;
189     }
190     cache->timeout = timeout;
191     cache->load_cache = load_hook;
192     cache->free_cache = free_hook;
193     cache->enabled = 1;
194 
195     if(0 == cache->timeout)
196         cache->timeout = netsnmp_ds_get_int(NETSNMP_DS_APPLICATION_ID,
197                                             NETSNMP_DS_AGENT_CACHE_TIMEOUT);
198 
199 
200     /*
201      * Add the registered OID information, and tack
202      * this onto the list for cache SNMP management
203      *
204      * Note that this list is not ordered.
205      *    table_iterator rules again!
206      */
207     if (rootoid) {
208         cache->rootoid = snmp_duplicate_objid(rootoid, rootoid_len);
209         cache->rootoid_len = rootoid_len;
210         cache->next = cache_head;
211         if (cache_head)
212             cache_head->prev = cache;
213         cache_head = cache;
214     }
215 
216     return cache;
217 }
218 
219 static netsnmp_cache *
netsnmp_cache_ref(netsnmp_cache * cache)220 netsnmp_cache_ref(netsnmp_cache *cache)
221 {
222     cache->refcnt++;
223     return cache;
224 }
225 
226 static void
netsnmp_cache_deref(netsnmp_cache * cache)227 netsnmp_cache_deref(netsnmp_cache *cache)
228 {
229     if (--cache->refcnt == 0) {
230         netsnmp_cache_remove(cache);
231         netsnmp_cache_free(cache);
232     }
233 }
234 
235 /** frees a cache
236  */
237 int
netsnmp_cache_free(netsnmp_cache * cache)238 netsnmp_cache_free(netsnmp_cache *cache)
239 {
240     netsnmp_cache  *pos;
241 
242     if (NULL == cache)
243         return SNMPERR_SUCCESS;
244 
245     for (pos = cache_head; pos; pos = pos->next) {
246         if (pos == cache) {
247             size_t          out_len = 0;
248             size_t          buf_len = 0;
249             char           *buf = NULL;
250 
251             sprint_realloc_objid((u_char **) &buf, &buf_len, &out_len,
252                                  1, pos->rootoid, pos->rootoid_len);
253             snmp_log(LOG_WARNING,
254                      "not freeing cache with root OID %s (still in list)\n",
255                      buf);
256             free(buf);
257             return SNMP_ERR_GENERR;
258         }
259     }
260 
261     if(0 != cache->timer_id)
262         netsnmp_cache_timer_stop(cache);
263 
264     if (cache->valid)
265         _cache_free(cache);
266 
267     if (cache->timestampM)
268 	free(cache->timestampM);
269 
270     if (cache->rootoid)
271         free(cache->rootoid);
272 
273     free(cache);
274 
275     return SNMPERR_SUCCESS;
276 }
277 
278 /** removes a cache
279  */
280 int
netsnmp_cache_remove(netsnmp_cache * cache)281 netsnmp_cache_remove(netsnmp_cache *cache)
282 {
283     netsnmp_cache  *cur,*prev;
284 
285     if (!cache || !cache_head)
286         return -1;
287 
288     if (cache == cache_head) {
289         cache_head = cache_head->next;
290         if (cache_head)
291             cache_head->prev = NULL;
292         return 0;
293     }
294 
295     prev = cache_head;
296     cur = cache_head->next;
297     for (; cur; prev = cur, cur = cur->next) {
298         if (cache == cur) {
299             prev->next = cur->next;
300             if (cur->next)
301                 cur->next->prev = cur->prev;
302             return 0;
303         }
304     }
305     return -1;
306 }
307 
308 /** callback function to call cache load function */
309 static void
_timer_reload(unsigned int regNo,void * clientargs)310 _timer_reload(unsigned int regNo, void *clientargs)
311 {
312     netsnmp_cache *cache = (netsnmp_cache *)clientargs;
313 
314     DEBUGMSGT(("cache_timer:start", "loading cache %p\n", cache));
315 
316     cache->expired = 1;
317 
318     _cache_load(cache);
319 }
320 
321 /** starts the recurring cache_load callback */
322 unsigned int
netsnmp_cache_timer_start(netsnmp_cache * cache)323 netsnmp_cache_timer_start(netsnmp_cache *cache)
324 {
325     if(NULL == cache)
326         return 0;
327 
328     DEBUGMSGTL(( "cache_timer:start", "OID: "));
329     DEBUGMSGOID(("cache_timer:start", cache->rootoid, cache->rootoid_len));
330     DEBUGMSG((   "cache_timer:start", "\n"));
331 
332     if(0 != cache->timer_id) {
333         snmp_log(LOG_WARNING, "cache has existing timer id.\n");
334         return cache->timer_id;
335     }
336 
337     if(! (cache->flags & NETSNMP_CACHE_AUTO_RELOAD)) {
338         snmp_log(LOG_ERR,
339                  "cache_timer_start called but auto_reload not set.\n");
340         return 0;
341     }
342 
343     cache->timer_id = snmp_alarm_register(cache->timeout, SA_REPEAT,
344                                           _timer_reload, cache);
345     if(0 == cache->timer_id) {
346         snmp_log(LOG_ERR,"could not register alarm\n");
347         return 0;
348     }
349 
350     cache->flags &= ~NETSNMP_CACHE_AUTO_RELOAD;
351     DEBUGMSGT(("cache_timer:start",
352                "starting timer %lu for cache %p\n", cache->timer_id, cache));
353     return cache->timer_id;
354 }
355 
356 /** stops the recurring cache_load callback */
357 void
netsnmp_cache_timer_stop(netsnmp_cache * cache)358 netsnmp_cache_timer_stop(netsnmp_cache *cache)
359 {
360     if(NULL == cache)
361         return;
362 
363     if(0 == cache->timer_id) {
364         snmp_log(LOG_WARNING, "cache has no timer id.\n");
365         return;
366     }
367 
368     DEBUGMSGT(("cache_timer:stop",
369                "stopping timer %lu for cache %p\n", cache->timer_id, cache));
370 
371     snmp_alarm_unregister(cache->timer_id);
372     cache->flags |= NETSNMP_CACHE_AUTO_RELOAD;
373 }
374 
375 
376 /** returns a cache handler that can be injected into a given handler chain.
377  */
378 netsnmp_mib_handler *
netsnmp_cache_handler_get(netsnmp_cache * cache)379 netsnmp_cache_handler_get(netsnmp_cache* cache)
380 {
381     netsnmp_mib_handler *ret = NULL;
382 
383     ret = netsnmp_create_handler("cache_handler",
384                                  netsnmp_cache_helper_handler);
385     if (ret) {
386         ret->flags |= MIB_HANDLER_AUTO_NEXT;
387         ret->myvoid = (void *) cache;
388 
389         if(NULL != cache) {
390             if ((cache->flags & NETSNMP_CACHE_PRELOAD) && ! cache->valid) {
391                 /*
392                  * load cache, ignore rc
393                  * (failed load doesn't affect registration)
394                  */
395                 (void)_cache_load(cache);
396             }
397             if (cache->flags & NETSNMP_CACHE_AUTO_RELOAD)
398                 netsnmp_cache_timer_start(cache);
399 
400         }
401     }
402     return ret;
403 }
404 
405 /** Makes sure that memory allocated for the cache is freed when the handler
406  *  is unregistered.
407  */
netsnmp_cache_handler_owns_cache(netsnmp_mib_handler * handler)408 void netsnmp_cache_handler_owns_cache(netsnmp_mib_handler *handler)
409 {
410     netsnmp_assert(handler->myvoid);
411     ((netsnmp_cache *)handler->myvoid)->refcnt++;
412     handler->data_clone = (void *(*)(void *))netsnmp_cache_ref;
413     handler->data_free = (void(*)(void*))netsnmp_cache_deref;
414 }
415 
416 /** returns a cache handler that can be injected into a given handler chain.
417  */
418 netsnmp_mib_handler *
netsnmp_get_cache_handler(int timeout,NetsnmpCacheLoad * load_hook,NetsnmpCacheFree * free_hook,const oid * rootoid,int rootoid_len)419 netsnmp_get_cache_handler(int timeout, NetsnmpCacheLoad * load_hook,
420                           NetsnmpCacheFree * free_hook,
421                           const oid * rootoid, int rootoid_len)
422 {
423     netsnmp_mib_handler *ret = NULL;
424     netsnmp_cache  *cache = NULL;
425 
426     ret = netsnmp_cache_handler_get(NULL);
427     if (ret) {
428         cache = netsnmp_cache_create(timeout, load_hook, free_hook,
429                                      rootoid, rootoid_len);
430         ret->myvoid = (void *) cache;
431         netsnmp_cache_handler_owns_cache(ret);
432     }
433     return ret;
434 }
435 
436 #if !defined(NETSNMP_FEATURE_REMOVE_NETSNMP_CACHE_HANDLER_REGISTER) || !defined(NETSNMP_FEATURE_REMOVE_NETSNMP_REGISTER_CACHE_HANDLER)
437 static int
_cache_handler_register(netsnmp_handler_registration * reginfo,netsnmp_mib_handler * handler)438 _cache_handler_register(netsnmp_handler_registration * reginfo,
439                         netsnmp_mib_handler *handler)
440 {
441     /** success path */
442     if (reginfo && handler &&
443         (netsnmp_inject_handler(reginfo, handler) == SNMPERR_SUCCESS))
444         return netsnmp_register_handler(reginfo);
445 
446     /** error path */
447     snmp_log(LOG_ERR, "could not register cache handler\n");
448 
449     if (handler)
450         netsnmp_handler_free(handler);
451 
452     netsnmp_handler_registration_free(reginfo);
453 
454     return MIB_REGISTRATION_FAILED;
455 }
456 #endif
457 
458 /** functionally the same as calling netsnmp_register_handler() but also
459  * injects a cache handler at the same time for you. */
460 netsnmp_feature_child_of(netsnmp_cache_handler_register,netsnmp_unused);
461 #ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_CACHE_HANDLER_REGISTER
462 int
netsnmp_cache_handler_register(netsnmp_handler_registration * reginfo,netsnmp_cache * cache)463 netsnmp_cache_handler_register(netsnmp_handler_registration * reginfo,
464                                netsnmp_cache* cache)
465 {
466     if ((NULL == reginfo) || (NULL == cache)) {
467         snmp_log(LOG_ERR, "bad param in netsnmp_cache_handler_register\n");
468         netsnmp_handler_registration_free(reginfo);
469         return MIB_REGISTRATION_FAILED;
470     }
471 
472     return _cache_handler_register(reginfo, netsnmp_cache_handler_get(cache));
473 }
474 #endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_CACHE_HANDLER_REGISTER */
475 
476 /** functionally the same as calling netsnmp_register_handler() but also
477  * injects a cache handler at the same time for you. */
478 netsnmp_feature_child_of(netsnmp_register_cache_handler,netsnmp_unused);
479 #ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_REGISTER_CACHE_HANDLER
480 int
netsnmp_register_cache_handler(netsnmp_handler_registration * reginfo,int timeout,NetsnmpCacheLoad * load_hook,NetsnmpCacheFree * free_hook)481 netsnmp_register_cache_handler(netsnmp_handler_registration * reginfo,
482                                int timeout, NetsnmpCacheLoad * load_hook,
483                                NetsnmpCacheFree * free_hook)
484 {
485     netsnmp_mib_handler *handler;
486 
487     if (NULL == reginfo) {
488         snmp_log(LOG_ERR, "bad param in netsnmp_cache_handler_register\n");
489         netsnmp_handler_registration_free(reginfo);
490         return MIB_REGISTRATION_FAILED;
491     }
492 
493     handler =  netsnmp_get_cache_handler(timeout, load_hook, free_hook,
494                                          reginfo->rootoid,
495                                          reginfo->rootoid_len);
496 
497     return _cache_handler_register(reginfo, handler);
498 }
499 #endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_REGISTER_CACHE_HANDLER */
500 
501 static char *
_build_cache_name(const char * name)502 _build_cache_name(const char *name)
503 {
504     char *dup = (char*)malloc(strlen(name) + strlen(CACHE_NAME) + 2);
505     if (NULL == dup)
506         return NULL;
507     sprintf(dup, "%s:%s", CACHE_NAME, name);
508     return dup;
509 }
510 
511 /** Insert the cache information for a given request (PDU) */
512 void
netsnmp_cache_reqinfo_insert(netsnmp_cache * cache,netsnmp_agent_request_info * reqinfo,const char * name)513 netsnmp_cache_reqinfo_insert(netsnmp_cache* cache,
514                              netsnmp_agent_request_info * reqinfo,
515                              const char *name)
516 {
517     char *cache_name = _build_cache_name(name);
518     if (NULL == netsnmp_agent_get_list_data(reqinfo, cache_name)) {
519         DEBUGMSGTL(("verbose:helper:cache_handler", " adding '%s' to %p\n",
520                     cache_name, reqinfo));
521         netsnmp_agent_add_list_data(reqinfo,
522                                     netsnmp_create_data_list(cache_name,
523                                                              cache, NULL));
524     }
525     SNMP_FREE(cache_name);
526 }
527 
528 /** Extract the cache information for a given request (PDU) */
529 netsnmp_cache  *
netsnmp_cache_reqinfo_extract(netsnmp_agent_request_info * reqinfo,const char * name)530 netsnmp_cache_reqinfo_extract(netsnmp_agent_request_info * reqinfo,
531                               const char *name)
532 {
533     netsnmp_cache  *result;
534     char *cache_name = _build_cache_name(name);
535     result = (netsnmp_cache*)netsnmp_agent_get_list_data(reqinfo, cache_name);
536     SNMP_FREE(cache_name);
537     return result;
538 }
539 
540 /** Extract the cache information for a given request (PDU) */
541 netsnmp_feature_child_of(netsnmp_extract_cache_info,netsnmp_unused);
542 #ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_EXTRACT_CACHE_INFO
543 netsnmp_cache  *
netsnmp_extract_cache_info(netsnmp_agent_request_info * reqinfo)544 netsnmp_extract_cache_info(netsnmp_agent_request_info * reqinfo)
545 {
546     return netsnmp_cache_reqinfo_extract(reqinfo, CACHE_NAME);
547 }
548 #endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_EXTRACT_CACHE_INFO */
549 
550 
551 /** Check if the cache timeout has passed. Sets and return the expired flag. */
552 int
netsnmp_cache_check_expired(netsnmp_cache * cache)553 netsnmp_cache_check_expired(netsnmp_cache *cache)
554 {
555     if(NULL == cache)
556         return 0;
557     if (cache->expired)
558         return 1;
559     if(!cache->valid || (NULL == cache->timestampM) || (-1 == cache->timeout))
560         cache->expired = 1;
561     else
562         cache->expired = netsnmp_ready_monotonic(cache->timestampM,
563                                                  1000 * cache->timeout);
564 
565     return cache->expired;
566 }
567 
568 /** Reload the cache if required */
569 int
netsnmp_cache_check_and_reload(netsnmp_cache * cache)570 netsnmp_cache_check_and_reload(netsnmp_cache * cache)
571 {
572     if (!cache) {
573         DEBUGMSGT(("helper:cache_handler", " no cache\n"));
574         return 0;	/* ?? or -1 */
575     }
576     if (!cache->valid || netsnmp_cache_check_expired(cache))
577         return _cache_load( cache );
578     else {
579         DEBUGMSGT(("helper:cache_handler", " cached (%d)\n",
580                    cache->timeout));
581         return 0;
582     }
583 }
584 
585 /** Is the cache valid for a given request? */
586 int
netsnmp_cache_is_valid(netsnmp_agent_request_info * reqinfo,const char * name)587 netsnmp_cache_is_valid(netsnmp_agent_request_info * reqinfo,
588                        const char* name)
589 {
590     netsnmp_cache  *cache = netsnmp_cache_reqinfo_extract(reqinfo, name);
591     return (cache && cache->valid);
592 }
593 
594 /** Is the cache valid for a given request?
595  * for backwards compatability. netsnmp_cache_is_valid() is preferred.
596  */
597 netsnmp_feature_child_of(netsnmp_is_cache_valid,netsnmp_unused);
598 #ifndef NETSNMP_FEATURE_REMOVE_NETSNMP_IS_CACHE_VALID
599 int
netsnmp_is_cache_valid(netsnmp_agent_request_info * reqinfo)600 netsnmp_is_cache_valid(netsnmp_agent_request_info * reqinfo)
601 {
602     return netsnmp_cache_is_valid(reqinfo, CACHE_NAME);
603 }
604 #endif /* NETSNMP_FEATURE_REMOVE_NETSNMP_IS_CACHE_VALID */
605 
606 /** Implements the cache handler */
607 int
netsnmp_cache_helper_handler(netsnmp_mib_handler * handler,netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo,netsnmp_request_info * requests)608 netsnmp_cache_helper_handler(netsnmp_mib_handler * handler,
609                              netsnmp_handler_registration * reginfo,
610                              netsnmp_agent_request_info * reqinfo,
611                              netsnmp_request_info * requests)
612 {
613     char addrstr[32];
614 
615     netsnmp_cache  *cache = NULL;
616     netsnmp_handler_args cache_hint;
617 
618     DEBUGMSGTL(("helper:cache_handler", "Got request (%d) for %s: ",
619                 reqinfo->mode, reginfo->handlerName));
620     DEBUGMSGOID(("helper:cache_handler", reginfo->rootoid,
621                  reginfo->rootoid_len));
622     DEBUGMSG(("helper:cache_handler", "\n"));
623 
624     netsnmp_assert(handler->flags & MIB_HANDLER_AUTO_NEXT);
625 
626     cache = (netsnmp_cache *) handler->myvoid;
627     if (netsnmp_ds_get_boolean(NETSNMP_DS_APPLICATION_ID,
628                                NETSNMP_DS_AGENT_NO_CACHING) ||
629         !cache || !cache->enabled || !cache->load_cache) {
630         DEBUGMSGT(("helper:cache_handler", " caching disabled or "
631                    "cache not found, disabled or had no load method\n"));
632         return SNMP_ERR_NOERROR;
633     }
634     snprintf(addrstr, sizeof(addrstr), "%p", cache);
635     DEBUGMSGTL(("helper:cache_handler", "using cache %s: ", addrstr));
636     DEBUGMSGOID(("helper:cache_handler", cache->rootoid, cache->rootoid_len));
637     DEBUGMSG(("helper:cache_handler", "\n"));
638 
639     /*
640      * Make the handler-chain parameters available to
641      * the cache_load hook routine.
642      */
643     cache_hint.handler = handler;
644     cache_hint.reginfo = reginfo;
645     cache_hint.reqinfo = reqinfo;
646     cache_hint.requests = requests;
647     cache->cache_hint = &cache_hint;
648 
649     switch (reqinfo->mode) {
650 
651     case MODE_GET:
652     case MODE_GETNEXT:
653     case MODE_GETBULK:
654 #ifndef NETSNMP_NO_WRITE_SUPPORT
655     case MODE_SET_RESERVE1:
656 #endif /* !NETSNMP_NO_WRITE_SUPPORT */
657 
658         /*
659          * only touch cache once per pdu request, to prevent a cache
660          * reload while a module is using cached data.
661          *
662          * XXX: this won't catch a request reloading the cache while
663          * a previous (delegated) request is still using the cache.
664          * maybe use a reference counter?
665          */
666         if (netsnmp_cache_is_valid(reqinfo, addrstr))
667             break;
668 
669         /*
670          * call the load hook, and update the cache timestamp.
671          * If it's not already there, add to reqinfo
672          */
673         netsnmp_cache_check_and_reload(cache);
674         netsnmp_cache_reqinfo_insert(cache, reqinfo, addrstr);
675         /** next handler called automatically - 'AUTO_NEXT' */
676         break;
677 
678 #ifndef NETSNMP_NO_WRITE_SUPPORT
679     case MODE_SET_RESERVE2:
680     case MODE_SET_FREE:
681     case MODE_SET_ACTION:
682     case MODE_SET_UNDO:
683         netsnmp_assert(netsnmp_cache_is_valid(reqinfo, addrstr));
684         /** next handler called automatically - 'AUTO_NEXT' */
685         break;
686 
687         /*
688          * A (successful) SET request wouldn't typically trigger a reload of
689          *  the cache, but might well invalidate the current contents.
690          * Only do this on the last pass through.
691          */
692     case MODE_SET_COMMIT:
693         if (cache->valid &&
694             ! (cache->flags & NETSNMP_CACHE_DONT_INVALIDATE_ON_SET) ) {
695             cache->free_cache(cache, cache->magic);
696             cache->valid = 0;
697         }
698         /** next handler called automatically - 'AUTO_NEXT' */
699         break;
700 #endif /* NETSNMP_NO_WRITE_SUPPORT */
701 
702     default:
703         snmp_log(LOG_WARNING, "cache_handler: Unrecognised mode (%d)\n",
704                  reqinfo->mode);
705         netsnmp_request_set_error_all(requests, SNMP_ERR_GENERR);
706         return SNMP_ERR_GENERR;
707     }
708     if (cache->flags & NETSNMP_CACHE_RESET_TIMER_ON_USE)
709         netsnmp_set_monotonic_marker(&cache->timestampM);
710     return SNMP_ERR_NOERROR;
711 }
712 
713 static void
_cache_free(netsnmp_cache * cache)714 _cache_free( netsnmp_cache *cache )
715 {
716     if (NULL != cache->free_cache) {
717         cache->free_cache(cache, cache->magic);
718         cache->valid = 0;
719     }
720 }
721 
722 static int
_cache_load(netsnmp_cache * cache)723 _cache_load( netsnmp_cache *cache )
724 {
725     int ret = -1;
726 
727     /*
728      * If we've got a valid cache, then release it before reloading
729      */
730     if (cache->valid &&
731         (! (cache->flags & NETSNMP_CACHE_DONT_FREE_BEFORE_LOAD)))
732         _cache_free(cache);
733 
734     if ( cache->load_cache)
735         ret = cache->load_cache(cache, cache->magic);
736     if (ret < 0) {
737         DEBUGMSGT(("helper:cache_handler", " load failed (%d)\n", ret));
738         cache->valid = 0;
739         return ret;
740     }
741     cache->valid = 1;
742     cache->expired = 0;
743 
744     /*
745      * If we didn't previously have any valid caches outstanding,
746      *   then schedule a pass of the auto-release routine.
747      */
748     if ((!cache_outstanding_valid) &&
749         (! (cache->flags & NETSNMP_CACHE_DONT_FREE_EXPIRED))) {
750         snmp_alarm_register(CACHE_RELEASE_FREQUENCY,
751                             0, release_cached_resources, NULL);
752         cache_outstanding_valid = 1;
753     }
754     netsnmp_set_monotonic_marker(&cache->timestampM);
755     DEBUGMSGT(("helper:cache_handler", " loaded (%d)\n", cache->timeout));
756 
757     return ret;
758 }
759 
760 
761 
762 /** run regularly to automatically release cached resources.
763  * xxx - method to prevent cache from expiring while a request
764  *     is being processed (e.g. delegated request). proposal:
765  *     set a flag, which would be cleared when request finished
766  *     (which could be acomplished by a dummy data list item in
767  *     agent req info & custom free function).
768  */
769 void
release_cached_resources(unsigned int regNo,void * clientargs)770 release_cached_resources(unsigned int regNo, void *clientargs)
771 {
772     netsnmp_cache  *cache = NULL;
773 
774     cache_outstanding_valid = 0;
775     DEBUGMSGTL(("helper:cache_handler", "running auto-release\n"));
776     for (cache = cache_head; cache; cache = cache->next) {
777         DEBUGMSGTL(("helper:cache_handler"," checking %p (flags 0x%x)\n",
778                      cache, cache->flags));
779         if (cache->valid &&
780             ! (cache->flags & NETSNMP_CACHE_DONT_AUTO_RELEASE)) {
781             DEBUGMSGTL(("helper:cache_handler","  releasing %p\n", cache));
782             /*
783              * Check to see if this cache has timed out.
784              * If so, release the cached resources.
785              * Otherwise, note that we still have at
786              *   least one active cache.
787              */
788             if (netsnmp_cache_check_expired(cache)) {
789                 if(! (cache->flags & NETSNMP_CACHE_DONT_FREE_EXPIRED))
790                     _cache_free(cache);
791             } else {
792                 cache_outstanding_valid = 1;
793             }
794         }
795     }
796     /*
797      * If there are any caches still valid & active,
798      *   then schedule another pass.
799      */
800     if (cache_outstanding_valid) {
801         snmp_alarm_register(CACHE_RELEASE_FREQUENCY,
802                             0, release_cached_resources, NULL);
803     }
804 }
805 /**  @} */
806 
807