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