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