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