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
26char 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
61size_t
62ntpsnmpd_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
160size_t
161ntpsnmpd_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
214size_t
215read_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
246int 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
292int 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
328int 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
361int 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 */
419int
420get_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 */
462int
463get_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
497int 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
537void
538init_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