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