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