/* * Copyright (c) 2005-2006 The FreeBSD Project * All rights reserved. * * Author: Victor Cruceru * * Redistribution of this software and documentation and use in source and * binary forms, with or without modification, are permitted provided that * the following conditions are met: * * 1. Redistributions of source code or documentation must retain the above * copyright notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ * * Host Resources MIB implementation for SNMPd: instrumentation for * hrSWInstalledTable */ #include #include #include #include #include #include #include #include #include #include #include #include #include "hostres_snmp.h" #include "hostres_oid.h" #include "hostres_tree.h" #define CONTENTS_FNAME "+CONTENTS" enum SWInstalledType { SWI_UNKNOWN = 1, SWI_OPERATING_SYSTEM = 2, SWI_DEVICE_DRIVER = 3, SWI_APPLICATION = 4 }; #define SW_NAME_MLEN (64 + 1) /* * This structure is used to hold a SNMP table entry * for HOST-RESOURCES-MIB's hrSWInstalledTable */ struct swins_entry { int32_t index; u_char *name; /* max len for this is SW_NAME_MLEN */ const struct asn_oid *id; int32_t type; /* from enum SWInstalledType */ u_char date[11]; u_int date_len; #define HR_SWINSTALLED_FOUND 0x001 #define HR_SWINSTALLED_IMMUTABLE 0x002 uint32_t flags; TAILQ_ENTRY(swins_entry) link; }; TAILQ_HEAD(swins_tbl, swins_entry); /* * Table to keep a conistent mapping between software and indexes. */ struct swins_map_entry { int32_t index; /* swins_entry::index */ u_char *name; /* map key,a copy of swins_entry::name*/ /* * next may be NULL if the respective hrSWInstalledTblEntry * is (temporally) gone */ struct swins_entry *entry; STAILQ_ENTRY(swins_map_entry) link; }; STAILQ_HEAD(swins_map, swins_map_entry); /* map for consistent indexing */ static struct swins_map swins_map = STAILQ_HEAD_INITIALIZER(swins_map); /* the head of the list with hrSWInstalledTable's entries */ static struct swins_tbl swins_tbl = TAILQ_HEAD_INITIALIZER(swins_tbl); /* next int available for indexing the hrSWInstalledTable */ static uint32_t next_swins_index = 1; /* last (agent) tick when hrSWInstalledTable was updated */ static uint64_t swins_tick; /* maximum number of ticks between updates of network table */ uint32_t swins_tbl_refresh = HR_SWINS_TBL_REFRESH * 100; /* package directory */ u_char *pkg_dir; /* last change of package list */ static time_t os_pkg_last_change; /** * Create a new entry into the hrSWInstalledTable */ static struct swins_entry * swins_entry_create(const char *name) { struct swins_entry *entry; struct swins_map_entry *map; STAILQ_FOREACH(map, &swins_map, link) if (strcmp((const char *)map->name, name) == 0) break; if (map == NULL) { size_t name_len; /* new object - get a new index */ if (next_swins_index > INT_MAX) { syslog(LOG_ERR, "%s: hrSWInstalledTable index wrap", __func__ ); /* There isn't much we can do here. * If the next_swins_index is consumed * then we can't add entries to this table * So it is better to exit - if the table is sparsed * at the next agent run we can fill it fully. */ errx(EX_SOFTWARE, "hrSWInstalledTable index wrap"); } if ((map = malloc(sizeof(*map))) == NULL) { syslog(LOG_ERR, "%s: %m", __func__ ); return (NULL); } name_len = strlen(name) + 1; if (name_len > SW_NAME_MLEN) name_len = SW_NAME_MLEN; if ((map->name = malloc(name_len)) == NULL) { syslog(LOG_WARNING, "%s: %m", __func__); free(map); return (NULL); } map->index = next_swins_index++; strlcpy((char *)map->name, name, name_len); STAILQ_INSERT_TAIL(&swins_map, map, link); HRDBG("%s added into hrSWInstalled at %d", name, map->index); } if ((entry = malloc(sizeof(*entry))) == NULL) { syslog(LOG_WARNING, "%s: %m", __func__); return (NULL); } memset(entry, 0, sizeof(*entry)); if ((entry->name = strdup(map->name)) == NULL) { syslog(LOG_WARNING, "%s: %m", __func__); free(entry); return (NULL); } entry->index = map->index; map->entry = entry; INSERT_OBJECT_INT(entry, &swins_tbl); return (entry); } /** * Delete an entry in the hrSWInstalledTable */ static void swins_entry_delete(struct swins_entry *entry) { struct swins_map_entry *map; assert(entry != NULL); TAILQ_REMOVE(&swins_tbl, entry, link); STAILQ_FOREACH(map, &swins_map, link) if (map->entry == entry) { map->entry = NULL; break; } free(entry->name); free(entry); } /** * Find an entry given it's name */ static struct swins_entry * swins_find_by_name(const char *name) { struct swins_entry *entry; TAILQ_FOREACH(entry, &swins_tbl, link) if (strcmp((const char*)entry->name, name) == 0) return (entry); return (NULL); } /** * Finalize this table */ void fini_swins_tbl(void) { struct swins_map_entry *n1; while ((n1 = STAILQ_FIRST(&swins_map)) != NULL) { STAILQ_REMOVE_HEAD(&swins_map, link); if (n1->entry != NULL) { TAILQ_REMOVE(&swins_tbl, n1->entry, link); free(n1->entry->name); free(n1->entry); } free(n1->name); free(n1); } assert(TAILQ_EMPTY(&swins_tbl)); } /** * Get the *running* O/S identification */ static void swins_get_OS_ident(void) { struct utsname os_id; char os_string[SW_NAME_MLEN] = ""; struct swins_entry *entry; u_char *boot; struct stat sb; struct tm k_ts; if (uname(&os_id) == -1) { syslog(LOG_WARNING, "%s: %m", __func__); return; } snprintf(os_string, sizeof(os_string), "%s: %s", os_id.sysname, os_id.version); if ((entry = swins_find_by_name(os_string)) != NULL || (entry = swins_entry_create(os_string)) == NULL) return; entry->flags |= (HR_SWINSTALLED_FOUND | HR_SWINSTALLED_IMMUTABLE); entry->id = &oid_zeroDotZero; entry->type = (int32_t)SWI_OPERATING_SYSTEM; memset(entry->date, 0, sizeof(entry->date)); if (OS_getSystemInitialLoadParameters(&boot) == SNMP_ERR_NOERROR && strlen(boot) > 0 && stat(boot, &sb) == 0 && localtime_r(&sb.st_ctime, &k_ts) != NULL) entry->date_len = make_date_time(entry->date, &k_ts, 0); } /** * Read the installed packages */ static int swins_get_packages(void) { struct stat sb; DIR *p_dir; struct dirent *ent; struct tm k_ts; char *pkg_file; struct swins_entry *entry; int ret = 0; if (pkg_dir == NULL) /* initialisation may have failed */ return (-1); if (stat(pkg_dir, &sb) != 0) { syslog(LOG_ERR, "hrSWInstalledTable: stat(\"%s\") failed: %m", pkg_dir); return (-1); } if (!S_ISDIR(sb.st_mode)) { syslog(LOG_ERR, "hrSWInstalledTable: \"%s\" is not a directory", pkg_dir); return (-1); } if (sb.st_ctime <= os_pkg_last_change) { HRDBG("no need to rescan installed packages -- " "directory time-stamp unmodified"); TAILQ_FOREACH(entry, &swins_tbl, link) entry->flags |= HR_SWINSTALLED_FOUND; return (0); } if ((p_dir = opendir(pkg_dir)) == NULL) { syslog(LOG_ERR, "hrSWInstalledTable: opendir(\"%s\") failed: " "%m", pkg_dir); return (-1); } while (errno = 0, (ent = readdir(p_dir)) != NULL) { HRDBG(" pkg file: %s", ent->d_name); /* check that the contents file is a regular file */ if (asprintf(&pkg_file, "%s/%s/%s", pkg_dir, ent->d_name, CONTENTS_FNAME) == -1) continue; if (stat(pkg_file, &sb) != 0 ) { free(pkg_file); continue; } if (!S_ISREG(sb.st_mode)) { syslog(LOG_ERR, "hrSWInstalledTable: \"%s\" not a " "regular file -- skipped", pkg_file); free(pkg_file); continue; } free(pkg_file); /* read directory timestamp on package */ if (asprintf(&pkg_file, "%s/%s", pkg_dir, ent->d_name) == -1) continue; if (stat(pkg_file, &sb) == -1 || localtime_r(&sb.st_ctime, &k_ts) == NULL) { free(pkg_file); continue; } free(pkg_file); /* update or create entry */ if ((entry = swins_find_by_name(ent->d_name)) == NULL && (entry = swins_entry_create(ent->d_name)) == NULL) { ret = -1; goto PKG_LOOP_END; } entry->flags |= HR_SWINSTALLED_FOUND; entry->id = &oid_zeroDotZero; entry->type = (int32_t)SWI_APPLICATION; entry->date_len = make_date_time(entry->date, &k_ts, 0); } if (errno != 0) { syslog(LOG_ERR, "hrSWInstalledTable: readdir_r(\"%s\") failed:" " %m", pkg_dir); ret = -1; } else { /* * save the timestamp of directory * to avoid any further scanning */ os_pkg_last_change = sb.st_ctime; } PKG_LOOP_END: (void)closedir(p_dir); return (ret); } /** * Refresh the installed software table. */ void refresh_swins_tbl(void) { int ret; struct swins_entry *entry, *entry_tmp; if (this_tick - swins_tick < swins_tbl_refresh) { HRDBG("no refresh needed"); return; } /* mark each entry as missing */ TAILQ_FOREACH(entry, &swins_tbl, link) entry->flags &= ~HR_SWINSTALLED_FOUND; ret = swins_get_packages(); TAILQ_FOREACH_SAFE(entry, &swins_tbl, link, entry_tmp) if (!(entry->flags & HR_SWINSTALLED_FOUND) && !(entry->flags & HR_SWINSTALLED_IMMUTABLE)) swins_entry_delete(entry); if (ret == 0) swins_tick = this_tick; } /** * Create and populate the package table */ void init_swins_tbl(void) { if ((pkg_dir = malloc(sizeof(PATH_PKGDIR))) == NULL) syslog(LOG_ERR, "%s: %m", __func__); else strcpy(pkg_dir, PATH_PKGDIR); swins_get_OS_ident(); refresh_swins_tbl(); HRDBG("init done"); } /** * SNMP handler */ int op_hrSWInstalledTable(struct snmp_context *ctx __unused, struct snmp_value *value, u_int sub, u_int iidx __unused, enum snmp_op curr_op) { struct swins_entry *entry; refresh_swins_tbl(); switch (curr_op) { case SNMP_OP_GETNEXT: if ((entry = NEXT_OBJECT_INT(&swins_tbl, &value->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); value->var.len = sub + 1; value->var.subs[sub] = entry->index; goto get; case SNMP_OP_GET: if ((entry = FIND_OBJECT_INT(&swins_tbl, &value->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); goto get; case SNMP_OP_SET: if ((entry = FIND_OBJECT_INT(&swins_tbl, &value->var, sub)) == NULL) return (SNMP_ERR_NO_CREATION); return (SNMP_ERR_NOT_WRITEABLE); case SNMP_OP_ROLLBACK: case SNMP_OP_COMMIT: abort(); } abort(); get: switch (value->var.subs[sub - 1]) { case LEAF_hrSWInstalledIndex: value->v.integer = entry->index; return (SNMP_ERR_NOERROR); case LEAF_hrSWInstalledName: return (string_get(value, entry->name, -1)); break; case LEAF_hrSWInstalledID: assert(entry->id != NULL); value->v.oid = *entry->id; return (SNMP_ERR_NOERROR); case LEAF_hrSWInstalledType: value->v.integer = entry->type; return (SNMP_ERR_NOERROR); case LEAF_hrSWInstalledDate: return (string_get(value, entry->date, entry->date_len)); } abort(); } /** * Scalars */ int op_hrSWInstalled(struct snmp_context *ctx __unused, struct snmp_value *value __unused, u_int sub, u_int iidx __unused, enum snmp_op curr_op) { /* only SNMP GET is possible */ switch (curr_op) { case SNMP_OP_GET: goto get; case SNMP_OP_SET: return (SNMP_ERR_NOT_WRITEABLE); case SNMP_OP_ROLLBACK: case SNMP_OP_COMMIT: case SNMP_OP_GETNEXT: abort(); } abort(); get: switch (value->var.subs[sub - 1]) { case LEAF_hrSWInstalledLastChange: case LEAF_hrSWInstalledLastUpdateTime: /* * We always update the entire table so these two tick * values should be equal. */ refresh_swins_tbl(); if (swins_tick <= start_tick) value->v.uint32 = 0; else { uint64_t lastChange = swins_tick - start_tick; /* may overflow the SNMP type */ value->v.uint32 = (lastChange > UINT_MAX ? UINT_MAX : lastChange); } return (SNMP_ERR_NOERROR); default: abort(); } }