1 /* $NetBSD: ntpSnmpSubagentObject.c,v 1.5 2020/05/25 20:47:26 christos Exp $ */
2
3 /*****************************************************************************
4 *
5 * ntpSnmpSubAgentObject.c
6 *
7 * This file provides the callback functions for net-snmp and registers the
8 * serviced MIB objects with the master agent.
9 *
10 * Each object has its own callback function that is called by the
11 * master agent process whenever someone queries the corresponding MIB
12 * object.
13 *
14 * At the moment this triggers a full send/receive procedure for each
15 * queried MIB object, one of the things that are still on my todo list:
16 * a caching mechanism that reduces the number of requests sent to the
17 * ntpd process.
18 *
19 ****************************************************************************/
20 #include <ntp_snmp.h>
21 #include <ctype.h>
22 #include <ntp.h>
23 #include <libntpq.h>
24
25 /* general purpose buffer length definition */
26 #define NTPQ_BUFLEN 2048
27
28 char ntpvalue[NTPQ_BUFLEN];
29
30
31 /*****************************************************************************
32 *
33 * ntpsnmpd_parse_string
34 *
35 * This function will parse a given NULL terminated string and cut it
36 * into a fieldname and a value part (using the '=' as the delimiter.
37 * The fieldname will be converted to uppercase and all whitespace
38 * characters are removed from it.
39 * The value part is stripped, e.g. all whitespace characters are removed
40 * from the beginning and end of the string.
41 * If the value is started and ended with quotes ("), they will be removed
42 * and everything between the quotes is left untouched (including
43 * whitespace)
44 * Example:
45 * server host name = hello world!
46 * will result in a field string "SERVERHOSTNAME" and a value
47 * of "hello world!".
48 * My first Parameter = " is this! "
49 * results in a field string "MYFIRSTPARAMETER" and a value " is this! "
50 ****************************************************************************
51 * Parameters:
52 * string const char * The source string to parse.
53 * NOTE: must be NULL terminated!
54 * field char * The buffer for the field name.
55 * fieldsize size_t The size of the field buffer.
56 * value char * The buffer for the value.
57 * valuesize size_t The size of the value buffer.
58 *
59 * Returns:
60 * size_t length of value string
61 ****************************************************************************/
62
63 size_t
ntpsnmpd_parse_string(const char * string,char * field,size_t fieldsize,char * value,size_t valuesize)64 ntpsnmpd_parse_string(
65 const char * string,
66 char * field,
67 size_t fieldsize,
68 char * value,
69 size_t valuesize
70 )
71 {
72 int i;
73 int j;
74 int loop;
75 size_t str_cnt;
76 size_t val_cnt;
77
78 /* we need at least one byte to work with to simplify */
79 if (fieldsize < 1 || valuesize < 1)
80 return 0;
81
82 str_cnt = strlen(string);
83
84 /* Parsing the field name */
85 j = 0;
86 loop = TRUE;
87 for (i = 0; loop && i <= str_cnt; i++) {
88 switch (string[i]) {
89
90 case '\t': /* Tab */
91 case '\n': /* LF */
92 case '\r': /* CR */
93 case ' ': /* Space */
94 break;
95
96 case '=':
97 loop = FALSE;
98 break;
99
100 default:
101 if (j < fieldsize)
102 field[j++] = toupper(string[i]);
103 }
104 }
105
106 j = min(j, fieldsize - 1);
107 field[j] = '\0';
108
109 /* Now parsing the value */
110 value[0] = '\0';
111 j = 0;
112 for (val_cnt = 0; i < str_cnt; i++) {
113 if (string[i] > 0x0D && string[i] != ' ')
114 val_cnt = min(j + 1, valuesize - 1);
115
116 if (value[0] != '\0' ||
117 (string[i] > 0x0D && string[i] != ' ')) {
118 if (j < valuesize)
119 value[j++] = string[i];
120 }
121 }
122 value[val_cnt] = '\0';
123
124 if (value[0] == '"') {
125 val_cnt--;
126 strlcpy(value, &value[1], valuesize);
127 if (val_cnt > 0 && value[val_cnt - 1] == '"') {
128 val_cnt--;
129 value[val_cnt] = '\0';
130 }
131 }
132
133 return val_cnt;
134 }
135
136
137 /*****************************************************************************
138 *
139 * ntpsnmpd_cut_string
140 *
141 * This function will parse a given NULL terminated string and cut it
142 * into fields using the specified delimiter character.
143 * It will then copy the requested field into a destination buffer
144 * Example:
145 * ntpsnmpd_cut_string(read:my:lips:fool, RESULT, ':', 2, sizeof(RESULT))
146 * will copy "lips" to RESULT.
147 ****************************************************************************
148 * Parameters:
149 * src const char * The name of the source string variable
150 * NOTE: must be NULL terminated!
151 * dest char * The name of the string which takes the
152 * requested field content
153 * delim char The delimiter character
154 * fieldnumber int The number of the required field
155 * (start counting with 0)
156 * maxsize size_t The maximum size of dest
157 *
158 * Returns:
159 * size_t length of resulting dest string
160 ****************************************************************************/
161
162 size_t
ntpsnmpd_cut_string(const char * string,char * dest,char delim,int fieldnumber,size_t maxsize)163 ntpsnmpd_cut_string(
164 const char * string,
165 char * dest,
166 char delim,
167 int fieldnumber,
168 size_t maxsize
169 )
170 {
171 size_t i;
172 size_t j;
173 int l;
174 size_t str_cnt;
175
176 if (maxsize < 1)
177 return 0;
178
179 str_cnt = strlen(string);
180 j = 0;
181 memset(dest, 0, maxsize);
182
183 /* Parsing the field name */
184 for (i = 0, l = 0; i < str_cnt && l <= fieldnumber; i++) {
185 if (string[i] == delim)
186 l++; /* next field */
187 else if (l == fieldnumber && j < maxsize)
188 dest[j++] = string[i];
189 }
190 j = min(j, maxsize - 1);
191 dest[j] = '\0';
192
193 return j;
194 }
195
196
197 /*****************************************************************************
198 *
199 * read_ntp_value
200 *
201 * This function retrieves the value for a given variable, currently
202 * this only supports sysvars. It starts a full mode 6 send/receive/parse
203 * iteration and needs to be optimized, e.g. by using a caching mechanism
204 *
205 ****************************************************************************
206 * Parameters:
207 * variable char* The name of the required variable
208 * rbuffer char* The buffer where the value goes
209 * maxlength int Max. number of bytes for resultbuf
210 *
211 * Returns:
212 * u_int number of chars that have been copied to
213 * rbuffer
214 ****************************************************************************/
215
216 size_t
read_ntp_value(const char * variable,char * value,size_t valuesize)217 read_ntp_value(
218 const char * variable,
219 char * value,
220 size_t valuesize
221 )
222 {
223 size_t sv_len;
224 char sv_data[NTPQ_BUFLEN];
225
226 memset(sv_data, 0, sizeof(sv_data));
227 sv_len = ntpq_read_sysvars(sv_data, sizeof(sv_data));
228
229 if (0 == sv_len)
230 return 0;
231 else
232 return ntpq_getvar(sv_data, sv_len, variable, value,
233 valuesize);
234 }
235
236
237 /*****************************************************************************
238 *
239 * The get_xxx functions
240 *
241 * The following function calls are callback functions that will be
242 * used by the master agent process to retrieve a value for a requested
243 * MIB object.
244 *
245 ****************************************************************************/
246
247
get_ntpEntSoftwareName(netsnmp_mib_handler * handler,netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo,netsnmp_request_info * requests)248 int get_ntpEntSoftwareName (netsnmp_mib_handler *handler,
249 netsnmp_handler_registration *reginfo,
250 netsnmp_agent_request_info *reqinfo,
251 netsnmp_request_info *requests)
252 {
253 char ntp_softwarename[NTPQ_BUFLEN];
254
255 memset (ntp_softwarename, 0, NTPQ_BUFLEN);
256
257 switch (reqinfo->mode) {
258 case MODE_GET:
259 {
260 if ( read_ntp_value("product", ntpvalue, NTPQ_BUFLEN) )
261 {
262 snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
263 (u_char *)ntpvalue,
264 strlen(ntpvalue)
265 );
266 }
267 else if ( read_ntp_value("version", ntpvalue, NTPQ_BUFLEN) )
268 {
269 ntpsnmpd_cut_string(ntpvalue, ntp_softwarename, ' ', 0, sizeof(ntp_softwarename)-1);
270 snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
271 (u_char *)ntp_softwarename,
272 strlen(ntp_softwarename)
273 );
274 } else {
275 snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
276 (u_char *)"N/A",
277 3
278 );
279 }
280 break;
281
282 }
283
284
285 default:
286 /* If we cannot get the information we need, we will return a generic error to the SNMP client */
287 return SNMP_ERR_GENERR;
288 }
289
290 return SNMP_ERR_NOERROR;
291 }
292
293
get_ntpEntSoftwareVersion(netsnmp_mib_handler * handler,netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo,netsnmp_request_info * requests)294 int get_ntpEntSoftwareVersion (netsnmp_mib_handler *handler,
295 netsnmp_handler_registration *reginfo,
296 netsnmp_agent_request_info *reqinfo,
297 netsnmp_request_info *requests)
298 {
299
300 switch (reqinfo->mode) {
301 case MODE_GET:
302 {
303
304 if ( read_ntp_value("version", ntpvalue, NTPQ_BUFLEN) )
305 {
306 snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
307 (u_char *)ntpvalue,
308 strlen(ntpvalue)
309 );
310 } else {
311 snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
312 (u_char *)"N/A",
313 3
314 );
315 }
316 break;
317
318 }
319
320
321 default:
322 /* If we cannot get the information we need, we will return a generic error to the SNMP client */
323 return SNMP_ERR_GENERR;
324 }
325
326 return SNMP_ERR_NOERROR;
327 }
328
329
get_ntpEntSoftwareVendor(netsnmp_mib_handler * handler,netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo,netsnmp_request_info * requests)330 int get_ntpEntSoftwareVendor (netsnmp_mib_handler *handler,
331 netsnmp_handler_registration *reginfo,
332 netsnmp_agent_request_info *reqinfo,
333 netsnmp_request_info *requests)
334 {
335
336 switch (reqinfo->mode) {
337 case MODE_GET:
338 {
339
340 if ( read_ntp_value("vendor", ntpvalue, NTPQ_BUFLEN) )
341 {
342 snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
343 (u_char *)ntpvalue,
344 strlen(ntpvalue)
345 );
346 } else {
347 snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
348 (u_char *)"N/A",
349 3
350 );
351 }
352 break;
353
354 default:
355 /* If we cannot get the information we need, we will return a generic error to the SNMP client */
356 return SNMP_ERR_GENERR;
357 }
358 }
359 return SNMP_ERR_NOERROR;
360 }
361
362
get_ntpEntSystemType(netsnmp_mib_handler * handler,netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo,netsnmp_request_info * requests)363 int get_ntpEntSystemType (netsnmp_mib_handler *handler,
364 netsnmp_handler_registration *reginfo,
365 netsnmp_agent_request_info *reqinfo,
366 netsnmp_request_info *requests)
367 {
368
369 switch (reqinfo->mode) {
370 case MODE_GET:
371 {
372
373 if ( read_ntp_value("systemtype", ntpvalue, NTPQ_BUFLEN) )
374 {
375 snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
376 (u_char *)ntpvalue,
377 strlen(ntpvalue)
378 );
379 }
380
381 if ( read_ntp_value("system", ntpvalue, NTPQ_BUFLEN) )
382 {
383 snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
384 (u_char *)ntpvalue,
385 strlen(ntpvalue)
386 );
387 } else {
388 snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
389 (u_char *)"N/A",
390 3
391 );
392 }
393 break;
394
395 }
396
397
398 default:
399 /* If we cannot get the information we need, we will return a generic error to the SNMP client */
400 return SNMP_ERR_GENERR;
401 }
402
403 return SNMP_ERR_NOERROR;
404 }
405
406
407 /*
408 * ntpEntTimeResolution
409 * "The time resolution in integer format, where the resolution
410 * is represented as divisions of a second, e.g., a value of 1000
411 * translates to 1.0 ms."
412 *
413 * ntpEntTimeResolution is a challenge for ntpd, as the resolution is
414 * not known nor exposed by ntpd, only the measured precision (time to
415 * read the clock).
416 *
417 * Logically the resolution must be at least the precision, so report
418 * it as our best approximation of resolution until/unless ntpd provides
419 * better.
420 */
421 int
get_ntpEntTimeResolution(netsnmp_mib_handler * handler,netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo,netsnmp_request_info * requests)422 get_ntpEntTimeResolution(
423 netsnmp_mib_handler * handler,
424 netsnmp_handler_registration * reginfo,
425 netsnmp_agent_request_info * reqinfo,
426 netsnmp_request_info * requests
427 )
428 {
429 int precision;
430 u_int32 resolution;
431
432 switch (reqinfo->mode) {
433
434 case MODE_GET:
435 if (!read_ntp_value("precision", ntpvalue,
436 sizeof(ntpvalue)))
437 return SNMP_ERR_GENERR;
438 if (1 != sscanf(ntpvalue, "%d", &precision))
439 return SNMP_ERR_GENERR;
440 if (precision >= 0)
441 return SNMP_ERR_GENERR;
442 precision = max(precision, -31);
443 resolution = 1 << -precision;
444 snmp_set_var_typed_value(
445 requests->requestvb,
446 ASN_UNSIGNED,
447 (void *)&resolution,
448 sizeof(resolution));
449 break;
450
451 default:
452 return SNMP_ERR_GENERR;
453 }
454
455 return SNMP_ERR_NOERROR;
456 }
457
458
459 /*
460 * ntpEntTimePrecision
461 * "The entity's precision in integer format, shows the precision.
462 * A value of -5 would mean 2^-5 = 31.25 ms."
463 */
464 int
get_ntpEntTimePrecision(netsnmp_mib_handler * handler,netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo,netsnmp_request_info * requests)465 get_ntpEntTimePrecision(
466 netsnmp_mib_handler * handler,
467 netsnmp_handler_registration * reginfo,
468 netsnmp_agent_request_info * reqinfo,
469 netsnmp_request_info * requests
470 )
471 {
472 int precision;
473 int32 precision32;
474
475 switch (reqinfo->mode) {
476
477 case MODE_GET:
478 if (!read_ntp_value("precision", ntpvalue,
479 sizeof(ntpvalue)))
480 return SNMP_ERR_GENERR;
481 if (1 != sscanf(ntpvalue, "%d", &precision))
482 return SNMP_ERR_GENERR;
483 precision32 = (int32)precision;
484 snmp_set_var_typed_value(
485 requests->requestvb,
486 ASN_INTEGER,
487 (void *)&precision32,
488 sizeof(precision32));
489 break;
490
491 default:
492 return SNMP_ERR_GENERR;
493 }
494
495 return SNMP_ERR_NOERROR;
496 }
497
498
get_ntpEntTimeDistance(netsnmp_mib_handler * handler,netsnmp_handler_registration * reginfo,netsnmp_agent_request_info * reqinfo,netsnmp_request_info * requests)499 int get_ntpEntTimeDistance (netsnmp_mib_handler *handler,
500 netsnmp_handler_registration *reginfo,
501 netsnmp_agent_request_info *reqinfo,
502 netsnmp_request_info *requests)
503 {
504 switch (reqinfo->mode) {
505 case MODE_GET:
506 {
507
508 if ( read_ntp_value("rootdelay", ntpvalue, NTPQ_BUFLEN) )
509 {
510 snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
511 (u_char *)ntpvalue,
512 strlen(ntpvalue)
513 );
514 } else {
515 snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
516 (u_char *)"N/A",
517 3
518 );
519 }
520 break;
521
522 }
523
524
525 default:
526 /* If we cannot get the information we need, we will return a generic error to the SNMP client */
527 return SNMP_ERR_GENERR;
528 }
529
530 return SNMP_ERR_NOERROR;
531 }
532
533
534 /*
535 *
536 * Initialize sub agent
537 */
538
539 void
init_ntpSnmpSubagentObject(void)540 init_ntpSnmpSubagentObject(void)
541 {
542 /* Register all MIB objects with the agentx master */
543 NTP_OID_RO( ntpEntSoftwareName, 1, 1, 1, 0);
544 NTP_OID_RO( ntpEntSoftwareVersion, 1, 1, 2, 0);
545 NTP_OID_RO( ntpEntSoftwareVendor, 1, 1, 3, 0);
546 NTP_OID_RO( ntpEntSystemType, 1, 1, 4, 0);
547 NTP_OID_RO( ntpEntTimeResolution, 1, 1, 5, 0);
548 NTP_OID_RO( ntpEntTimePrecision, 1, 1, 6, 0);
549 NTP_OID_RO( ntpEntTimeDistance, 1, 1, 7, 0);
550 }
551
552