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 
32 /*
33  * Host Resources MIB scalars implementation for SNMPd.
34  */
35 
36 #include <sys/types.h>
37 #include <sys/sysctl.h>
38 
39 #include <pwd.h>
40 #include <stdlib.h>
41 #include <stdint.h>
42 #include <string.h>
43 #include <syslog.h>
44 #include <utmp.h>
45 
46 #include "hostres_snmp.h"
47 #include "hostres_oid.h"
48 #include "hostres_tree.h"
49 
50 /* file pointer to keep an open instance of utmp */
51 static FILE *utmp_fp;
52 
53 /* boot timestamp in centi-seconds */
54 static uint64_t kernel_boot;
55 
56 /* physical memory size in Kb */
57 static uint64_t phys_mem_size;
58 
59 /* boot line (malloced) */
60 static u_char *boot_line;
61 
62 /* maximum number of processes */
63 static uint32_t max_proc;
64 
65 /**
66  * Free all static data
67  */
68 void
69 fini_scalars(void)
70 {
71 
72 	free(boot_line);
73 
74 	if (utmp_fp != NULL)
75 		(void)fclose(utmp_fp);
76 }
77 
78 /**
79  * Get system uptime in hundredths of seconds since the epoch
80  * Returns 0 in case of an error
81  */
82 static int
83 OS_getSystemUptime(uint32_t *ut)
84 {
85 	struct timeval right_now;
86 	uint64_t now;
87 
88 	if (kernel_boot == 0) {
89 		/* first time, do the sysctl */
90 		struct timeval kernel_boot_timestamp;
91 		int mib[2] = { CTL_KERN, KERN_BOOTTIME };
92 		size_t len = sizeof(kernel_boot_timestamp);
93 
94 		if (sysctl(mib, 2, &kernel_boot_timestamp,
95 		    &len, NULL, 0) == -1) {
96 			syslog(LOG_ERR, "sysctl KERN_BOOTTIME failed: %m");
97 			return (SNMP_ERR_GENERR);
98 		}
99 
100 		HRDBG("boot timestamp from kernel: {%lld, %ld}",
101 		    (long long)kernel_boot_timestamp.tv_sec,
102 		    (long)kernel_boot_timestamp.tv_usec);
103 
104 		kernel_boot = ((uint64_t)kernel_boot_timestamp.tv_sec * 100) +
105 		    (kernel_boot_timestamp.tv_usec / 10000);
106 	}
107 
108 	if (gettimeofday(&right_now, NULL) < 0) {
109 		syslog(LOG_ERR, "gettimeofday failed: %m");
110 		return (SNMP_ERR_GENERR);
111 	}
112 	now = ((uint64_t)right_now.tv_sec * 100) + (right_now.tv_usec / 10000);
113 
114 	if (now - kernel_boot > UINT32_MAX)
115 		*ut = UINT32_MAX;
116 	else
117 		*ut = now - kernel_boot;
118 
119 	return (SNMP_ERR_NOERROR);
120 }
121 
122 /**
123  * Get system local date and time in a foramt suitable for DateAndTime TC:
124  *           field  octets  contents                  range
125  *           -----  ------  --------                  -----
126  *             1      1-2   year*                     0..65536
127  *             2       3    month                     1..12
128  *             3       4    day                       1..31
129  *             4       5    hour                      0..23
130  *             5       6    minutes                   0..59
131  *             6       7    seconds                   0..60
132  *                          (use 60 for leap-second)
133  *             7       8    deci-seconds              0..9
134  *             8       9    direction from UTC        '+' / '-'
135  *             9      10    hours from UTC*           0..13
136  *            10      11    minutes from UTC          0..59
137  *
138  *           * Notes:
139  *           - the value of year is in network-byte order
140  *           - daylight saving time in New Zealand is +13
141  *
142  *           For example, Tuesday May 26, 1992 at 1:30:15 PM EDT would be
143  *           displayed as:
144  *
145  *                            1992-5-26,13:30:15.0,-4:0
146  *
147  * Returns -1 in case of an error or the length of the string (8 or 11)
148  * Actually returns always 11 on freebsd
149  */
150 static int
151 OS_getSystemDate(struct snmp_value *value)
152 {
153 	u_char s_date_time[11];
154 	struct tm tloc_tm;
155 	time_t tloc_time_t;
156 	struct timeval right_now;
157 	int string_len;
158 
159 	if (gettimeofday(&right_now, NULL) < 0) {
160 		syslog(LOG_ERR, "gettimeofday failed: %m");
161 		return (SNMP_ERR_GENERR);
162 	}
163 
164 	tloc_time_t = right_now.tv_sec;
165 
166 	if (localtime_r(&tloc_time_t, &tloc_tm) == NULL) {
167 		syslog(LOG_ERR, "localtime_r() failed: %m ");
168 		return (SNMP_ERR_GENERR);
169 	}
170 
171 	string_len = make_date_time(s_date_time, &tloc_tm,
172 	    right_now.tv_usec / 100000);
173 
174 	return (string_get(value, s_date_time, string_len));
175 }
176 
177 /**
178  * Get kernel boot path. For FreeBSD it seems that no arguments are
179  * present. Returns NULL if an error occured. The returned data is a
180  * pointer to a global strorage.
181  */
182 int
183 OS_getSystemInitialLoadParameters(u_char **params)
184 {
185 
186 	if (boot_line == NULL) {
187 		int mib[2] = { CTL_KERN, KERN_BOOTFILE };
188 		char *buf;
189 		size_t buf_len = 0;
190 
191 		/* get the needed buffer len */
192 		if (sysctl(mib, 2, NULL, &buf_len, NULL, 0) != 0) {
193 			syslog(LOG_ERR,
194 			    "sysctl({CTL_KERN,KERN_BOOTFILE}) failed: %m");
195 			return (SNMP_ERR_GENERR);
196 		}
197 
198 		if ((buf = malloc(buf_len)) == NULL) {
199 			syslog(LOG_ERR, "malloc failed");
200 			return (SNMP_ERR_GENERR);
201 		}
202                 if (sysctl(mib, 2, buf, &buf_len, NULL, 0)) {
203 			syslog(LOG_ERR,
204 			    "sysctl({CTL_KERN,KERN_BOOTFILE}) failed: %m");
205 			free(buf);
206 			return (SNMP_ERR_GENERR);
207 		}
208 
209 		boot_line = buf;
210 		HRDBG("kernel boot file: %s", boot_line);
211 	}
212 
213 	*params = boot_line;
214 	return (SNMP_ERR_NOERROR);
215 }
216 
217 /**
218  * Get number of current users which are logged in
219  */
220 static int
221 OS_getSystemNumUsers(uint32_t *nu)
222 {
223 	struct utmp utmp;
224 	static int first_time = 1;
225 
226 	if (utmp_fp == NULL) {
227 		if (!first_time)
228 			return (SNMP_ERR_GENERR);
229 		first_time = 0;
230 		if ((utmp_fp = fopen(_PATH_UTMP, "r")) == NULL) {
231 			syslog(LOG_ERR, "fopen(%s) failed: %m", _PATH_UTMP);
232 			return (SNMP_ERR_GENERR);
233 		}
234 	}
235 
236 	/* start with the begining of the utmp file */
237 	(void)rewind(utmp_fp);
238 
239 	*nu = 0;
240 	while (fread(&utmp, sizeof(utmp), 1, utmp_fp) == 1) {
241 		if (utmp.ut_name[0] != '\0' && utmp.ut_line[0] != '\0') {
242 			if (getpwnam(utmp.ut_name) == NULL)
243 				continue;
244 			(*nu)++;
245 		}
246 	}
247 
248 	return (SNMP_ERR_NOERROR);
249 }
250 
251 /**
252  * Get number of current processes existing into the system
253  */
254 static int
255 OS_getSystemProcesses(uint32_t *proc_count)
256 {
257 	int pc;
258 
259 	if (hr_kd == NULL)
260 		return (SNMP_ERR_GENERR);
261 
262 	if (kvm_getprocs(hr_kd, KERN_PROC_ALL, 0, &pc) == NULL) {
263 		syslog(LOG_ERR, "kvm_getprocs failed: %m");
264 		return (SNMP_ERR_GENERR);
265 	}
266 
267 	*proc_count = pc;
268 	return (SNMP_ERR_NOERROR);
269 }
270 
271 /**
272  * Get maximum number of processes allowed on this system
273  */
274 static int
275 OS_getSystemMaxProcesses(uint32_t *mproc)
276 {
277 
278 	if (max_proc == 0) {
279 		int mib[2] = { CTL_KERN, KERN_MAXPROC };
280 		int mp;
281 		size_t len = sizeof(mp);
282 
283 		if (sysctl(mib, 2, &mp, &len, NULL, 0) == -1) {
284 			syslog(LOG_ERR, "sysctl KERN_MAXPROC failed: %m");
285 			return (SNMP_ERR_GENERR);
286 		}
287 		max_proc = mp;
288 	}
289 
290 	*mproc = max_proc;
291 	return (SNMP_ERR_NOERROR);
292 }
293 
294 /*
295  * Get the physical memeory size in Kbytes.
296  * Returns SNMP error code.
297  */
298 static int
299 OS_getMemorySize(uint32_t *ms)
300 {
301 
302 	if (phys_mem_size == 0) {
303 		int mib[2] = { CTL_HW, HW_PHYSMEM };
304 		u_long physmem;
305 		size_t len = sizeof(physmem);
306 
307 		if (sysctl(mib, 2, &physmem, &len, NULL, 0) == -1) {
308 			syslog(LOG_ERR,
309 			    "sysctl({ CTL_HW, HW_PHYSMEM }) failed: %m");
310 			return (SNMP_ERR_GENERR);
311 		}
312 
313 		phys_mem_size = physmem / 1024;
314 	}
315 
316 	if (phys_mem_size > UINT32_MAX)
317 		*ms = UINT32_MAX;
318 	else
319 		*ms = phys_mem_size;
320         return (SNMP_ERR_NOERROR);
321 }
322 
323 /*
324  * Try to use the s_date_time parameter as a DateAndTime TC to fill in
325  * the second parameter.
326  * Returns 0 on succes and -1 for an error.
327  * Bug: time zone info is not used
328  */
329 static struct timeval *
330 OS_checkSystemDateInput(const u_char *str, u_int len)
331 {
332 	struct tm tm_to_set;
333 	time_t t;
334 	struct timeval *tv;
335 
336 	if (len != 8 && len != 11)
337 		return (NULL);
338 
339 	if (str[2] == 0 || str[2] > 12 ||
340 	    str[3] == 0 || str[3] > 31 ||
341 	    str[4] > 23 || str[5] > 59 || str[6] > 60 || str[7] > 9)
342 		return (NULL);
343 
344 	tm_to_set.tm_year = ((str[0] << 8) + str[1]) - 1900;
345 	tm_to_set.tm_mon = str[2] - 1;
346 	tm_to_set.tm_mday = str[3];
347 	tm_to_set.tm_hour = str[4];
348 	tm_to_set.tm_min = str[5];
349 	tm_to_set.tm_sec = str[6];
350 	tm_to_set.tm_isdst = 0;
351 
352 	/* now make UTC from it */
353 	if ((t = timegm(&tm_to_set)) == (time_t)-1)
354 		return (NULL);
355 
356 	/* now apply timezone if specified */
357 	if (len == 11) {
358 		if (str[9] > 13 || str[10] > 59)
359 			return (NULL);
360 		if (str[8] == '+')
361 			t += 3600 * str[9] + 60 * str[10];
362 		else
363 			t -= 3600 * str[9] + 60 * str[10];
364 	}
365 
366 	if ((tv = malloc(sizeof(*tv))) == NULL)
367 		return (NULL);
368 
369 	tv->tv_sec = t;
370 	tv->tv_usec = (int32_t)str[7] * 100000;
371 
372 	return (tv);
373 }
374 
375 /*
376  * Set system date and time. Timezone is not changed
377  */
378 static int
379 OS_setSystemDate(const struct timeval *timeval_to_set)
380 {
381 	if (settimeofday(timeval_to_set, NULL) == -1) {
382 		syslog(LOG_ERR, "settimeofday failed: %m");
383 		return (SNMP_ERR_GENERR);
384         }
385 	return (SNMP_ERR_NOERROR);
386 }
387 
388 /*
389  * prototype of this function was genrated by gensnmptree tool in header file
390  * hostres_tree.h
391  * Returns SNMP_ERR_NOERROR on success
392  */
393 int
394 op_hrSystem(struct snmp_context *ctx, struct snmp_value *value,
395     u_int sub, u_int iidx __unused, enum snmp_op curr_op)
396 {
397 	int err;
398 	u_char *str;
399 
400 	switch (curr_op) {
401 
402           case SNMP_OP_GET:
403 		switch (value->var.subs[sub - 1]) {
404 
405 		case LEAF_hrSystemUptime:
406 			return (OS_getSystemUptime(&value->v.uint32));
407 
408 		case LEAF_hrSystemDate:
409 			return (OS_getSystemDate(value));
410 
411 		case LEAF_hrSystemInitialLoadDevice:
412 			value->v.uint32 = 0; /* FIXME */
413 			return (SNMP_ERR_NOERROR);
414 
415 		case LEAF_hrSystemInitialLoadParameters:
416 			if ((err = OS_getSystemInitialLoadParameters(&str)) !=
417 			    SNMP_ERR_NOERROR)
418 				return (err);
419 			return (string_get(value, str, -1));
420 
421 		case LEAF_hrSystemNumUsers:
422 			return (OS_getSystemNumUsers(&value->v.uint32));
423 
424 		case LEAF_hrSystemProcesses:
425 			return (OS_getSystemProcesses(&value->v.uint32));
426 
427 		case LEAF_hrSystemMaxProcesses:
428 			return (OS_getSystemMaxProcesses(&value->v.uint32));
429 		}
430 		abort();
431 
432 	  case SNMP_OP_SET:
433 		switch (value->var.subs[sub - 1]) {
434 
435 		case LEAF_hrSystemDate:
436 			if ((ctx->scratch->ptr1 =
437 			    OS_checkSystemDateInput(value->v.octetstring.octets,
438 			    value->v.octetstring.len)) == NULL)
439 				return (SNMP_ERR_WRONG_VALUE);
440 
441 			return (SNMP_ERR_NOERROR);
442 
443 		case LEAF_hrSystemInitialLoadDevice:
444 		case LEAF_hrSystemInitialLoadParameters:
445 			return (SNMP_ERR_NOT_WRITEABLE);
446 
447 		}
448 		abort();
449 
450 	  case SNMP_OP_ROLLBACK:
451 		switch (value->var.subs[sub - 1]) {
452 
453 		case LEAF_hrSystemDate:
454 			free(ctx->scratch->ptr1);
455 			return (SNMP_ERR_NOERROR);
456 
457 		case LEAF_hrSystemInitialLoadDevice:
458 		case LEAF_hrSystemInitialLoadParameters:
459 			abort();
460 		}
461 		abort();
462 
463 	  case SNMP_OP_COMMIT:
464 		switch (value->var.subs[sub - 1]) {
465 
466 		case LEAF_hrSystemDate:
467 			(void)OS_setSystemDate(ctx->scratch->ptr1);
468 			free(ctx->scratch->ptr1);
469 			return (SNMP_ERR_NOERROR);
470 
471 		case LEAF_hrSystemInitialLoadDevice:
472 		case LEAF_hrSystemInitialLoadParameters:
473 			abort();
474 		}
475 		abort();
476 
477 	  case SNMP_OP_GETNEXT:
478 		abort();
479 	}
480 	abort();
481 }
482 
483 /*
484  * prototype of this function was genrated by gensnmptree tool
485  * in the header file hostres_tree.h
486  * Returns SNMP_ERR_NOERROR on success
487  */
488 int
489 op_hrStorage(struct snmp_context *ctx __unused, struct snmp_value *value,
490     u_int sub, u_int iidx __unused, enum snmp_op curr_op)
491 {
492 
493 	/* only GET is possible */
494 	switch (curr_op) {
495 
496 	case SNMP_OP_GET:
497 		switch (value->var.subs[sub - 1]) {
498 
499 		case LEAF_hrMemorySize:
500 			return (OS_getMemorySize(&value->v.uint32));
501 		}
502 		abort();
503 
504 	case SNMP_OP_SET:
505 		return (SNMP_ERR_NOT_WRITEABLE);
506 
507 	case SNMP_OP_ROLLBACK:
508 	case SNMP_OP_COMMIT:
509 	case SNMP_OP_GETNEXT:
510 		abort();
511 	}
512 	abort();
513 }
514