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