1 /* 2 * Copyright (c) 2005-2006 The FreeBSD Project 3 * All rights reserved. 4 * 5 * Author: Victor Cruceru <soc-victor@freebsd.org> 6 * 7 * Redistribution of this software and documentation and use in source and 8 * binary forms, with or without modification, are permitted provided that 9 * the following conditions are met: 10 * 11 * 1. Redistributions of source code or documentation must retain the above 12 * copyright notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 * 29 * Host Resources MIB implementation for SNMPd: instrumentation for 30 * hrSWInstalledTable 31 */ 32 33 #include <sys/limits.h> 34 #include <sys/stat.h> 35 #include <sys/sysctl.h> 36 #include <sys/utsname.h> 37 38 #include <assert.h> 39 #include <dirent.h> 40 #include <err.h> 41 #include <errno.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <syslog.h> 45 #include <sysexits.h> 46 47 #include "hostres_snmp.h" 48 #include "hostres_oid.h" 49 #include "hostres_tree.h" 50 51 #define CONTENTS_FNAME "+CONTENTS" 52 53 enum SWInstalledType { 54 SWI_UNKNOWN = 1, 55 SWI_OPERATING_SYSTEM = 2, 56 SWI_DEVICE_DRIVER = 3, 57 SWI_APPLICATION = 4 58 }; 59 60 #define SW_NAME_MLEN (64 + 1) 61 62 /* 63 * This structure is used to hold a SNMP table entry 64 * for HOST-RESOURCES-MIB's hrSWInstalledTable 65 */ 66 struct swins_entry { 67 int32_t index; 68 u_char *name; /* max len for this is SW_NAME_MLEN */ 69 const struct asn_oid *id; 70 int32_t type; /* from enum SWInstalledType */ 71 u_char date[11]; 72 u_int date_len; 73 74 #define HR_SWINSTALLED_FOUND 0x001 75 #define HR_SWINSTALLED_IMMUTABLE 0x002 76 uint32_t flags; 77 78 TAILQ_ENTRY(swins_entry) link; 79 }; 80 TAILQ_HEAD(swins_tbl, swins_entry); 81 82 /* 83 * Table to keep a conistent mapping between software and indexes. 84 */ 85 struct swins_map_entry { 86 int32_t index; /* swins_entry::index */ 87 u_char *name; /* map key,a copy of swins_entry::name*/ 88 89 /* 90 * next may be NULL if the respective hrSWInstalledTblEntry 91 * is (temporally) gone 92 */ 93 struct swins_entry *entry; 94 95 STAILQ_ENTRY(swins_map_entry) link; 96 }; 97 STAILQ_HEAD(swins_map, swins_map_entry); 98 99 /* map for consistent indexing */ 100 static struct swins_map swins_map = STAILQ_HEAD_INITIALIZER(swins_map); 101 102 /* the head of the list with hrSWInstalledTable's entries */ 103 static struct swins_tbl swins_tbl = TAILQ_HEAD_INITIALIZER(swins_tbl); 104 105 /* next int available for indexing the hrSWInstalledTable */ 106 static uint32_t next_swins_index = 1; 107 108 /* last (agent) tick when hrSWInstalledTable was updated */ 109 static uint64_t swins_tick; 110 111 /* maximum number of ticks between updates of network table */ 112 uint32_t swins_tbl_refresh = HR_SWINS_TBL_REFRESH * 100; 113 114 /* package directory */ 115 u_char *pkg_dir; 116 117 /* last change of package list */ 118 static time_t os_pkg_last_change; 119 120 /** 121 * Create a new entry into the hrSWInstalledTable 122 */ 123 static struct swins_entry * 124 swins_entry_create(const char *name) 125 { 126 struct swins_entry *entry; 127 struct swins_map_entry *map; 128 129 STAILQ_FOREACH(map, &swins_map, link) 130 if (strcmp((const char *)map->name, name) == 0) 131 break; 132 133 if (map == NULL) { 134 size_t name_len; 135 /* new object - get a new index */ 136 if (next_swins_index > INT_MAX) { 137 syslog(LOG_ERR, "%s: hrSWInstalledTable index wrap", 138 __func__ ); 139 /* There isn't much we can do here. 140 * If the next_swins_index is consumed 141 * then we can't add entries to this table 142 * So it is better to exit - if the table is sparsed 143 * at the next agent run we can fill it fully. 144 */ 145 errx(EX_SOFTWARE, "hrSWInstalledTable index wrap"); 146 } 147 148 if ((map = malloc(sizeof(*map))) == NULL) { 149 syslog(LOG_ERR, "%s: %m", __func__ ); 150 return (NULL); 151 } 152 153 name_len = strlen(name) + 1; 154 if (name_len > SW_NAME_MLEN) 155 name_len = SW_NAME_MLEN; 156 157 if ((map->name = malloc(name_len)) == NULL) { 158 syslog(LOG_WARNING, "%s: %m", __func__); 159 free(map); 160 return (NULL); 161 } 162 163 map->index = next_swins_index++; 164 strlcpy((char *)map->name, name, name_len); 165 166 STAILQ_INSERT_TAIL(&swins_map, map, link); 167 168 HRDBG("%s added into hrSWInstalled at %d", name, map->index); 169 } 170 171 if ((entry = malloc(sizeof(*entry))) == NULL) { 172 syslog(LOG_WARNING, "%s: %m", __func__); 173 return (NULL); 174 } 175 memset(entry, 0, sizeof(*entry)); 176 177 if ((entry->name = strdup(map->name)) == NULL) { 178 syslog(LOG_WARNING, "%s: %m", __func__); 179 free(entry); 180 return (NULL); 181 } 182 183 entry->index = map->index; 184 map->entry = entry; 185 186 INSERT_OBJECT_INT(entry, &swins_tbl); 187 188 return (entry); 189 } 190 191 /** 192 * Delete an entry in the hrSWInstalledTable 193 */ 194 static void 195 swins_entry_delete(struct swins_entry *entry) 196 { 197 struct swins_map_entry *map; 198 199 assert(entry != NULL); 200 201 TAILQ_REMOVE(&swins_tbl, entry, link); 202 203 STAILQ_FOREACH(map, &swins_map, link) 204 if (map->entry == entry) { 205 map->entry = NULL; 206 break; 207 } 208 209 free(entry->name); 210 free(entry); 211 } 212 213 /** 214 * Find an entry given it's name 215 */ 216 static struct swins_entry * 217 swins_find_by_name(const char *name) 218 { 219 struct swins_entry *entry; 220 221 TAILQ_FOREACH(entry, &swins_tbl, link) 222 if (strcmp((const char*)entry->name, name) == 0) 223 return (entry); 224 return (NULL); 225 } 226 227 /** 228 * Finalize this table 229 */ 230 void 231 fini_swins_tbl(void) 232 { 233 struct swins_map_entry *n1; 234 235 while ((n1 = STAILQ_FIRST(&swins_map)) != NULL) { 236 STAILQ_REMOVE_HEAD(&swins_map, link); 237 if (n1->entry != NULL) { 238 TAILQ_REMOVE(&swins_tbl, n1->entry, link); 239 free(n1->entry->name); 240 free(n1->entry); 241 } 242 free(n1->name); 243 free(n1); 244 } 245 assert(TAILQ_EMPTY(&swins_tbl)); 246 } 247 248 /** 249 * Get the *running* O/S identification 250 */ 251 static void 252 swins_get_OS_ident(void) 253 { 254 struct utsname os_id; 255 char os_string[SW_NAME_MLEN] = ""; 256 struct swins_entry *entry; 257 u_char *boot; 258 struct stat sb; 259 struct tm k_ts; 260 261 if (uname(&os_id) == -1) { 262 syslog(LOG_WARNING, "%s: %m", __func__); 263 return; 264 } 265 266 snprintf(os_string, sizeof(os_string), "%s: %s", 267 os_id.sysname, os_id.version); 268 269 if ((entry = swins_find_by_name(os_string)) != NULL || 270 (entry = swins_entry_create(os_string)) == NULL) 271 return; 272 273 entry->flags |= (HR_SWINSTALLED_FOUND | HR_SWINSTALLED_IMMUTABLE); 274 entry->id = &oid_zeroDotZero; 275 entry->type = (int32_t)SWI_OPERATING_SYSTEM; 276 memset(entry->date, 0, sizeof(entry->date)); 277 278 if (OS_getSystemInitialLoadParameters(&boot) == SNMP_ERR_NOERROR && 279 strlen(boot) > 0 && stat(boot, &sb) == 0 && 280 localtime_r(&sb.st_ctime, &k_ts) != NULL) 281 entry->date_len = make_date_time(entry->date, &k_ts, 0); 282 } 283 284 /** 285 * Read the installed packages 286 */ 287 static int 288 swins_get_packages(void) 289 { 290 struct stat sb; 291 DIR *p_dir; 292 struct dirent *ent; 293 struct tm k_ts; 294 char *pkg_file; 295 struct swins_entry *entry; 296 int ret = 0; 297 298 if (pkg_dir == NULL) 299 /* initialisation may have failed */ 300 return (-1); 301 302 if (stat(pkg_dir, &sb) != 0) { 303 syslog(LOG_ERR, "hrSWInstalledTable: stat(\"%s\") failed: %m", 304 pkg_dir); 305 return (-1); 306 } 307 if (!S_ISDIR(sb.st_mode)) { 308 syslog(LOG_ERR, "hrSWInstalledTable: \"%s\" is not a directory", 309 pkg_dir); 310 return (-1); 311 } 312 if (sb.st_ctime <= os_pkg_last_change) { 313 HRDBG("no need to rescan installed packages -- " 314 "directory time-stamp unmodified"); 315 316 TAILQ_FOREACH(entry, &swins_tbl, link) 317 entry->flags |= HR_SWINSTALLED_FOUND; 318 319 return (0); 320 } 321 322 if ((p_dir = opendir(pkg_dir)) == NULL) { 323 syslog(LOG_ERR, "hrSWInstalledTable: opendir(\"%s\") failed: " 324 "%m", pkg_dir); 325 return (-1); 326 } 327 328 while (errno = 0, (ent = readdir(p_dir)) != NULL) { 329 HRDBG(" pkg file: %s", ent->d_name); 330 331 /* check that the contents file is a regular file */ 332 if (asprintf(&pkg_file, "%s/%s/%s", pkg_dir, ent->d_name, 333 CONTENTS_FNAME) == -1) 334 continue; 335 336 if (stat(pkg_file, &sb) != 0 ) { 337 free(pkg_file); 338 continue; 339 } 340 341 if (!S_ISREG(sb.st_mode)) { 342 syslog(LOG_ERR, "hrSWInstalledTable: \"%s\" not a " 343 "regular file -- skipped", pkg_file); 344 free(pkg_file); 345 continue; 346 } 347 free(pkg_file); 348 349 /* read directory timestamp on package */ 350 if (asprintf(&pkg_file, "%s/%s", pkg_dir, ent->d_name) == -1) 351 continue; 352 353 if (stat(pkg_file, &sb) == -1 || 354 localtime_r(&sb.st_ctime, &k_ts) == NULL) { 355 free(pkg_file); 356 continue; 357 } 358 free(pkg_file); 359 360 /* update or create entry */ 361 if ((entry = swins_find_by_name(ent->d_name)) == NULL && 362 (entry = swins_entry_create(ent->d_name)) == NULL) { 363 ret = -1; 364 goto PKG_LOOP_END; 365 } 366 367 entry->flags |= HR_SWINSTALLED_FOUND; 368 entry->id = &oid_zeroDotZero; 369 entry->type = (int32_t)SWI_APPLICATION; 370 371 entry->date_len = make_date_time(entry->date, &k_ts, 0); 372 } 373 374 if (errno != 0) { 375 syslog(LOG_ERR, "hrSWInstalledTable: readdir_r(\"%s\") failed:" 376 " %m", pkg_dir); 377 ret = -1; 378 } else { 379 /* 380 * save the timestamp of directory 381 * to avoid any further scanning 382 */ 383 os_pkg_last_change = sb.st_ctime; 384 } 385 PKG_LOOP_END: 386 (void)closedir(p_dir); 387 return (ret); 388 } 389 390 /** 391 * Refresh the installed software table. 392 */ 393 void 394 refresh_swins_tbl(void) 395 { 396 int ret; 397 struct swins_entry *entry, *entry_tmp; 398 399 if (this_tick - swins_tick < swins_tbl_refresh) { 400 HRDBG("no refresh needed"); 401 return; 402 } 403 404 /* mark each entry as missing */ 405 TAILQ_FOREACH(entry, &swins_tbl, link) 406 entry->flags &= ~HR_SWINSTALLED_FOUND; 407 408 ret = swins_get_packages(); 409 410 TAILQ_FOREACH_SAFE(entry, &swins_tbl, link, entry_tmp) 411 if (!(entry->flags & HR_SWINSTALLED_FOUND) && 412 !(entry->flags & HR_SWINSTALLED_IMMUTABLE)) 413 swins_entry_delete(entry); 414 415 if (ret == 0) 416 swins_tick = this_tick; 417 } 418 419 /** 420 * Create and populate the package table 421 */ 422 void 423 init_swins_tbl(void) 424 { 425 426 if ((pkg_dir = malloc(sizeof(PATH_PKGDIR))) == NULL) 427 syslog(LOG_ERR, "%s: %m", __func__); 428 else 429 strcpy(pkg_dir, PATH_PKGDIR); 430 431 swins_get_OS_ident(); 432 refresh_swins_tbl(); 433 434 HRDBG("init done"); 435 } 436 437 /** 438 * SNMP handler 439 */ 440 int 441 op_hrSWInstalledTable(struct snmp_context *ctx __unused, 442 struct snmp_value *value, u_int sub, u_int iidx __unused, 443 enum snmp_op curr_op) 444 { 445 struct swins_entry *entry; 446 447 refresh_swins_tbl(); 448 449 switch (curr_op) { 450 451 case SNMP_OP_GETNEXT: 452 if ((entry = NEXT_OBJECT_INT(&swins_tbl, 453 &value->var, sub)) == NULL) 454 return (SNMP_ERR_NOSUCHNAME); 455 value->var.len = sub + 1; 456 value->var.subs[sub] = entry->index; 457 goto get; 458 459 case SNMP_OP_GET: 460 if ((entry = FIND_OBJECT_INT(&swins_tbl, 461 &value->var, sub)) == NULL) 462 return (SNMP_ERR_NOSUCHNAME); 463 goto get; 464 465 case SNMP_OP_SET: 466 if ((entry = FIND_OBJECT_INT(&swins_tbl, 467 &value->var, sub)) == NULL) 468 return (SNMP_ERR_NO_CREATION); 469 return (SNMP_ERR_NOT_WRITEABLE); 470 471 case SNMP_OP_ROLLBACK: 472 case SNMP_OP_COMMIT: 473 abort(); 474 } 475 abort(); 476 477 get: 478 switch (value->var.subs[sub - 1]) { 479 480 case LEAF_hrSWInstalledIndex: 481 value->v.integer = entry->index; 482 return (SNMP_ERR_NOERROR); 483 484 case LEAF_hrSWInstalledName: 485 return (string_get(value, entry->name, -1)); 486 break; 487 488 case LEAF_hrSWInstalledID: 489 assert(entry->id != NULL); 490 value->v.oid = *entry->id; 491 return (SNMP_ERR_NOERROR); 492 493 case LEAF_hrSWInstalledType: 494 value->v.integer = entry->type; 495 return (SNMP_ERR_NOERROR); 496 497 case LEAF_hrSWInstalledDate: 498 return (string_get(value, entry->date, entry->date_len)); 499 } 500 abort(); 501 } 502 503 /** 504 * Scalars 505 */ 506 int 507 op_hrSWInstalled(struct snmp_context *ctx __unused, 508 struct snmp_value *value __unused, u_int sub, 509 u_int iidx __unused, enum snmp_op curr_op) 510 { 511 512 /* only SNMP GET is possible */ 513 switch (curr_op) { 514 515 case SNMP_OP_GET: 516 goto get; 517 518 case SNMP_OP_SET: 519 return (SNMP_ERR_NOT_WRITEABLE); 520 521 case SNMP_OP_ROLLBACK: 522 case SNMP_OP_COMMIT: 523 case SNMP_OP_GETNEXT: 524 abort(); 525 } 526 abort(); 527 528 get: 529 switch (value->var.subs[sub - 1]) { 530 531 case LEAF_hrSWInstalledLastChange: 532 case LEAF_hrSWInstalledLastUpdateTime: 533 /* 534 * We always update the entire table so these two tick 535 * values should be equal. 536 */ 537 refresh_swins_tbl(); 538 if (swins_tick <= start_tick) 539 value->v.uint32 = 0; 540 else { 541 uint64_t lastChange = swins_tick - start_tick; 542 543 /* may overflow the SNMP type */ 544 value->v.uint32 = 545 (lastChange > UINT_MAX ? UINT_MAX : lastChange); 546 } 547 548 return (SNMP_ERR_NOERROR); 549 550 default: 551 abort(); 552 } 553 } 554