Path: blob/main/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swinstalled_tbl.c
108262 views
/*1* Copyright (c) 2005-2006 The FreeBSD Project2* All rights reserved.3*4* Author: Victor Cruceru <[email protected]>5*6* Redistribution of this software and documentation and use in source and7* binary forms, with or without modification, are permitted provided that8* the following conditions are met:9*10* 1. Redistributions of source code or documentation must retain the above11* copyright notice, this list of conditions and the following disclaimer.12* 2. Redistributions in binary form must reproduce the above copyright13* notice, this list of conditions and the following disclaimer in the14* documentation and/or other materials provided with the distribution.15*16* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND17* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE18* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE19* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE20* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL21* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS22* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)23* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT24* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY25* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF26* SUCH DAMAGE.27*28* Host Resources MIB implementation for SNMPd: instrumentation for29* hrSWInstalledTable30*/3132#include <sys/limits.h>33#include <sys/stat.h>34#include <sys/sysctl.h>35#include <sys/utsname.h>3637#include <assert.h>38#include <dirent.h>39#include <err.h>40#include <errno.h>41#include <stdlib.h>42#include <string.h>43#include <syslog.h>44#include <sysexits.h>4546#include "hostres_snmp.h"47#include "hostres_oid.h"48#include "hostres_tree.h"4950#define CONTENTS_FNAME "+CONTENTS"5152enum SWInstalledType {53SWI_UNKNOWN = 1,54SWI_OPERATING_SYSTEM = 2,55SWI_DEVICE_DRIVER = 3,56SWI_APPLICATION = 457};5859#define SW_NAME_MLEN (64 + 1)6061/*62* This structure is used to hold a SNMP table entry63* for HOST-RESOURCES-MIB's hrSWInstalledTable64*/65struct swins_entry {66int32_t index;67u_char *name; /* max len for this is SW_NAME_MLEN */68const struct asn_oid *id;69int32_t type; /* from enum SWInstalledType */70u_char date[11];71u_int date_len;7273#define HR_SWINSTALLED_FOUND 0x00174#define HR_SWINSTALLED_IMMUTABLE 0x00275uint32_t flags;7677TAILQ_ENTRY(swins_entry) link;78};79TAILQ_HEAD(swins_tbl, swins_entry);8081/*82* Table to keep a conistent mapping between software and indexes.83*/84struct swins_map_entry {85int32_t index; /* swins_entry::index */86u_char *name; /* map key,a copy of swins_entry::name*/8788/*89* next may be NULL if the respective hrSWInstalledTblEntry90* is (temporally) gone91*/92struct swins_entry *entry;9394STAILQ_ENTRY(swins_map_entry) link;95};96STAILQ_HEAD(swins_map, swins_map_entry);9798/* map for consistent indexing */99static struct swins_map swins_map = STAILQ_HEAD_INITIALIZER(swins_map);100101/* the head of the list with hrSWInstalledTable's entries */102static struct swins_tbl swins_tbl = TAILQ_HEAD_INITIALIZER(swins_tbl);103104/* next int available for indexing the hrSWInstalledTable */105static uint32_t next_swins_index = 1;106107/* last (agent) tick when hrSWInstalledTable was updated */108static uint64_t swins_tick;109110/* maximum number of ticks between updates of network table */111uint32_t swins_tbl_refresh = HR_SWINS_TBL_REFRESH * 100;112113/* package directory */114u_char *pkg_dir;115116/* last change of package list */117static time_t os_pkg_last_change;118119/**120* Create a new entry into the hrSWInstalledTable121*/122static struct swins_entry *123swins_entry_create(const char *name)124{125struct swins_entry *entry;126struct swins_map_entry *map;127128STAILQ_FOREACH(map, &swins_map, link)129if (strcmp((const char *)map->name, name) == 0)130break;131132if (map == NULL) {133size_t name_len;134/* new object - get a new index */135if (next_swins_index > INT_MAX) {136syslog(LOG_ERR, "%s: hrSWInstalledTable index wrap",137__func__ );138/* There isn't much we can do here.139* If the next_swins_index is consumed140* then we can't add entries to this table141* So it is better to exit - if the table is sparsed142* at the next agent run we can fill it fully.143*/144errx(EX_SOFTWARE, "hrSWInstalledTable index wrap");145}146147if ((map = malloc(sizeof(*map))) == NULL) {148syslog(LOG_ERR, "%s: %m", __func__ );149return (NULL);150}151152name_len = strlen(name) + 1;153if (name_len > SW_NAME_MLEN)154name_len = SW_NAME_MLEN;155156if ((map->name = malloc(name_len)) == NULL) {157syslog(LOG_WARNING, "%s: %m", __func__);158free(map);159return (NULL);160}161162map->index = next_swins_index++;163strlcpy((char *)map->name, name, name_len);164165STAILQ_INSERT_TAIL(&swins_map, map, link);166167HRDBG("%s added into hrSWInstalled at %d", name, map->index);168}169170if ((entry = malloc(sizeof(*entry))) == NULL) {171syslog(LOG_WARNING, "%s: %m", __func__);172return (NULL);173}174memset(entry, 0, sizeof(*entry));175176if ((entry->name = strdup(map->name)) == NULL) {177syslog(LOG_WARNING, "%s: %m", __func__);178free(entry);179return (NULL);180}181182entry->index = map->index;183map->entry = entry;184185INSERT_OBJECT_INT(entry, &swins_tbl);186187return (entry);188}189190/**191* Delete an entry in the hrSWInstalledTable192*/193static void194swins_entry_delete(struct swins_entry *entry)195{196struct swins_map_entry *map;197198assert(entry != NULL);199200TAILQ_REMOVE(&swins_tbl, entry, link);201202STAILQ_FOREACH(map, &swins_map, link)203if (map->entry == entry) {204map->entry = NULL;205break;206}207208free(entry->name);209free(entry);210}211212/**213* Find an entry given it's name214*/215static struct swins_entry *216swins_find_by_name(const char *name)217{218struct swins_entry *entry;219220TAILQ_FOREACH(entry, &swins_tbl, link)221if (strcmp((const char*)entry->name, name) == 0)222return (entry);223return (NULL);224}225226/**227* Finalize this table228*/229void230fini_swins_tbl(void)231{232struct swins_map_entry *n1;233234while ((n1 = STAILQ_FIRST(&swins_map)) != NULL) {235STAILQ_REMOVE_HEAD(&swins_map, link);236if (n1->entry != NULL) {237TAILQ_REMOVE(&swins_tbl, n1->entry, link);238free(n1->entry->name);239free(n1->entry);240}241free(n1->name);242free(n1);243}244assert(TAILQ_EMPTY(&swins_tbl));245}246247/**248* Get the *running* O/S identification249*/250static void251swins_get_OS_ident(void)252{253struct utsname os_id;254char os_string[SW_NAME_MLEN] = "";255struct swins_entry *entry;256u_char *boot;257struct stat sb;258struct tm k_ts;259260if (uname(&os_id) == -1) {261syslog(LOG_WARNING, "%s: %m", __func__);262return;263}264265snprintf(os_string, sizeof(os_string), "%s: %s",266os_id.sysname, os_id.version);267268if ((entry = swins_find_by_name(os_string)) != NULL ||269(entry = swins_entry_create(os_string)) == NULL)270return;271272entry->flags |= (HR_SWINSTALLED_FOUND | HR_SWINSTALLED_IMMUTABLE);273entry->id = &oid_zeroDotZero;274entry->type = (int32_t)SWI_OPERATING_SYSTEM;275memset(entry->date, 0, sizeof(entry->date));276277if (OS_getSystemInitialLoadParameters(&boot) == SNMP_ERR_NOERROR &&278strlen(boot) > 0 && stat(boot, &sb) == 0 &&279localtime_r(&sb.st_ctime, &k_ts) != NULL)280entry->date_len = make_date_time(entry->date, &k_ts, 0);281}282283/**284* Read the installed packages285*/286static int287swins_get_packages(void)288{289struct stat sb;290DIR *p_dir;291struct dirent *ent;292struct tm k_ts;293char *pkg_file;294struct swins_entry *entry;295int ret = 0;296297if (pkg_dir == NULL)298/* initialisation may have failed */299return (-1);300301if (stat(pkg_dir, &sb) != 0) {302syslog(LOG_ERR, "hrSWInstalledTable: stat(\"%s\") failed: %m",303pkg_dir);304return (-1);305}306if (!S_ISDIR(sb.st_mode)) {307syslog(LOG_ERR, "hrSWInstalledTable: \"%s\" is not a directory",308pkg_dir);309return (-1);310}311if (sb.st_ctime <= os_pkg_last_change) {312HRDBG("no need to rescan installed packages -- "313"directory time-stamp unmodified");314315TAILQ_FOREACH(entry, &swins_tbl, link)316entry->flags |= HR_SWINSTALLED_FOUND;317318return (0);319}320321if ((p_dir = opendir(pkg_dir)) == NULL) {322syslog(LOG_ERR, "hrSWInstalledTable: opendir(\"%s\") failed: "323"%m", pkg_dir);324return (-1);325}326327while (errno = 0, (ent = readdir(p_dir)) != NULL) {328HRDBG(" pkg file: %s", ent->d_name);329330/* check that the contents file is a regular file */331if (asprintf(&pkg_file, "%s/%s/%s", pkg_dir, ent->d_name,332CONTENTS_FNAME) == -1)333continue;334335if (stat(pkg_file, &sb) != 0 ) {336free(pkg_file);337continue;338}339340if (!S_ISREG(sb.st_mode)) {341syslog(LOG_ERR, "hrSWInstalledTable: \"%s\" not a "342"regular file -- skipped", pkg_file);343free(pkg_file);344continue;345}346free(pkg_file);347348/* read directory timestamp on package */349if (asprintf(&pkg_file, "%s/%s", pkg_dir, ent->d_name) == -1)350continue;351352if (stat(pkg_file, &sb) == -1 ||353localtime_r(&sb.st_ctime, &k_ts) == NULL) {354free(pkg_file);355continue;356}357free(pkg_file);358359/* update or create entry */360if ((entry = swins_find_by_name(ent->d_name)) == NULL &&361(entry = swins_entry_create(ent->d_name)) == NULL) {362ret = -1;363goto PKG_LOOP_END;364}365366entry->flags |= HR_SWINSTALLED_FOUND;367entry->id = &oid_zeroDotZero;368entry->type = (int32_t)SWI_APPLICATION;369370entry->date_len = make_date_time(entry->date, &k_ts, 0);371}372373if (errno != 0) {374syslog(LOG_ERR, "hrSWInstalledTable: readdir(\"%s\") failed:"375" %m", pkg_dir);376ret = -1;377} else {378/*379* save the timestamp of directory380* to avoid any further scanning381*/382os_pkg_last_change = sb.st_ctime;383}384PKG_LOOP_END:385(void)closedir(p_dir);386return (ret);387}388389/**390* Refresh the installed software table.391*/392void393refresh_swins_tbl(void)394{395int ret;396struct swins_entry *entry, *entry_tmp;397398if (this_tick - swins_tick < swins_tbl_refresh) {399HRDBG("no refresh needed");400return;401}402403/* mark each entry as missing */404TAILQ_FOREACH(entry, &swins_tbl, link)405entry->flags &= ~HR_SWINSTALLED_FOUND;406407ret = swins_get_packages();408409TAILQ_FOREACH_SAFE(entry, &swins_tbl, link, entry_tmp)410if (!(entry->flags & HR_SWINSTALLED_FOUND) &&411!(entry->flags & HR_SWINSTALLED_IMMUTABLE))412swins_entry_delete(entry);413414if (ret == 0)415swins_tick = this_tick;416}417418/**419* Create and populate the package table420*/421void422init_swins_tbl(void)423{424425if ((pkg_dir = malloc(sizeof(PATH_PKGDIR))) == NULL)426syslog(LOG_ERR, "%s: %m", __func__);427else428strcpy(pkg_dir, PATH_PKGDIR);429430swins_get_OS_ident();431refresh_swins_tbl();432433HRDBG("init done");434}435436/**437* SNMP handler438*/439int440op_hrSWInstalledTable(struct snmp_context *ctx __unused,441struct snmp_value *value, u_int sub, u_int iidx __unused,442enum snmp_op curr_op)443{444struct swins_entry *entry;445446refresh_swins_tbl();447448switch (curr_op) {449450case SNMP_OP_GETNEXT:451if ((entry = NEXT_OBJECT_INT(&swins_tbl,452&value->var, sub)) == NULL)453return (SNMP_ERR_NOSUCHNAME);454value->var.len = sub + 1;455value->var.subs[sub] = entry->index;456goto get;457458case SNMP_OP_GET:459if ((entry = FIND_OBJECT_INT(&swins_tbl,460&value->var, sub)) == NULL)461return (SNMP_ERR_NOSUCHNAME);462goto get;463464case SNMP_OP_SET:465if ((entry = FIND_OBJECT_INT(&swins_tbl,466&value->var, sub)) == NULL)467return (SNMP_ERR_NO_CREATION);468return (SNMP_ERR_NOT_WRITEABLE);469470case SNMP_OP_ROLLBACK:471case SNMP_OP_COMMIT:472abort();473}474abort();475476get:477switch (value->var.subs[sub - 1]) {478479case LEAF_hrSWInstalledIndex:480value->v.integer = entry->index;481return (SNMP_ERR_NOERROR);482483case LEAF_hrSWInstalledName:484return (string_get(value, entry->name, -1));485break;486487case LEAF_hrSWInstalledID:488assert(entry->id != NULL);489value->v.oid = *entry->id;490return (SNMP_ERR_NOERROR);491492case LEAF_hrSWInstalledType:493value->v.integer = entry->type;494return (SNMP_ERR_NOERROR);495496case LEAF_hrSWInstalledDate:497return (string_get(value, entry->date, entry->date_len));498}499abort();500}501502/**503* Scalars504*/505int506op_hrSWInstalled(struct snmp_context *ctx __unused,507struct snmp_value *value __unused, u_int sub,508u_int iidx __unused, enum snmp_op curr_op)509{510511/* only SNMP GET is possible */512switch (curr_op) {513514case SNMP_OP_GET:515goto get;516517case SNMP_OP_SET:518return (SNMP_ERR_NOT_WRITEABLE);519520case SNMP_OP_ROLLBACK:521case SNMP_OP_COMMIT:522case SNMP_OP_GETNEXT:523abort();524}525abort();526527get:528switch (value->var.subs[sub - 1]) {529530case LEAF_hrSWInstalledLastChange:531case LEAF_hrSWInstalledLastUpdateTime:532/*533* We always update the entire table so these two tick534* values should be equal.535*/536refresh_swins_tbl();537if (swins_tick <= start_tick)538value->v.uint32 = 0;539else {540uint64_t lastChange = swins_tick - start_tick;541542/* may overflow the SNMP type */543value->v.uint32 =544(lastChange > UINT_MAX ? UINT_MAX : lastChange);545}546547return (SNMP_ERR_NOERROR);548549default:550abort();551}552}553554555