1 /*
2  * Portions of this file are subject to the following copyright(s).  See
3  * the Net-SNMP's COPYING file for more details and other copyrights
4  * that may apply:
5  *
6  * Portions of this file are copyrighted by:
7  * Copyright (c) 2016 VMware, 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 
12 #include <net-snmp/net-snmp-config.h>
13 #include <net-snmp/net-snmp-features.h>
14 
15 #include <net-snmp/net-snmp-includes.h>
16 #include <net-snmp/agent/net-snmp-agent-includes.h>
17 
18 #include <net-snmp/agent/row_merge.h>
19 
20 #include <stdint.h>
21 
22 #if HAVE_STRING_H
23 #include <string.h>
24 #else
25 #include <strings.h>
26 #endif
27 
28 netsnmp_feature_provide(row_merge);
29 netsnmp_feature_child_of(row_merge, row_merge_all);
30 netsnmp_feature_child_of(row_merge_all, mib_helpers);
31 
32 
33 #ifndef NETSNMP_FEATURE_REMOVE_ROW_MERGE
34 /** @defgroup row_merge row_merge
35  *  Calls sub handlers with request for one row at a time.
36  *  @ingroup utilities
37  *  This helper splits a whole bunch of requests into chunks based on the row
38  *  index that they refer to, and passes all requests for a given row to the lower handlers.
39  *  This is useful for handlers that don't want to process multiple rows at the
40  *  same time, but are happy to iterate through the request list for a single row.
41  *  @{
42  */
43 
44 /** returns a row_merge handler that can be injected into a given
45  *  handler chain.
46  */
47 netsnmp_mib_handler *
netsnmp_get_row_merge_handler(int prefix_len)48 netsnmp_get_row_merge_handler(int prefix_len)
49 {
50     netsnmp_mib_handler *ret = NULL;
51     ret = netsnmp_create_handler("row_merge",
52                                   netsnmp_row_merge_helper_handler);
53     if (ret) {
54         ret->myvoid = (void *)(intptr_t)prefix_len;
55     }
56     return ret;
57 }
58 
59 /** functionally the same as calling netsnmp_register_handler() but also
60  * injects a row_merge handler at the same time for you. */
61 netsnmp_feature_child_of(register_row_merge, row_merge_all);
62 #ifndef NETSNMP_FEATURE_REMOVE_REGISTER_ROW_MERGE
63 int
netsnmp_register_row_merge(netsnmp_handler_registration * reginfo)64 netsnmp_register_row_merge(netsnmp_handler_registration *reginfo)
65 {
66     netsnmp_mib_handler *handler;
67 
68     if (!reginfo)
69         return MIB_REGISTRATION_FAILED;
70 
71     handler = netsnmp_get_row_merge_handler(reginfo->rootoid_len+1);
72     if (handler &&
73         (netsnmp_inject_handler(reginfo, handler) == SNMPERR_SUCCESS))
74         return netsnmp_register_handler(reginfo);
75 
76     snmp_log(LOG_ERR, "failed to register row_merge\n");
77     netsnmp_handler_free(handler);
78     netsnmp_handler_registration_free(reginfo);
79 
80     return MIB_REGISTRATION_FAILED;
81 }
82 #endif /* NETSNMP_FEATURE_REMOVE_REGISTER_ROW_MERGE */
83 
84 static void
_rm_status_free(void * mem)85 _rm_status_free(void *mem)
86 {
87     netsnmp_row_merge_status *rm_status = (netsnmp_row_merge_status*)mem;
88 
89     if (NULL != rm_status->saved_requests)
90         free(rm_status->saved_requests);
91 
92     if (NULL != rm_status->saved_status)
93         free(rm_status->saved_status);
94 
95     free(mem);
96 }
97 
98 
99 /** retrieve row_merge_status
100  */
101 netsnmp_row_merge_status *
netsnmp_row_merge_status_get(netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo,int create_missing)102 netsnmp_row_merge_status_get(netsnmp_handler_registration *reginfo,
103                              netsnmp_agent_request_info *reqinfo,
104                              int create_missing)
105 {
106     netsnmp_row_merge_status *rm_status;
107     char buf[64];
108     int rc;
109 
110     /*
111      * see if we've already been here
112      */
113     rc = snprintf(buf, sizeof(buf), "row_merge:%p", reginfo);
114     if ((-1 == rc) || ((size_t)rc >= sizeof(buf))) {
115         snmp_log(LOG_ERR,"error creating key\n");
116         return NULL;
117     }
118 
119     rm_status = (netsnmp_row_merge_status*)netsnmp_agent_get_list_data(reqinfo, buf);
120     if ((NULL == rm_status) && create_missing) {
121         netsnmp_data_list *data_list;
122 
123         rm_status = SNMP_MALLOC_TYPEDEF(netsnmp_row_merge_status);
124         if (NULL == rm_status) {
125             snmp_log(LOG_ERR,"error allocating memory\n");
126             return NULL;
127         }
128         data_list = netsnmp_create_data_list(buf, rm_status,
129                                              _rm_status_free);
130         if (NULL == data_list) {
131             free(rm_status);
132             return NULL;
133         }
134         netsnmp_agent_add_list_data(reqinfo, data_list);
135     }
136 
137     return rm_status;
138 }
139 
140 /** Determine if this is the first row
141  *
142  * returns 1 if this is the first row for this pass of the handler.
143  */
144 int
netsnmp_row_merge_status_first(netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo)145 netsnmp_row_merge_status_first(netsnmp_handler_registration *reginfo,
146                                netsnmp_agent_request_info *reqinfo)
147 {
148     netsnmp_row_merge_status *rm_status;
149 
150     /*
151      * find status
152      */
153     rm_status = netsnmp_row_merge_status_get(reginfo, reqinfo, 0);
154     if (NULL == rm_status)
155         return 0;
156 
157     return (rm_status->count == 1) ? 1 : (rm_status->current == 1);
158 }
159 
160 /** Determine if this is the last row
161  *
162  * returns 1 if this is the last row for this pass of the handler.
163  */
164 int
netsnmp_row_merge_status_last(netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo)165 netsnmp_row_merge_status_last(netsnmp_handler_registration *reginfo,
166                               netsnmp_agent_request_info *reqinfo)
167 {
168     netsnmp_row_merge_status *rm_status;
169 
170     /*
171      * find status
172      */
173     rm_status = netsnmp_row_merge_status_get(reginfo, reqinfo, 0);
174     if (NULL == rm_status)
175         return 0;
176 
177     return (rm_status->count == 1) ? 1 :
178         (rm_status->current == rm_status->rows);
179 }
180 
181 
182 #define ROW_MERGE_WAITING 0
183 #define ROW_MERGE_ACTIVE  1
184 #define ROW_MERGE_DONE    2
185 #define ROW_MERGE_HEAD    3
186 
187 /** Implements the row_merge handler */
188 int
netsnmp_row_merge_helper_handler(netsnmp_mib_handler * handler,netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo,netsnmp_request_info * requests)189 netsnmp_row_merge_helper_handler(netsnmp_mib_handler *handler,
190                                  netsnmp_handler_registration *reginfo,
191                                  netsnmp_agent_request_info *reqinfo,
192                                  netsnmp_request_info *requests)
193 {
194     netsnmp_request_info *request, **saved_requests;
195     char *saved_status;
196     netsnmp_row_merge_status *rm_status;
197     int i, j, ret, tail, count, final_rc = SNMP_ERR_NOERROR;
198 
199     /*
200      * Use the prefix length as supplied during registration, rather
201      *  than trying to second-guess what the MIB implementer wanted.
202      */
203     int SKIP_OID = (int)(intptr_t)handler->myvoid;
204 
205     DEBUGMSGTL(("helper:row_merge", "Got request (%d): ", SKIP_OID));
206     DEBUGMSGOID(("helper:row_merge", reginfo->rootoid, reginfo->rootoid_len));
207     DEBUGMSG(("helper:row_merge", "\n"));
208 
209     /*
210      * find or create status
211      */
212     rm_status = netsnmp_row_merge_status_get(reginfo, reqinfo, 1);
213 
214     /*
215      * Count the requests, and set up an array to keep
216      *  track of the original order.
217      */
218     for (count = 0, request = requests; request; request = request->next) {
219         DEBUGIF("helper:row_merge") {
220             DEBUGMSGTL(("helper:row_merge", "  got varbind: "));
221             DEBUGMSGOID(("helper:row_merge", request->requestvb->name,
222                          request->requestvb->name_length));
223             DEBUGMSG(("helper:row_merge", "\n"));
224         }
225         count++;
226     }
227 
228     /*
229      * Optimization: skip all this if there is just one request
230      */
231     if(count == 1) {
232         rm_status->count = count;
233         if (requests->processed)
234             return SNMP_ERR_NOERROR;
235         return netsnmp_call_next_handler(handler, reginfo, reqinfo, requests);
236     }
237 
238     /*
239      * we really should only have to do this once, instead of every pass.
240      * as a precaution, we'll do it every time, but put in some asserts
241      * to see if we have to.
242      */
243     /*
244      * if the count changed, re-do everything
245      */
246     if ((0 != rm_status->count) && (rm_status->count != count)) {
247         /*
248          * ok, i know next/bulk can cause this condition. Probably
249          * GET, too. need to rethink this mode counting. maybe
250          * add the mode to the rm_status structure? xxx-rks
251          */
252         if ((reqinfo->mode != MODE_GET) &&
253             (reqinfo->mode != MODE_GETNEXT) &&
254             (reqinfo->mode != MODE_GETBULK)) {
255             netsnmp_assert((NULL != rm_status->saved_requests) &&
256                            (NULL != rm_status->saved_status));
257         }
258         DEBUGMSGTL(("helper:row_merge", "count changed! do over...\n"));
259 
260         SNMP_FREE(rm_status->saved_requests);
261         SNMP_FREE(rm_status->saved_status);
262 
263         rm_status->count = 0;
264         rm_status->rows = 0;
265     }
266 
267     if (0 == rm_status->count) {
268         /*
269          * allocate memory for saved structure
270          */
271         rm_status->saved_requests =
272             (netsnmp_request_info**)calloc(count+1,
273                                            sizeof(netsnmp_request_info*));
274         rm_status->saved_status = (char*)calloc(count,sizeof(char));
275     }
276 
277     saved_status = rm_status->saved_status;
278     saved_requests = rm_status->saved_requests;
279 
280     /*
281      * set up saved requests, and set any processed requests to done
282      */
283     i = 0;
284     for (request = requests; request; request = request->next, i++) {
285         if (request->processed) {
286             saved_status[i] = ROW_MERGE_DONE;
287             DEBUGMSGTL(("helper:row_merge", "  skipping processed oid: "));
288             DEBUGMSGOID(("helper:row_merge", request->requestvb->name,
289                          request->requestvb->name_length));
290             DEBUGMSG(("helper:row_merge", "\n"));
291         }
292         else
293             saved_status[i] = ROW_MERGE_WAITING;
294         if (0 != rm_status->count)
295             netsnmp_assert(saved_requests[i] == request);
296         saved_requests[i] = request;
297         saved_requests[i]->prev = NULL;
298     }
299     saved_requests[i] = NULL;
300 
301     /*
302      * Note that saved_requests[count] is valid
303      *    (because of the 'count+1' in the calloc above),
304      * but NULL (since it's past the end of the list).
305      * This simplifies the re-linking later.
306      */
307 
308     /*
309      * Work through the (unprocessed) requests in order.
310      * For each of these, search the rest of the list for any
311      *   matching indexes, and link them into a new list.
312      */
313     for (i=0; i<count; i++) {
314 	if (saved_status[i] != ROW_MERGE_WAITING)
315 	    continue;
316 
317         if (0 == rm_status->count)
318             rm_status->rows++;
319         DEBUGMSGTL(("helper:row_merge", " row %d oid[%d]: ", rm_status->rows, i));
320         DEBUGMSGOID(("helper:row_merge", saved_requests[i]->requestvb->name,
321                      saved_requests[i]->requestvb->name_length));
322         DEBUGMSG(("helper:row_merge", "\n"));
323 
324 	saved_requests[i]->next = NULL;
325 	saved_status[i] = ROW_MERGE_HEAD;
326 	tail = i;
327         for (j=i+1; j<count; j++) {
328 	    if (saved_status[j] != ROW_MERGE_WAITING)
329 	        continue;
330 
331             DEBUGMSGTL(("helper:row_merge", "? oid[%d]: ", j));
332             DEBUGMSGOID(("helper:row_merge",
333                          saved_requests[j]->requestvb->name,
334                          saved_requests[j]->requestvb->name_length));
335             if (!snmp_oid_compare(
336                     saved_requests[i]->requestvb->name+SKIP_OID,
337                     saved_requests[i]->requestvb->name_length-SKIP_OID,
338                     saved_requests[j]->requestvb->name+SKIP_OID,
339                     saved_requests[j]->requestvb->name_length-SKIP_OID)) {
340                 DEBUGMSG(("helper:row_merge", " match\n"));
341                 saved_requests[tail]->next = saved_requests[j];
342                 saved_requests[j]->next    = NULL;
343                 saved_requests[j]->prev = saved_requests[tail];
344 	        saved_status[j] = ROW_MERGE_ACTIVE;
345 	        tail = j;
346             }
347             else
348                 DEBUGMSG(("helper:row_merge", " no match\n"));
349         }
350     }
351 
352     /*
353      * not that we have a list for each row, call next handler...
354      */
355     if (0 == rm_status->count)
356         rm_status->count = count;
357     rm_status->current = 0;
358     for (i=0; i<count; i++) {
359 	if (saved_status[i] != ROW_MERGE_HEAD)
360 	    continue;
361 
362         /*
363          * found the head of a new row,
364          * call the next handler with this list
365          */
366         rm_status->current++;
367         ret = netsnmp_call_next_handler(handler, reginfo, reqinfo,
368 			                saved_requests[i]);
369         if (ret != SNMP_ERR_NOERROR) {
370             snmp_log(LOG_WARNING,
371                      "bad rc (%d) from next handler in row_merge\n", ret);
372             if (SNMP_ERR_NOERROR == final_rc)
373                 final_rc = ret;
374         }
375     }
376 
377     /*
378      * restore original linked list
379      */
380     for (i=0; i<count; i++) {
381 	saved_requests[i]->next = saved_requests[i+1];
382         if (i>0)
383 	    saved_requests[i]->prev = saved_requests[i-1];
384     }
385 
386     return final_rc;
387 }
388 
389 /**
390  *  initializes the row_merge helper which then registers a row_merge
391  *  handler as a run-time injectable handler for configuration file
392  *  use.
393  */
394 void
netsnmp_init_row_merge(void)395 netsnmp_init_row_merge(void)
396 {
397     netsnmp_register_handler_by_name("row_merge",
398                                      netsnmp_get_row_merge_handler(-1));
399 }
400 #else /* NETSNMP_FEATURE_REMOVE_ROW_MERGE */
401 netsnmp_feature_unused(row_merge);
402 #endif /* NETSNMP_FEATURE_REMOVE_ROW_MERGE */
403 
404 
405 /**  @} */
406 
407