/src/net-snmp/agent/helpers/table.c
Line | Count | Source |
1 | | /* |
2 | | * table.c |
3 | | */ |
4 | | |
5 | | /* Portions of this file are subject to the following copyright(s). See |
6 | | * the Net-SNMP's COPYING file for more details and other copyrights |
7 | | * that may apply: |
8 | | */ |
9 | | /* |
10 | | * Portions of this file are copyrighted by: |
11 | | * Copyright © 2003 Sun Microsystems, Inc. All rights reserved. |
12 | | * Use is subject to license terms specified in the COPYING file |
13 | | * distributed with the Net-SNMP package. |
14 | | */ |
15 | | /* |
16 | | * Portions of this file are copyrighted by: |
17 | | * Copyright (C) 2007 Apple, Inc. All rights reserved. |
18 | | * Use is subject to license terms specified in the COPYING file |
19 | | * distributed with the Net-SNMP package. |
20 | | * |
21 | | * Portions of this file are copyrighted by: |
22 | | * Copyright (c) 2016 VMware, Inc. All rights reserved. |
23 | | * Use is subject to license terms specified in the COPYING file |
24 | | * distributed with the Net-SNMP package. |
25 | | */ |
26 | | |
27 | | #include <net-snmp/net-snmp-config.h> |
28 | | |
29 | | #include <net-snmp/net-snmp-features.h> |
30 | | #include <net-snmp/net-snmp-includes.h> |
31 | | #include <net-snmp/agent/net-snmp-agent-includes.h> |
32 | | |
33 | | #include <net-snmp/agent/table.h> |
34 | | |
35 | | #ifndef NETSNMP_NO_WRITE_SUPPORT |
36 | | netsnmp_feature_require(oid_stash); |
37 | | #endif /* !NETSNMP_NO_WRITE_SUPPORT */ |
38 | | |
39 | | #ifdef HAVE_STRING_H |
40 | | #include <string.h> |
41 | | #else |
42 | | #include <strings.h> |
43 | | #endif |
44 | | |
45 | | #include <net-snmp/library/snmp_assert.h> |
46 | | |
47 | | netsnmp_feature_child_of(table_all, mib_helpers); |
48 | | |
49 | | netsnmp_feature_child_of(table_build_result, table_all); |
50 | | netsnmp_feature_child_of(table_get_or_create_row_stash, table_all); |
51 | | netsnmp_feature_child_of(registration_owns_table_info, table_all); |
52 | | netsnmp_feature_child_of(table_sparse, table_all); |
53 | | |
54 | | static void table_helper_cleanup(netsnmp_agent_request_info *reqinfo, |
55 | | netsnmp_request_info *request, |
56 | | int status); |
57 | | static void table_data_free_func(void *data); |
58 | | static int |
59 | | sparse_table_helper_handler(netsnmp_mib_handler *handler, |
60 | | netsnmp_handler_registration *reginfo, |
61 | | netsnmp_agent_request_info *reqinfo, |
62 | | netsnmp_request_info *requests); |
63 | | |
64 | | /** @defgroup table table |
65 | | * Helps you implement a table. |
66 | | * @ingroup handler |
67 | | * |
68 | | * This handler helps you implement a table by doing some of the |
69 | | * processing for you. |
70 | | * |
71 | | * This handler truly shows the power of the new handler mechanism. |
72 | | * By creating a table handler and injecting it into your calling |
73 | | * chain, or by using the netsnmp_register_table() function to register your |
74 | | * table, you get access to some pre-parsed information. |
75 | | * Specifically, the table handler pulls out the column number and |
76 | | * indexes from the request oid so that you don't have to do the |
77 | | * complex work to do that parsing within your own code. |
78 | | * |
79 | | * To do this, the table handler needs to know up front how your |
80 | | * table is structured. To inform it about this, you fill in a |
81 | | * table_registration_info structure that is passed to the table |
82 | | * handler. It contains the asn index types for the table as well as |
83 | | * the minimum and maximum column that should be used. |
84 | | * |
85 | | * @{ |
86 | | */ |
87 | | |
88 | | /** Given a netsnmp_table_registration_info object, creates a table handler. |
89 | | * You can use this table handler by injecting it into a calling |
90 | | * chain. When the handler gets called, it'll do processing and |
91 | | * store it's information into the request->parent_data structure. |
92 | | * |
93 | | * The table helper handler pulls out the column number and indexes from |
94 | | * the request oid so that you don't have to do the complex work of |
95 | | * parsing within your own code. |
96 | | * |
97 | | * @param tabreq is a pointer to a netsnmp_table_registration_info struct. |
98 | | * The table handler needs to know up front how your table is structured. |
99 | | * A netsnmp_table_registration_info structure that is |
100 | | * passed to the table handler should contain the asn index types for the |
101 | | * table as well as the minimum and maximum column that should be used. |
102 | | * |
103 | | * @return Returns a pointer to a netsnmp_mib_handler struct which contains |
104 | | * the handler's name and the access method |
105 | | * |
106 | | */ |
107 | | netsnmp_mib_handler * |
108 | | netsnmp_get_table_handler(netsnmp_table_registration_info *tabreq) |
109 | 0 | { |
110 | 0 | netsnmp_mib_handler *ret = NULL; |
111 | |
|
112 | 0 | if (!tabreq) { |
113 | 0 | snmp_log(LOG_INFO, "netsnmp_get_table_handler(NULL) called\n"); |
114 | 0 | return NULL; |
115 | 0 | } |
116 | | |
117 | 0 | ret = netsnmp_create_handler(TABLE_HANDLER_NAME, table_helper_handler); |
118 | 0 | if (ret) { |
119 | 0 | ret->myvoid = (void *) tabreq; |
120 | 0 | tabreq->number_indexes = count_varbinds(tabreq->indexes); |
121 | 0 | } |
122 | 0 | return ret; |
123 | 0 | } |
124 | | |
125 | | static void *netsnmp_clone_tri(void *tri) |
126 | 0 | { |
127 | 0 | return netsnmp_table_registration_info_clone(tri); |
128 | 0 | } |
129 | | |
130 | | static void netsnmp_free_tri(void *tri) |
131 | 0 | { |
132 | 0 | netsnmp_table_registration_info_free(tri); |
133 | 0 | } |
134 | | |
135 | | /** Configures a handler such that table registration information is freed by |
136 | | * netsnmp_handler_free(). Should only be called if handler->myvoid points to |
137 | | * an object of type netsnmp_table_registration_info. |
138 | | */ |
139 | | void netsnmp_handler_owns_table_info(netsnmp_mib_handler *handler) |
140 | 0 | { |
141 | 0 | netsnmp_assert(handler); |
142 | 0 | netsnmp_assert(handler->myvoid); |
143 | 0 | handler->data_clone = netsnmp_clone_tri; |
144 | 0 | handler->data_free = netsnmp_free_tri; |
145 | 0 | } |
146 | | |
147 | | /** Configures a handler such that table registration information is freed by |
148 | | * netsnmp_handler_free(). Should only be called if reg->handler->myvoid |
149 | | * points to an object of type netsnmp_table_registration_info. |
150 | | */ |
151 | | #ifndef NETSNMP_FEATURE_REMOVE_REGISTRATION_OWNS_TABLE_INFO |
152 | | void netsnmp_registration_owns_table_info(netsnmp_handler_registration *reg) |
153 | 0 | { |
154 | 0 | if (reg) |
155 | 0 | netsnmp_handler_owns_table_info(reg->handler); |
156 | 0 | } |
157 | | #endif /* NETSNMP_FEATURE_REMOVE_REGISTRATION_OWNS_TABLE_INFO */ |
158 | | |
159 | | /** creates a table handler given the netsnmp_table_registration_info object, |
160 | | * inserts it into the request chain and then calls |
161 | | * netsnmp_register_handler() to register the table into the agent. |
162 | | */ |
163 | | int |
164 | | netsnmp_register_table(netsnmp_handler_registration *reginfo, |
165 | | netsnmp_table_registration_info *tabreq) |
166 | 0 | { |
167 | 0 | netsnmp_mib_handler *handler = netsnmp_get_table_handler(tabreq); |
168 | 0 | if (!handler || |
169 | 0 | (netsnmp_inject_handler(reginfo, handler) != SNMPERR_SUCCESS)) { |
170 | 0 | snmp_log(LOG_ERR, "could not create table handler\n"); |
171 | 0 | netsnmp_handler_free(handler); |
172 | 0 | netsnmp_handler_registration_free(reginfo); |
173 | 0 | return MIB_REGISTRATION_FAILED; |
174 | 0 | } |
175 | | |
176 | 0 | return netsnmp_register_handler(reginfo); |
177 | 0 | } |
178 | | |
179 | | int |
180 | | netsnmp_unregister_table(netsnmp_handler_registration *reginfo) |
181 | 0 | { |
182 | | /* Locate "this" reginfo */ |
183 | | /* SNMP_FREE(reginfo->myvoid); */ |
184 | 0 | return netsnmp_unregister_handler(reginfo); |
185 | 0 | } |
186 | | |
187 | | /** Extracts the processed table information from a given request. |
188 | | * Call this from subhandlers on a request to extract the processed |
189 | | * netsnmp_request_info information. The resulting information includes the |
190 | | * index values and the column number. |
191 | | * |
192 | | * @param request populated netsnmp request structure |
193 | | * |
194 | | * @return populated netsnmp_table_request_info structure |
195 | | */ |
196 | | NETSNMP_INLINE netsnmp_table_request_info * |
197 | | netsnmp_extract_table_info(netsnmp_request_info *request) |
198 | 0 | { |
199 | 0 | return (netsnmp_table_request_info *) |
200 | 0 | netsnmp_request_get_list_data(request, TABLE_HANDLER_NAME); |
201 | 0 | } |
202 | | |
203 | | /** extracts the registered netsnmp_table_registration_info object from a |
204 | | * netsnmp_handler_registration object */ |
205 | | netsnmp_table_registration_info * |
206 | | netsnmp_find_table_registration_info(netsnmp_handler_registration *reginfo) |
207 | 0 | { |
208 | 0 | return (netsnmp_table_registration_info *) |
209 | 0 | netsnmp_find_handler_data_by_name(reginfo, TABLE_HANDLER_NAME); |
210 | 0 | } |
211 | | |
212 | | /** implements the table helper handler */ |
213 | | int |
214 | | table_helper_handler(netsnmp_mib_handler *handler, |
215 | | netsnmp_handler_registration *reginfo, |
216 | | netsnmp_agent_request_info *reqinfo, |
217 | | netsnmp_request_info *requests) |
218 | 0 | { |
219 | |
|
220 | 0 | netsnmp_request_info *request; |
221 | 0 | netsnmp_table_registration_info *tbl_info; |
222 | 0 | int oid_index_pos; |
223 | 0 | unsigned int oid_column_pos; |
224 | 0 | unsigned int tmp_idx; |
225 | 0 | ssize_t tmp_len; |
226 | 0 | int incomplete, out_of_range; |
227 | 0 | int status = SNMP_ERR_NOERROR, need_processing = 0; |
228 | 0 | oid *tmp_name; |
229 | 0 | netsnmp_table_request_info *tbl_req_info; |
230 | 0 | netsnmp_variable_list *vb; |
231 | |
|
232 | 0 | if (!reginfo || !handler) |
233 | 0 | return SNMPERR_GENERR; |
234 | | |
235 | 0 | oid_index_pos = reginfo->rootoid_len + 2; |
236 | 0 | oid_column_pos = reginfo->rootoid_len + 1; |
237 | 0 | tbl_info = (netsnmp_table_registration_info *) handler->myvoid; |
238 | |
|
239 | 0 | if ((!handler->myvoid) || (!tbl_info->indexes)) { |
240 | 0 | snmp_log(LOG_ERR, "improperly registered table found\n"); |
241 | 0 | snmp_log(LOG_ERR, "name: %s, table info: %p, indexes: %p\n", |
242 | 0 | handler->handler_name, handler->myvoid, tbl_info->indexes); |
243 | | |
244 | | /* |
245 | | * XXX-rks: unregister table? |
246 | | */ |
247 | 0 | return SNMP_ERR_GENERR; |
248 | 0 | } |
249 | | |
250 | 0 | DEBUGIF("helper:table:req") { |
251 | 0 | DEBUGMSGTL(("helper:table:req", |
252 | 0 | "Got %s (%d) mode request for handler %s: base oid:", |
253 | 0 | se_find_label_in_slist("agent_mode", reqinfo->mode), |
254 | 0 | reqinfo->mode, handler->handler_name)); |
255 | 0 | DEBUGMSGOID(("helper:table:req", reginfo->rootoid, |
256 | 0 | reginfo->rootoid_len)); |
257 | 0 | DEBUGMSG(("helper:table:req", "\n")); |
258 | 0 | } |
259 | | |
260 | | /* |
261 | | * if the agent request info has a state reference, then this is a |
262 | | * later pass of a set request and we can skip all the lookup stuff. |
263 | | * |
264 | | * xxx-rks: this might break for handlers which only handle one varbind |
265 | | * at a time... those handlers should not save data by their handler_name |
266 | | * in the netsnmp_agent_request_info. |
267 | | */ |
268 | 0 | if (netsnmp_agent_get_list_data(reqinfo, handler->next->handler_name)) { |
269 | 0 | #ifndef NETSNMP_NO_WRITE_SUPPORT |
270 | 0 | if (MODE_IS_SET(reqinfo->mode)) { |
271 | 0 | return netsnmp_call_next_handler(handler, reginfo, reqinfo, |
272 | 0 | requests); |
273 | 0 | } else { |
274 | 0 | #endif /* NETSNMP_NO_WRITE_SUPPORT */ |
275 | | /** XXX-rks: memory leak. add cleanup handler? */ |
276 | 0 | netsnmp_free_agent_data_sets(reqinfo); |
277 | 0 | #ifndef NETSNMP_NO_WRITE_SUPPORT |
278 | 0 | } |
279 | 0 | #endif /* NETSNMP_NO_WRITE_SUPPORT */ |
280 | 0 | } |
281 | | |
282 | 0 | #ifndef NETSNMP_NO_WRITE_SUPPORT |
283 | 0 | if ( MODE_IS_SET(reqinfo->mode) && |
284 | 0 | (reqinfo->mode != MODE_SET_RESERVE1)) { |
285 | | /* |
286 | | * for later set modes, we can skip all the index parsing, |
287 | | * and we always need to let child handlers have a chance |
288 | | * to clean up, if they were called in the first place (i.e. have |
289 | | * a valid table info pointer). |
290 | | */ |
291 | 0 | if(NULL == netsnmp_extract_table_info(requests)) { |
292 | 0 | DEBUGMSGTL(("helper:table","no table info for set - skipping\n")); |
293 | 0 | } |
294 | 0 | else |
295 | 0 | need_processing = 1; |
296 | 0 | } |
297 | 0 | else { |
298 | 0 | #endif /* NETSNMP_NO_WRITE_SUPPORT */ |
299 | | /* |
300 | | * for GETS, only continue if we have at least one valid request. |
301 | | * for RESERVE1, only continue if we have indexes for all requests. |
302 | | */ |
303 | | |
304 | | /* |
305 | | * loop through requests |
306 | | */ |
307 | |
|
308 | 0 | for (request = requests; request; request = request->next) { |
309 | 0 | netsnmp_variable_list *var = request->requestvb; |
310 | |
|
311 | 0 | DEBUGMSGOID(("verbose:table", var->name, var->name_length)); |
312 | 0 | DEBUGMSG(("verbose:table", "\n")); |
313 | |
|
314 | 0 | if (request->processed) { |
315 | 0 | DEBUGMSG(("verbose:table", "already processed\n")); |
316 | 0 | continue; |
317 | 0 | } |
318 | 0 | netsnmp_assert(request->status == SNMP_ERR_NOERROR); |
319 | | |
320 | | /* |
321 | | * this should probably be handled further up |
322 | | */ |
323 | 0 | if ((reqinfo->mode == MODE_GET) && (var->type != ASN_NULL)) { |
324 | | /* |
325 | | * valid request if ASN_NULL |
326 | | */ |
327 | 0 | DEBUGMSGTL(("helper:table", |
328 | 0 | " GET var type is not ASN_NULL\n")); |
329 | 0 | netsnmp_set_request_error(reqinfo, request, |
330 | 0 | SNMP_ERR_WRONGTYPE); |
331 | 0 | continue; |
332 | 0 | } |
333 | | |
334 | 0 | #ifndef NETSNMP_NO_WRITE_SUPPORT |
335 | 0 | if (reqinfo->mode == MODE_SET_RESERVE1) { |
336 | 0 | DEBUGIF("helper:table:set") { |
337 | 0 | u_char *buf = NULL; |
338 | 0 | size_t buf_len = 0, out_len = 0; |
339 | 0 | DEBUGMSGTL(("helper:table:set", " SET_REQUEST for OID: ")); |
340 | 0 | DEBUGMSGOID(("helper:table:set", var->name, var->name_length)); |
341 | 0 | out_len = 0; |
342 | 0 | if (sprint_realloc_by_type(&buf, &buf_len, &out_len, 1, |
343 | 0 | var, NULL, NULL, NULL)) { |
344 | 0 | DEBUGMSG(("helper:table:set"," type=%d(%02x), value=%s\n", |
345 | 0 | var->type, var->type, buf)); |
346 | 0 | } else { |
347 | 0 | if (buf != NULL) { |
348 | 0 | DEBUGMSG(("helper:table:set", |
349 | 0 | " type=%d(%02x), value=%s [TRUNCATED]\n", |
350 | 0 | var->type, var->type, buf)); |
351 | 0 | } else { |
352 | 0 | DEBUGMSG(("helper:table:set", |
353 | 0 | " type=%d(%02x), value=[NIL] [TRUNCATED]\n", |
354 | 0 | var->type, var->type)); |
355 | 0 | } |
356 | 0 | } |
357 | 0 | if (buf != NULL) { |
358 | 0 | free(buf); |
359 | 0 | } |
360 | 0 | } |
361 | 0 | } |
362 | 0 | #endif /* NETSNMP_NO_WRITE_SUPPORT */ |
363 | | |
364 | | /* |
365 | | * check to make sure its in table range |
366 | | */ |
367 | |
|
368 | 0 | out_of_range = 0; |
369 | | /* |
370 | | * if our root oid is > var->name and this is not a GETNEXT, |
371 | | * then the oid is out of range. (only compare up to shorter |
372 | | * length) |
373 | | */ |
374 | 0 | if (reginfo->rootoid_len > var->name_length) |
375 | 0 | tmp_len = var->name_length; |
376 | 0 | else |
377 | 0 | tmp_len = reginfo->rootoid_len; |
378 | 0 | if (snmp_oid_compare(reginfo->rootoid, reginfo->rootoid_len, |
379 | 0 | var->name, tmp_len) > 0) { |
380 | 0 | if (reqinfo->mode == MODE_GETNEXT) { |
381 | 0 | if (var->name != var->name_loc) |
382 | 0 | SNMP_FREE(var->name); |
383 | 0 | snmp_set_var_objid(var, reginfo->rootoid, |
384 | 0 | reginfo->rootoid_len); |
385 | 0 | } else { |
386 | 0 | DEBUGMSGTL(("helper:table", " oid is out of range.\n")); |
387 | 0 | out_of_range = 1; |
388 | 0 | } |
389 | 0 | } |
390 | | /* |
391 | | * if var->name is longer than the root, make sure it is |
392 | | * table.1 (table.ENTRY). |
393 | | */ |
394 | 0 | else if ((var->name_length > reginfo->rootoid_len) && |
395 | 0 | (var->name[reginfo->rootoid_len] != 1)) { |
396 | 0 | if ((var->name[reginfo->rootoid_len] < 1) && |
397 | 0 | (reqinfo->mode == MODE_GETNEXT)) { |
398 | 0 | var->name[reginfo->rootoid_len] = 1; |
399 | 0 | var->name_length = reginfo->rootoid_len; |
400 | 0 | } else { |
401 | 0 | out_of_range = 1; |
402 | 0 | DEBUGMSGTL(("helper:table", " oid is out of range.\n")); |
403 | 0 | } |
404 | 0 | } |
405 | | /* |
406 | | * if it is not in range, then mark it in the request list |
407 | | * because we can't process it, and set an error so |
408 | | * nobody else wastes time trying to process it either. |
409 | | */ |
410 | 0 | if (out_of_range) { |
411 | 0 | DEBUGMSGTL(("helper:table", " Not processed: ")); |
412 | 0 | DEBUGMSGOID(("helper:table", var->name, var->name_length)); |
413 | 0 | DEBUGMSG(("helper:table", "\n")); |
414 | | |
415 | | /* |
416 | | * Reject requests of the form 'myTable.N' (N != 1) |
417 | | */ |
418 | 0 | #ifndef NETSNMP_NO_WRITE_SUPPORT |
419 | 0 | if (reqinfo->mode == MODE_SET_RESERVE1) |
420 | 0 | table_helper_cleanup(reqinfo, request, |
421 | 0 | SNMP_ERR_NOTWRITABLE); |
422 | 0 | else |
423 | 0 | #endif /* NETSNMP_NO_WRITE_SUPPORT */ |
424 | 0 | if (reqinfo->mode == MODE_GET) |
425 | 0 | table_helper_cleanup(reqinfo, request, |
426 | 0 | SNMP_NOSUCHOBJECT); |
427 | 0 | else |
428 | 0 | request->processed = 1; /* skip if next handler called */ |
429 | 0 | continue; |
430 | 0 | } |
431 | | |
432 | | |
433 | | /* |
434 | | * Check column ranges; set-up to pull out indexes from OID. |
435 | | */ |
436 | | |
437 | 0 | incomplete = 0; |
438 | 0 | tbl_req_info = netsnmp_extract_table_info(request); |
439 | 0 | if (NULL == tbl_req_info) { |
440 | 0 | tbl_req_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_request_info); |
441 | 0 | if (tbl_req_info == NULL) { |
442 | 0 | table_helper_cleanup(reqinfo, request, |
443 | 0 | SNMP_ERR_GENERR); |
444 | 0 | continue; |
445 | 0 | } |
446 | 0 | tbl_req_info->reg_info = tbl_info; |
447 | 0 | tbl_req_info->indexes = snmp_clone_varbind(tbl_info->indexes); |
448 | 0 | tbl_req_info->number_indexes = 0; /* none yet */ |
449 | 0 | netsnmp_request_add_list_data(request, |
450 | 0 | netsnmp_create_data_list |
451 | 0 | (TABLE_HANDLER_NAME, |
452 | 0 | (void *) tbl_req_info, |
453 | 0 | table_data_free_func)); |
454 | 0 | } else { |
455 | 0 | DEBUGMSGTL(("helper:table", " using existing tbl_req_info\n ")); |
456 | 0 | } |
457 | | |
458 | | /* |
459 | | * do we have a column? |
460 | | */ |
461 | 0 | if (var->name_length > oid_column_pos) { |
462 | | /* |
463 | | * oid is long enough to contain COLUMN info |
464 | | */ |
465 | 0 | DEBUGMSGTL(("helper:table:col", |
466 | 0 | " have at least a column (%" NETSNMP_PRIo "d)\n", |
467 | 0 | var->name[oid_column_pos])); |
468 | 0 | if (var->name[oid_column_pos] < tbl_info->min_column) { |
469 | 0 | DEBUGMSGTL(("helper:table:col", |
470 | 0 | " but it's less than min (%d)\n", |
471 | 0 | tbl_info->min_column)); |
472 | 0 | if (reqinfo->mode == MODE_GETNEXT) { |
473 | | /* |
474 | | * fix column, truncate useless column info |
475 | | */ |
476 | 0 | var->name_length = oid_column_pos; |
477 | 0 | tbl_req_info->colnum = tbl_info->min_column; |
478 | 0 | } else |
479 | 0 | out_of_range = 1; |
480 | 0 | } else if (var->name[oid_column_pos] > tbl_info->max_column) |
481 | 0 | out_of_range = 1; |
482 | 0 | else |
483 | 0 | tbl_req_info->colnum = var->name[oid_column_pos]; |
484 | |
|
485 | 0 | if (out_of_range) { |
486 | | /* |
487 | | * this is out of range... remove from requests, free |
488 | | * memory |
489 | | */ |
490 | 0 | DEBUGMSGTL(("helper:table", |
491 | 0 | " oid is out of range. Not processed: ")); |
492 | 0 | DEBUGMSGOID(("helper:table", var->name, var->name_length)); |
493 | 0 | DEBUGMSG(("helper:table", "\n")); |
494 | | |
495 | | /* |
496 | | * Reject requests of the form 'myEntry.N' (invalid N) |
497 | | */ |
498 | 0 | #ifndef NETSNMP_NO_WRITE_SUPPORT |
499 | 0 | if (reqinfo->mode == MODE_SET_RESERVE1) |
500 | 0 | table_helper_cleanup(reqinfo, request, |
501 | 0 | SNMP_ERR_NOTWRITABLE); |
502 | 0 | else |
503 | 0 | #endif /* NETSNMP_NO_WRITE_SUPPORT */ |
504 | 0 | if (reqinfo->mode == MODE_GET) |
505 | 0 | table_helper_cleanup(reqinfo, request, |
506 | 0 | SNMP_NOSUCHOBJECT); |
507 | 0 | else |
508 | 0 | request->processed = 1; /* skip if next handler called */ |
509 | 0 | continue; |
510 | 0 | } |
511 | | /* |
512 | | * use column verification |
513 | | */ |
514 | 0 | else if (tbl_info->valid_columns) { |
515 | 0 | tbl_req_info->colnum = |
516 | 0 | netsnmp_closest_column(var->name[oid_column_pos], |
517 | 0 | tbl_info->valid_columns); |
518 | 0 | DEBUGMSGTL(("helper:table:col", " closest column is %d\n", |
519 | 0 | tbl_req_info->colnum)); |
520 | | /* |
521 | | * xxx-rks: document why the continue... |
522 | | */ |
523 | 0 | if (tbl_req_info->colnum == 0) |
524 | 0 | continue; |
525 | 0 | if (tbl_req_info->colnum != var->name[oid_column_pos]) { |
526 | 0 | DEBUGMSGTL(("helper:table:col", |
527 | 0 | " which doesn't match req " |
528 | 0 | "%" NETSNMP_PRIo "d - truncating index info\n", |
529 | 0 | var->name[oid_column_pos])); |
530 | | /* |
531 | | * different column! truncate useless index info |
532 | | */ |
533 | 0 | var->name_length = oid_column_pos + 1; /* pos is 0 based */ |
534 | 0 | } |
535 | 0 | } |
536 | | /* |
537 | | * var->name_length may have changed - check again |
538 | | */ |
539 | 0 | if ((int)var->name_length <= oid_index_pos) { /* pos is 0 based */ |
540 | 0 | DEBUGMSGTL(("helper:table", " not enough for indexes\n")); |
541 | 0 | tbl_req_info->index_oid_len = 0; /** none available */ |
542 | 0 | } else { |
543 | | /* |
544 | | * oid is long enough to contain INDEX info |
545 | | */ |
546 | 0 | tbl_req_info->index_oid_len = |
547 | 0 | var->name_length - oid_index_pos; |
548 | 0 | DEBUGMSGTL(("helper:table", " have %lu bytes of index\n", |
549 | 0 | (unsigned long)tbl_req_info->index_oid_len)); |
550 | 0 | netsnmp_assert(tbl_req_info->index_oid_len < MAX_OID_LEN); |
551 | 0 | memcpy(tbl_req_info->index_oid, &var->name[oid_index_pos], |
552 | 0 | tbl_req_info->index_oid_len * sizeof(oid)); |
553 | 0 | tmp_name = tbl_req_info->index_oid; |
554 | 0 | } |
555 | 0 | } else if (reqinfo->mode == MODE_GETNEXT || |
556 | 0 | reqinfo->mode == MODE_GETBULK) { |
557 | | /* |
558 | | * oid is NOT long enough to contain column or index info, so start |
559 | | * at the minimum column. Set index oid len to 0 because we don't |
560 | | * have any index info in the OID. |
561 | | */ |
562 | 0 | DEBUGMSGTL(("helper:table", " no column/index in request\n")); |
563 | 0 | tbl_req_info->index_oid_len = 0; |
564 | 0 | tbl_req_info->colnum = tbl_info->min_column; |
565 | 0 | } else { |
566 | | /* |
567 | | * oid is NOT long enough to contain index info, |
568 | | * so we can't do anything with it. |
569 | | * |
570 | | * Reject requests of the form 'myTable' or 'myEntry' |
571 | | */ |
572 | 0 | if (reqinfo->mode == MODE_GET ) { |
573 | 0 | table_helper_cleanup(reqinfo, request, SNMP_NOSUCHOBJECT); |
574 | 0 | #ifndef NETSNMP_NO_WRITE_SUPPORT |
575 | 0 | } else if (reqinfo->mode == MODE_SET_RESERVE1 ) { |
576 | 0 | table_helper_cleanup(reqinfo, request, SNMP_ERR_NOTWRITABLE); |
577 | 0 | #endif /* NETSNMP_NO_WRITE_SUPPORT */ |
578 | 0 | } |
579 | 0 | continue; |
580 | 0 | } |
581 | | |
582 | | /* |
583 | | * set up tmp_len to be the number of OIDs we have beyond the column; |
584 | | * these should be the index(s) for the table. If the index_oid_len |
585 | | * is 0, set tmp_len to -1 so that when we try to parse the index below, |
586 | | * we just zero fill everything. |
587 | | */ |
588 | 0 | if (tbl_req_info->index_oid_len == 0) { |
589 | 0 | incomplete = 1; |
590 | 0 | tmp_len = -1; |
591 | 0 | } else |
592 | 0 | tmp_len = tbl_req_info->index_oid_len; |
593 | | |
594 | | |
595 | | /* |
596 | | * for each index type, try to extract the index from var->name |
597 | | */ |
598 | 0 | DEBUGMSGTL(("helper:table", " looking for %d indexes\n", |
599 | 0 | tbl_info->number_indexes)); |
600 | 0 | for (tmp_idx = 0, vb = tbl_req_info->indexes; |
601 | 0 | tmp_idx < tbl_info->number_indexes; |
602 | 0 | ++tmp_idx, vb = vb->next_variable) { |
603 | 0 | size_t parsed_oid_len; |
604 | |
|
605 | 0 | if (incomplete && tmp_len) { |
606 | | /* |
607 | | * incomplete/illegal OID, set up dummy 0 to parse |
608 | | */ |
609 | 0 | DEBUGMSGTL(("helper:table", |
610 | 0 | " oid indexes not complete: ")); |
611 | 0 | DEBUGMSGOID(("helper:table", var->name, var->name_length)); |
612 | 0 | DEBUGMSG(("helper:table", "\n")); |
613 | | |
614 | | /* |
615 | | * no sense in trying anymore if this is a GET/SET. |
616 | | * |
617 | | * Reject requests of the form 'myObject' (no instance) |
618 | | */ |
619 | 0 | tmp_len = 0; |
620 | 0 | tmp_name = NULL; |
621 | 0 | break; |
622 | 0 | } |
623 | | /* |
624 | | * try and parse current index |
625 | | */ |
626 | 0 | netsnmp_assert(tmp_len >= 0); |
627 | 0 | parsed_oid_len = tmp_len; |
628 | 0 | if (parse_one_oid_index(&tmp_name, &parsed_oid_len, |
629 | 0 | vb, 1) != SNMPERR_SUCCESS) { |
630 | 0 | incomplete = 1; |
631 | 0 | tmp_len = -1; /* is this necessary? Better safe than |
632 | | * sorry */ |
633 | 0 | } else { |
634 | 0 | tmp_len = parsed_oid_len; |
635 | 0 | DEBUGMSGTL(("helper:table", " got 1 (incomplete=%d)\n", |
636 | 0 | incomplete)); |
637 | | /* |
638 | | * do not count incomplete indexes |
639 | | */ |
640 | 0 | if (incomplete) |
641 | 0 | continue; |
642 | 0 | ++tbl_req_info->number_indexes; /** got one ok */ |
643 | 0 | if (tmp_len <= 0) { |
644 | 0 | incomplete = 1; |
645 | 0 | tmp_len = -1; /* is this necessary? Better safe |
646 | | * than sorry */ |
647 | 0 | } |
648 | 0 | } |
649 | 0 | } /** for loop */ |
650 | |
|
651 | 0 | DEBUGIF("helper:table:results") { |
652 | 0 | unsigned int count; |
653 | 0 | u_char *buf = NULL; |
654 | 0 | size_t buf_len = 0, out_len = 0; |
655 | 0 | DEBUGMSGTL(("helper:table:results", " found %d indexes\n", |
656 | 0 | tbl_req_info->number_indexes)); |
657 | 0 | DEBUGMSGTL(("helper:table:results", |
658 | 0 | " column: %d, indexes: %d", |
659 | 0 | tbl_req_info->colnum, |
660 | 0 | tbl_req_info->number_indexes)); |
661 | 0 | for (vb = tbl_req_info->indexes, count = 0; |
662 | 0 | vb && count < tbl_req_info->number_indexes; |
663 | 0 | count++, vb = vb->next_variable) { |
664 | 0 | out_len = 0; |
665 | 0 | if (sprint_realloc_by_type(&buf, &buf_len, &out_len, 1, |
666 | 0 | vb, NULL, NULL, NULL)) { |
667 | 0 | DEBUGMSG(("helper:table:results", |
668 | 0 | " index: type=%d(%02x), value=%s", |
669 | 0 | vb->type, vb->type, buf)); |
670 | 0 | } else { |
671 | 0 | if (buf != NULL) { |
672 | 0 | DEBUGMSG(("helper:table:results", |
673 | 0 | " index: type=%d(%02x), value=%s [TRUNCATED]", |
674 | 0 | vb->type, vb->type, buf)); |
675 | 0 | } else { |
676 | 0 | DEBUGMSG(("helper:table:results", |
677 | 0 | " index: type=%d(%02x), value=[NIL] [TRUNCATED]", |
678 | 0 | vb->type, vb->type)); |
679 | 0 | } |
680 | 0 | } |
681 | 0 | } |
682 | 0 | if (buf != NULL) { |
683 | 0 | free(buf); |
684 | 0 | } |
685 | 0 | DEBUGMSG(("helper:table:results", "\n")); |
686 | 0 | } |
687 | | |
688 | | |
689 | | /* |
690 | | * do we have sufficient index info to continue? |
691 | | */ |
692 | |
|
693 | 0 | if ((reqinfo->mode != MODE_GETNEXT) && |
694 | 0 | ((tbl_req_info->number_indexes != tbl_info->number_indexes) || |
695 | 0 | (tmp_len != -1))) { |
696 | |
|
697 | 0 | DEBUGMSGTL(("helper:table", |
698 | 0 | "invalid index(es) for table - skipping\n")); |
699 | |
|
700 | 0 | #ifndef NETSNMP_NO_WRITE_SUPPORT |
701 | 0 | if ( MODE_IS_SET(reqinfo->mode) ) { |
702 | | /* |
703 | | * no point in continuing without indexes for set. |
704 | | */ |
705 | 0 | netsnmp_assert(reqinfo->mode == MODE_SET_RESERVE1); |
706 | | /** clear first request so we wont try to run FREE mode */ |
707 | 0 | netsnmp_free_request_data_sets(requests); |
708 | | /** set actual error */ |
709 | 0 | table_helper_cleanup(reqinfo, request, SNMP_ERR_NOCREATION); |
710 | 0 | need_processing = 0; /* don't call next handler */ |
711 | 0 | break; |
712 | 0 | } |
713 | 0 | #endif /* NETSNMP_NO_WRITE_SUPPORT */ |
714 | 0 | table_helper_cleanup(reqinfo, request, SNMP_NOSUCHINSTANCE); |
715 | 0 | continue; |
716 | 0 | } |
717 | 0 | netsnmp_assert(request->status == SNMP_ERR_NOERROR); |
718 | | |
719 | 0 | ++need_processing; |
720 | |
|
721 | 0 | } /* for each request */ |
722 | 0 | #ifndef NETSNMP_NO_WRITE_SUPPORT |
723 | 0 | } |
724 | 0 | #endif /* NETSNMP_NO_WRITE_SUPPORT */ |
725 | | |
726 | | /* |
727 | | * bail if there is nothing for our child handlers |
728 | | */ |
729 | 0 | if (0 == need_processing) |
730 | 0 | return status; |
731 | | |
732 | | /* |
733 | | * call our child access function |
734 | | */ |
735 | 0 | status = |
736 | 0 | netsnmp_call_next_handler(handler, reginfo, reqinfo, requests); |
737 | | |
738 | | /* |
739 | | * check for sparse tables |
740 | | */ |
741 | 0 | if (reqinfo->mode == MODE_GETNEXT) |
742 | 0 | sparse_table_helper_handler( handler, reginfo, reqinfo, requests ); |
743 | |
|
744 | 0 | return status; |
745 | 0 | } |
746 | | |
747 | 0 | #define SPARSE_TABLE_HANDLER_NAME "sparse_table" |
748 | | |
749 | | /** implements the sparse table helper handler |
750 | | * @internal |
751 | | * |
752 | | * @note |
753 | | * This function is static to prevent others from calling it |
754 | | * directly. It it automatically called by the table helper, |
755 | | * |
756 | | */ |
757 | | static int |
758 | | sparse_table_helper_handler(netsnmp_mib_handler *handler, |
759 | | netsnmp_handler_registration *reginfo, |
760 | | netsnmp_agent_request_info *reqinfo, |
761 | | netsnmp_request_info *requests) |
762 | 0 | { |
763 | 0 | int status = SNMP_ERR_NOERROR; |
764 | 0 | netsnmp_request_info *request; |
765 | 0 | oid coloid[MAX_OID_LEN]; |
766 | 0 | netsnmp_table_request_info *table_info; |
767 | | |
768 | | /* |
769 | | * since we don't call child handlers, warn if one was registered |
770 | | * beneath us. A special exception for the table helper, which calls |
771 | | * the handler directly. Use handle custom flag to only log once. |
772 | | */ |
773 | 0 | if((table_helper_handler != handler->access_method) && |
774 | 0 | (NULL != handler->next)) { |
775 | | /* |
776 | | * always warn if called without our own handler. If we |
777 | | * have our own handler, use custom bit 1 to only log once. |
778 | | */ |
779 | 0 | if((sparse_table_helper_handler != handler->access_method) || |
780 | 0 | !(handler->flags & MIB_HANDLER_CUSTOM1)) { |
781 | 0 | snmp_log(LOG_WARNING, "handler (%s) registered after sparse table " |
782 | 0 | "handler will not be called\n", |
783 | 0 | handler->next->handler_name ? |
784 | 0 | handler->next->handler_name : "" ); |
785 | 0 | if(sparse_table_helper_handler == handler->access_method) |
786 | 0 | handler->flags |= MIB_HANDLER_CUSTOM1; |
787 | 0 | } |
788 | 0 | } |
789 | |
|
790 | 0 | if (reqinfo->mode == MODE_GETNEXT) { |
791 | 0 | for(request = requests ; request; request = request->next) { |
792 | 0 | if ((request->requestvb->type == ASN_NULL && request->processed) || |
793 | 0 | request->delegated) |
794 | 0 | continue; |
795 | 0 | if (request->requestvb->type == SNMP_NOSUCHINSTANCE) { |
796 | | /* |
797 | | * get next skipped this value for this column, we |
798 | | * need to keep searching forward |
799 | | */ |
800 | 0 | DEBUGMSGT(("sparse", "retry for NOSUCHINSTANCE\n")); |
801 | 0 | request->requestvb->type = ASN_PRIV_RETRY; |
802 | 0 | } |
803 | 0 | if (request->requestvb->type == SNMP_NOSUCHOBJECT || |
804 | 0 | request->requestvb->type == SNMP_ENDOFMIBVIEW) { |
805 | | /* |
806 | | * get next has completely finished with this column, |
807 | | * so we need to try with the next column (if any) |
808 | | */ |
809 | 0 | DEBUGMSGT(("sparse", "retry for NOSUCHOBJECT\n")); |
810 | 0 | table_info = netsnmp_extract_table_info(request); |
811 | 0 | table_info->colnum = netsnmp_table_next_column(table_info); |
812 | 0 | if (0 != table_info->colnum) { |
813 | 0 | memcpy(coloid, reginfo->rootoid, |
814 | 0 | reginfo->rootoid_len * sizeof(oid)); |
815 | 0 | coloid[reginfo->rootoid_len] = 1; /* table.entry node */ |
816 | 0 | coloid[reginfo->rootoid_len+1] = table_info->colnum; |
817 | 0 | snmp_set_var_objid(request->requestvb, |
818 | 0 | coloid, reginfo->rootoid_len + 2); |
819 | | |
820 | 0 | request->requestvb->type = ASN_PRIV_RETRY; |
821 | 0 | } |
822 | 0 | else { |
823 | | /* |
824 | | * If we don't have column info, reset to null so |
825 | | * the agent will move on to the next table. |
826 | | */ |
827 | 0 | request->requestvb->type = ASN_NULL; |
828 | 0 | } |
829 | 0 | } |
830 | 0 | } |
831 | 0 | } |
832 | 0 | return status; |
833 | 0 | } |
834 | | |
835 | | /** create sparse table handler |
836 | | */ |
837 | | #ifndef NETSNMP_FEATURE_REMOVE_TABLE_SPARSE |
838 | | netsnmp_mib_handler * |
839 | | netsnmp_sparse_table_handler_get(void) |
840 | 0 | { |
841 | 0 | return netsnmp_create_handler(SPARSE_TABLE_HANDLER_NAME, |
842 | 0 | sparse_table_helper_handler); |
843 | 0 | } |
844 | | |
845 | | /** creates a table handler given the netsnmp_table_registration_info object, |
846 | | * inserts it into the request chain and then calls |
847 | | * netsnmp_register_handler() to register the table into the agent. |
848 | | */ |
849 | | int |
850 | | netsnmp_sparse_table_register(netsnmp_handler_registration *reginfo, |
851 | | netsnmp_table_registration_info *tabreq) |
852 | 0 | { |
853 | 0 | netsnmp_mib_handler *handler1, *handler2; |
854 | |
|
855 | 0 | handler1 = netsnmp_create_handler(SPARSE_TABLE_HANDLER_NAME, |
856 | 0 | sparse_table_helper_handler); |
857 | 0 | if (NULL == handler1) |
858 | 0 | return MIB_REGISTRATION_FAILED; |
859 | | |
860 | 0 | handler2 = netsnmp_get_table_handler(tabreq); |
861 | 0 | if (NULL == handler2 ) { |
862 | 0 | netsnmp_handler_free(handler1); |
863 | 0 | return SNMP_ERR_GENERR; |
864 | 0 | } |
865 | | |
866 | 0 | if (SNMPERR_SUCCESS != netsnmp_inject_handler(reginfo, handler1)) { |
867 | 0 | netsnmp_handler_free(handler1); |
868 | 0 | netsnmp_handler_free(handler2); |
869 | 0 | return MIB_REGISTRATION_FAILED; |
870 | 0 | } |
871 | | |
872 | 0 | if (SNMPERR_SUCCESS != netsnmp_inject_handler(reginfo, handler2)) { |
873 | | /** handler1 is in reginfo... remove and free?? */ |
874 | 0 | netsnmp_handler_free(handler2); |
875 | 0 | return MIB_REGISTRATION_FAILED; |
876 | 0 | } |
877 | | |
878 | | /** both handlers now in reginfo, so nothing to do on error */ |
879 | 0 | return netsnmp_register_handler(reginfo); |
880 | 0 | } |
881 | | #endif /* NETSNMP_FEATURE_REMOVE_TABLE_SPARSE */ |
882 | | |
883 | | |
884 | | #ifndef NETSNMP_FEATURE_REMOVE_TABLE_BUILD_RESULT |
885 | | /** Builds the result to be returned to the agent given the table information. |
886 | | * Use this function to return results from lower level handlers to |
887 | | * the agent. It takes care of building the proper resulting oid |
888 | | * (containing proper indexing) and inserts the result value into the |
889 | | * returning varbind. |
890 | | */ |
891 | | int |
892 | | netsnmp_table_build_result(netsnmp_handler_registration *reginfo, |
893 | | netsnmp_request_info *reqinfo, |
894 | | netsnmp_table_request_info *table_info, |
895 | | u_char type, u_char * result, size_t result_len) |
896 | 0 | { |
897 | |
|
898 | 0 | netsnmp_variable_list *var; |
899 | |
|
900 | 0 | if (!reqinfo || !table_info) |
901 | 0 | return SNMPERR_GENERR; |
902 | | |
903 | 0 | var = reqinfo->requestvb; |
904 | |
|
905 | 0 | if (var->name != var->name_loc) |
906 | 0 | free(var->name); |
907 | 0 | var->name = NULL; |
908 | |
|
909 | 0 | if (netsnmp_table_build_oid(reginfo, reqinfo, table_info) != |
910 | 0 | SNMPERR_SUCCESS) |
911 | 0 | return SNMPERR_GENERR; |
912 | | |
913 | 0 | snmp_set_var_typed_value(var, type, result, result_len); |
914 | |
|
915 | 0 | return SNMPERR_SUCCESS; |
916 | 0 | } |
917 | | |
918 | | /** given a registration info object, a request object and the table |
919 | | * info object it builds the request->requestvb->name oid from the |
920 | | * index values and column information found in the table_info |
921 | | * object. Index values are extracted from the table_info varbinds. |
922 | | */ |
923 | | int |
924 | | netsnmp_table_build_oid(netsnmp_handler_registration *reginfo, |
925 | | netsnmp_request_info *reqinfo, |
926 | | netsnmp_table_request_info *table_info) |
927 | 0 | { |
928 | 0 | oid tmpoid[MAX_OID_LEN]; |
929 | 0 | netsnmp_variable_list *var; |
930 | |
|
931 | 0 | if (!reginfo || !reqinfo || !table_info) |
932 | 0 | return SNMPERR_GENERR; |
933 | | |
934 | | /* |
935 | | * xxx-rks: inefficient. we do a copy here, then build_oid does it |
936 | | * again. either come up with a new utility routine, or |
937 | | * do some hijinks here to eliminate extra copy. |
938 | | * Probably could make sure all callers have the |
939 | | * index & variable list updated, and use |
940 | | * netsnmp_table_build_oid_from_index() instead of all this. |
941 | | */ |
942 | 0 | memcpy(tmpoid, reginfo->rootoid, reginfo->rootoid_len * sizeof(oid)); |
943 | 0 | tmpoid[reginfo->rootoid_len] = 1; /** .Entry */ |
944 | 0 | tmpoid[reginfo->rootoid_len + 1] = table_info->colnum; /** .column */ |
945 | |
|
946 | 0 | var = reqinfo->requestvb; |
947 | 0 | if (build_oid(&var->name, &var->name_length, |
948 | 0 | tmpoid, reginfo->rootoid_len + 2, table_info->indexes) |
949 | 0 | != SNMPERR_SUCCESS) |
950 | 0 | return SNMPERR_GENERR; |
951 | | |
952 | 0 | return SNMPERR_SUCCESS; |
953 | 0 | } |
954 | | #endif /* NETSNMP_FEATURE_REMOVE_TABLE_BUILD_RESULT */ |
955 | | |
956 | | /** given a registration info object, a request object and the table |
957 | | * info object it builds the request->requestvb->name oid from the |
958 | | * index values and column information found in the table_info |
959 | | * object. Index values are extracted from the table_info index oid. |
960 | | */ |
961 | | int |
962 | | netsnmp_table_build_oid_from_index(netsnmp_handler_registration *reginfo, |
963 | | netsnmp_request_info *reqinfo, |
964 | | netsnmp_table_request_info *table_info) |
965 | 0 | { |
966 | 0 | oid tmpoid[MAX_OID_LEN]; |
967 | 0 | netsnmp_variable_list *var; |
968 | 0 | int len; |
969 | |
|
970 | 0 | if (!reginfo || !reqinfo || !table_info) |
971 | 0 | return SNMPERR_GENERR; |
972 | | |
973 | 0 | var = reqinfo->requestvb; |
974 | 0 | len = reginfo->rootoid_len; |
975 | 0 | memcpy(tmpoid, reginfo->rootoid, len * sizeof(oid)); |
976 | 0 | tmpoid[len++] = 1; /* .Entry */ |
977 | 0 | tmpoid[len++] = table_info->colnum; /* .column */ |
978 | 0 | memcpy(&tmpoid[len], table_info->index_oid, |
979 | 0 | table_info->index_oid_len * sizeof(oid)); |
980 | 0 | len += table_info->index_oid_len; |
981 | 0 | snmp_set_var_objid( var, tmpoid, len ); |
982 | |
|
983 | 0 | return SNMPERR_SUCCESS; |
984 | 0 | } |
985 | | |
986 | | /** parses an OID into table indexes */ |
987 | | int |
988 | | netsnmp_update_variable_list_from_index(netsnmp_table_request_info *tri) |
989 | 0 | { |
990 | 0 | if (!tri) |
991 | 0 | return SNMPERR_GENERR; |
992 | | |
993 | | /* |
994 | | * free any existing allocated memory, then parse oid into varbinds |
995 | | */ |
996 | 0 | snmp_reset_var_buffers( tri->indexes); |
997 | |
|
998 | 0 | return parse_oid_indexes(tri->index_oid, tri->index_oid_len, |
999 | 0 | tri->indexes); |
1000 | 0 | } |
1001 | | |
1002 | | /** builds an oid given a set of indexes. */ |
1003 | | int |
1004 | | netsnmp_update_indexes_from_variable_list(netsnmp_table_request_info *tri) |
1005 | 0 | { |
1006 | 0 | if (!tri) |
1007 | 0 | return SNMPERR_GENERR; |
1008 | | |
1009 | 0 | return build_oid_noalloc(tri->index_oid, |
1010 | 0 | sizeof(tri->index_oid) / sizeof(tri->index_oid[0]), |
1011 | 0 | &tri->index_oid_len, NULL, 0, tri->indexes); |
1012 | 0 | } |
1013 | | |
1014 | | /** |
1015 | | * checks the original request against the current data being passed in if |
1016 | | * its greater than the request oid but less than the current valid |
1017 | | * return, set the current valid return to the new value. |
1018 | | * |
1019 | | * returns 1 if outvar was replaced with the oid from newvar (success). |
1020 | | * returns 0 if not. |
1021 | | */ |
1022 | | int |
1023 | | netsnmp_check_getnext_reply(netsnmp_request_info *request, |
1024 | | oid * prefix, |
1025 | | size_t prefix_len, |
1026 | | netsnmp_variable_list * newvar, |
1027 | | netsnmp_variable_list ** outvar) |
1028 | 0 | { |
1029 | 0 | oid myname[MAX_OID_LEN]; |
1030 | 0 | size_t myname_len; |
1031 | |
|
1032 | 0 | build_oid_noalloc(myname, MAX_OID_LEN, &myname_len, |
1033 | 0 | prefix, prefix_len, newvar); |
1034 | | /* |
1035 | | * is the build of the new indexes less than our current result |
1036 | | */ |
1037 | 0 | if ((!(*outvar) || snmp_oid_compare(myname + prefix_len, |
1038 | 0 | myname_len - prefix_len, |
1039 | 0 | (*outvar)->name + prefix_len, |
1040 | 0 | (*outvar)->name_length - |
1041 | 0 | prefix_len) < 0)) { |
1042 | | /* |
1043 | | * and greater than the requested oid |
1044 | | */ |
1045 | 0 | if (snmp_oid_compare(myname, myname_len, |
1046 | 0 | request->requestvb->name, |
1047 | 0 | request->requestvb->name_length) > 0) { |
1048 | | /* |
1049 | | * the new result must be better than the old |
1050 | | */ |
1051 | | #ifdef ONLY_WORKS_WITH_ONE_VARBIND |
1052 | | if (!*outvar) |
1053 | | *outvar = snmp_clone_varbind(newvar); |
1054 | | else |
1055 | | /* |
1056 | | * TODO: walk the full varbind list, setting |
1057 | | * *all* the values - not just the first. |
1058 | | */ |
1059 | | snmp_set_var_typed_value(*outvar, newvar->type, |
1060 | | newvar->val.string, newvar->val_len); |
1061 | | #else /* Interim replacement approach - less efficient, but it works! */ |
1062 | 0 | if (*outvar) |
1063 | 0 | snmp_free_varbind(*outvar); |
1064 | 0 | *outvar = snmp_clone_varbind(newvar); |
1065 | 0 | #endif |
1066 | 0 | snmp_set_var_objid(*outvar, myname, myname_len); |
1067 | |
|
1068 | 0 | return 1; |
1069 | 0 | } |
1070 | 0 | } |
1071 | 0 | return 0; |
1072 | 0 | } |
1073 | | |
1074 | | netsnmp_table_registration_info * |
1075 | | netsnmp_table_registration_info_clone(netsnmp_table_registration_info *tri) |
1076 | 0 | { |
1077 | 0 | netsnmp_table_registration_info *copy; |
1078 | 0 | copy = malloc(sizeof(*copy)); |
1079 | 0 | if (copy) { |
1080 | 0 | *copy = *tri; |
1081 | 0 | copy->indexes = snmp_clone_varbind(tri->indexes); |
1082 | 0 | if (!copy->indexes) { |
1083 | 0 | free(copy); |
1084 | 0 | copy = NULL; |
1085 | 0 | } |
1086 | 0 | } |
1087 | 0 | return copy; |
1088 | 0 | } |
1089 | | |
1090 | | void |
1091 | | netsnmp_table_registration_info_free(netsnmp_table_registration_info *tri) |
1092 | 0 | { |
1093 | 0 | if (NULL == tri) |
1094 | 0 | return; |
1095 | | |
1096 | 0 | if (NULL != tri->indexes) |
1097 | 0 | snmp_free_varbind(tri->indexes); |
1098 | |
|
1099 | | #if 0 |
1100 | | /* |
1101 | | * sigh... example use of valid_columns points to static memory, |
1102 | | * so freeing it would be bad... we'll just have to live with any |
1103 | | * leaks, for now... |
1104 | | */ |
1105 | | #endif |
1106 | |
|
1107 | 0 | free(tri); |
1108 | 0 | } |
1109 | | |
1110 | | /** @} */ |
1111 | | |
1112 | | /* |
1113 | | * internal routines |
1114 | | */ |
1115 | | void |
1116 | | table_data_free_func(void *data) |
1117 | 0 | { |
1118 | 0 | netsnmp_table_request_info *info = (netsnmp_table_request_info *) data; |
1119 | 0 | if (!info) |
1120 | 0 | return; |
1121 | 0 | snmp_free_varbind(info->indexes); |
1122 | 0 | free(info); |
1123 | 0 | } |
1124 | | |
1125 | | |
1126 | | |
1127 | | static void |
1128 | | table_helper_cleanup(netsnmp_agent_request_info *reqinfo, |
1129 | | netsnmp_request_info *request, int status) |
1130 | 0 | { |
1131 | 0 | netsnmp_set_request_error(reqinfo, request, status); |
1132 | 0 | netsnmp_free_request_data_sets(request); |
1133 | 0 | if (!request) |
1134 | 0 | return; |
1135 | 0 | request->parent_data = NULL; |
1136 | 0 | } |
1137 | | |
1138 | | |
1139 | | /* |
1140 | | * find the closest column to current (which may be current). |
1141 | | * |
1142 | | * called when a table runs out of rows for column X. This |
1143 | | * function is called with current = X + 1, to verify that |
1144 | | * X + 1 is a valid column, or find the next closest column if not. |
1145 | | * |
1146 | | * All list types should be sorted, lowest to highest. |
1147 | | */ |
1148 | | unsigned int |
1149 | | netsnmp_closest_column(unsigned int current, |
1150 | | netsnmp_column_info *valid_columns) |
1151 | 0 | { |
1152 | 0 | unsigned int closest = 0; |
1153 | 0 | int idx; |
1154 | |
|
1155 | 0 | if (valid_columns == NULL) |
1156 | 0 | return 0; |
1157 | | |
1158 | 0 | for( ; valid_columns; valid_columns = valid_columns->next) { |
1159 | |
|
1160 | 0 | if (valid_columns->isRange) { |
1161 | | /* |
1162 | | * if current < low range, it might be closest. |
1163 | | * otherwise, if it's < high range, current is in |
1164 | | * the range, and thus is an exact match. |
1165 | | */ |
1166 | 0 | if (current < valid_columns->details.range[0]) { |
1167 | 0 | if ( (valid_columns->details.range[0] < closest) || |
1168 | 0 | (0 == closest)) { |
1169 | 0 | closest = valid_columns->details.range[0]; |
1170 | 0 | } |
1171 | 0 | } else if (current <= valid_columns->details.range[1]) { |
1172 | 0 | closest = current; |
1173 | 0 | break; /* can not get any closer! */ |
1174 | 0 | } |
1175 | |
|
1176 | 0 | } /* range */ |
1177 | 0 | else { /* list */ |
1178 | | /* |
1179 | | * if current < first item, no need to iterate over list. |
1180 | | * that item is either closest, or not. |
1181 | | */ |
1182 | 0 | if (current < valid_columns->details.list[0]) { |
1183 | 0 | if ((valid_columns->details.list[0] < closest) || |
1184 | 0 | (0 == closest)) |
1185 | 0 | closest = valid_columns->details.list[0]; |
1186 | 0 | continue; |
1187 | 0 | } |
1188 | | |
1189 | | /** if current > last item in list, no need to iterate */ |
1190 | 0 | if (current > |
1191 | 0 | valid_columns->details.list[(int)valid_columns->list_count - 1]) |
1192 | 0 | continue; /* not in list range. */ |
1193 | | |
1194 | | /** skip anything less than current*/ |
1195 | 0 | for (idx = 0; valid_columns->details.list[idx] < current; ++idx) |
1196 | 0 | ; |
1197 | | |
1198 | | /** check for exact match */ |
1199 | 0 | if (current == valid_columns->details.list[idx]) { |
1200 | 0 | closest = current; |
1201 | 0 | break; /* can not get any closer! */ |
1202 | 0 | } |
1203 | | |
1204 | | /** list[idx] > current; is it < closest? */ |
1205 | 0 | if ((valid_columns->details.list[idx] < closest) || |
1206 | 0 | (0 == closest)) |
1207 | 0 | closest = valid_columns->details.list[idx]; |
1208 | |
|
1209 | 0 | } /* list */ |
1210 | 0 | } /* for */ |
1211 | |
|
1212 | 0 | return closest; |
1213 | 0 | } |
1214 | | |
1215 | | /** |
1216 | | * This function can be used to setup the table's definition within |
1217 | | * your module's initialize function, it takes a variable index parameter list |
1218 | | * for example: the table_info structure is followed by two integer index types |
1219 | | * netsnmp_table_helper_add_indexes( |
1220 | | * table_info, |
1221 | | * ASN_INTEGER, |
1222 | | * ASN_INTEGER, |
1223 | | * 0); |
1224 | | * |
1225 | | * @param tinfo is a pointer to a netsnmp_table_registration_info struct. |
1226 | | * The table handler needs to know up front how your table is structured. |
1227 | | * A netsnmp_table_registration_info structure that is |
1228 | | * passed to the table handler should contain the asn index types for the |
1229 | | * table as well as the minimum and maximum column that should be used. |
1230 | | * |
1231 | | * @return void |
1232 | | * |
1233 | | */ |
1234 | | void |
1235 | | netsnmp_table_helper_add_indexes(netsnmp_table_registration_info *tinfo, |
1236 | | ...) |
1237 | 0 | { |
1238 | 0 | va_list debugargs; |
1239 | 0 | int type; |
1240 | |
|
1241 | 0 | va_start(debugargs, tinfo); |
1242 | 0 | while ((type = va_arg(debugargs, int)) != 0) { |
1243 | 0 | netsnmp_table_helper_add_index(tinfo, type); |
1244 | 0 | } |
1245 | 0 | va_end(debugargs); |
1246 | 0 | } |
1247 | | |
1248 | | #ifndef NETSNMP_NO_WRITE_SUPPORT |
1249 | | #ifndef NETSNMP_FEATURE_REMOVE_TABLE_GET_OR_CREATE_ROW_STASH |
1250 | | static void |
1251 | 0 | _row_stash_data_list_free(void *ptr) { |
1252 | 0 | netsnmp_oid_stash_node **tmp = (netsnmp_oid_stash_node **)ptr; |
1253 | 0 | netsnmp_oid_stash_free(tmp, NULL); |
1254 | 0 | free(ptr); |
1255 | 0 | } |
1256 | | |
1257 | | /** returns a row-wide place to store data in. |
1258 | | @todo This function will likely change to add free pointer functions. */ |
1259 | | netsnmp_oid_stash_node ** |
1260 | | netsnmp_table_get_or_create_row_stash(netsnmp_agent_request_info *reqinfo, |
1261 | | const u_char * storage_name) |
1262 | 0 | { |
1263 | 0 | netsnmp_oid_stash_node **stashp = NULL; |
1264 | 0 | stashp = (netsnmp_oid_stash_node **) |
1265 | 0 | netsnmp_agent_get_list_data(reqinfo, (const char *) storage_name); |
1266 | |
|
1267 | 0 | if (!stashp) { |
1268 | | /* |
1269 | | * hasn't be created yet. we create it here. |
1270 | | */ |
1271 | 0 | stashp = SNMP_MALLOC_TYPEDEF(netsnmp_oid_stash_node *); |
1272 | |
|
1273 | 0 | if (!stashp) |
1274 | 0 | return NULL; /* ack. out of mem */ |
1275 | | |
1276 | 0 | netsnmp_agent_add_list_data(reqinfo, |
1277 | 0 | netsnmp_create_data_list((const char *) storage_name, |
1278 | 0 | stashp, |
1279 | 0 | _row_stash_data_list_free)); |
1280 | 0 | } |
1281 | 0 | return stashp; |
1282 | 0 | } |
1283 | | #endif /* NETSNMP_FEATURE_REMOVE_TABLE_GET_OR_CREATE_ROW_STASH */ |
1284 | | #endif /* NETSNMP_NO_WRITE_SUPPORT */ |
1285 | | |
1286 | | /* |
1287 | | * advance the table info colnum to the next column, or 0 if there are no more |
1288 | | * |
1289 | | * @return new column, or 0 if there are no more |
1290 | | */ |
1291 | | unsigned int |
1292 | | netsnmp_table_next_column(netsnmp_table_request_info *table_info) |
1293 | 0 | { |
1294 | 0 | if (NULL == table_info) |
1295 | 0 | return 0; |
1296 | | |
1297 | | /* |
1298 | | * try and validate next column |
1299 | | */ |
1300 | 0 | if (table_info->reg_info->valid_columns) |
1301 | 0 | return netsnmp_closest_column(table_info->colnum + 1, |
1302 | 0 | table_info->reg_info->valid_columns); |
1303 | | |
1304 | | /* |
1305 | | * can't validate. assume 1..max_column are valid |
1306 | | */ |
1307 | 0 | if (table_info->colnum < table_info->reg_info->max_column) |
1308 | 0 | return table_info->colnum + 1; |
1309 | | |
1310 | 0 | return 0; /* out of range */ |
1311 | 0 | } |