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