xref: /freebsd/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swinstalled_tbl.c (revision 3d11b6c8f01e1fca5936a11d6996448467851a94)
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