xref: /netbsd-src/external/bsd/ntp/dist/ntpsnmpd/ntpSnmpSubagentObject.c (revision cdfa2a7ef92791ba9db70a584a1d904730e6fb46)
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