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 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 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 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 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 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 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 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 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 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 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 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