hostres_swinstalled_tbl.c revision 310899
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: stable/11/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swinstalled_tbl.c 310899 2016-12-31 10:28:59Z ngie $
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
55enum 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 */
68struct 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};
82TAILQ_HEAD(swins_tbl, swins_entry);
83
84/*
85 * Table to keep a conistent mapping between software and indexes.
86 */
87struct 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};
99STAILQ_HEAD(swins_map, swins_map_entry);
100
101/* map for consistent indexing */
102static struct swins_map swins_map = STAILQ_HEAD_INITIALIZER(swins_map);
103
104/* the head of the list with hrSWInstalledTable's entries */
105static struct swins_tbl swins_tbl = TAILQ_HEAD_INITIALIZER(swins_tbl);
106
107/* next int available for indexing the hrSWInstalledTable */
108static uint32_t next_swins_index = 1;
109
110/* last (agent) tick when hrSWInstalledTable was updated */
111static uint64_t swins_tick;
112
113/* maximum number of ticks between updates of network table */
114uint32_t swins_tbl_refresh = HR_SWINS_TBL_REFRESH * 100;
115
116/* package directory */
117u_char *pkg_dir;
118
119/* last change of package list */
120static time_t os_pkg_last_change;
121
122/**
123 * Create a new entry into the hrSWInstalledTable
124 */
125static struct swins_entry *
126swins_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 */
196static void
197swins_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 */
218static struct swins_entry *
219swins_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 */
232void
233fini_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 */
253static void
254swins_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 */
289static int
290swins_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 */
395void
396refresh_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 */
424void
425init_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 */
442int
443op_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 */
508int
509op_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