1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * The snmp library helps to prepare the PDUs and communicate with
31  * the snmp agent on the SP side via the ds_snmp driver.
32  */
33 
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <thread.h>
39 #include <synch.h>
40 #include <errno.h>
41 #include <sys/time.h>
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include <fcntl.h>
45 #include <libnvpair.h>
46 #include <sys/ds_snmp.h>
47 
48 #include "libpiclsnmp.h"
49 #include "snmplib.h"
50 #include "asn1.h"
51 #include "pdu.h"
52 #include "debug.h"
53 
54 #pragma init(libpiclsnmp_init)		/* need this in .init */
55 
56 /*
57  * Data from the MIB is fetched based on the hints about object
58  * groups received from (possibly many threads in) the application.
59  * However, the fetched data is kept in a common cache for use across
60  * all threads, so even a GETBULK is issued only when absolutely
61  * necessary.
62  *
63  * Note that locking is not fine grained (there's no locking per row)
64  * since we don't expect too many MT consumers right away.
65  *
66  */
67 static mutex_t	mibcache_lock;
68 static nvlist_t	**mibcache = NULL;
69 static uint_t	n_mibcache_rows = 0;
70 
71 static mutex_t snmp_reqid_lock;
72 static int snmp_reqid = 1;
73 
74 #ifdef SNMP_DEBUG
75 uint_t snmp_nsends = 0;
76 uint_t snmp_sentbytes = 0;
77 uint_t snmp_nrecvs = 0;
78 uint_t snmp_rcvdbytes = 0;
79 #endif
80 
81 #ifdef USE_SOCKETS
82 #define	SNMP_DEFAULT_PORT	161
83 #define	SNMP_MAX_RECV_PKTSZ	(64 * 1024)
84 #endif
85 
86 /*
87  * Static function declarations
88  */
89 static void	libpiclsnmp_init(void);
90 
91 static int	lookup_int(char *, int, int *, int);
92 static int	lookup_str(char *, int, char **, int);
93 static int	lookup_bitstr(char *, int, uchar_t **, uint_t *, int);
94 
95 static oidgroup_t *locate_oid_group(struct picl_snmphdl *, char *);
96 static int	search_oid_in_group(char *, char *, int);
97 
98 static snmp_pdu_t *fetch_single(struct picl_snmphdl *, char *, int, int *);
99 static snmp_pdu_t *fetch_next(struct picl_snmphdl *, char *, int, int *);
100 static void	fetch_bulk(struct picl_snmphdl *, char *, int, int, int, int *);
101 static int	fetch_single_str(struct picl_snmphdl *, char *, int,
102 		    char **, int *);
103 static int	fetch_single_int(struct picl_snmphdl *, char *, int,
104 		    int *, int *);
105 static int	fetch_single_bitstr(struct picl_snmphdl *, char *, int,
106 		    uchar_t **, uint_t *, int *);
107 
108 static int	snmp_send_request(struct picl_snmphdl *, snmp_pdu_t *, int *);
109 static int	snmp_recv_reply(struct picl_snmphdl *, snmp_pdu_t *, int *);
110 
111 static int	mibcache_realloc(int);
112 static void	mibcache_populate(snmp_pdu_t *, int);
113 static char	*oid_to_oidstr(oid *, size_t);
114 
115 
116 static void
117 libpiclsnmp_init(void)
118 {
119 	(void) mutex_init(&mibcache_lock, USYNC_THREAD, NULL);
120 	if (mibcache_realloc(0) < 0)
121 		(void) mutex_destroy(&mibcache_lock);
122 
123 	(void) mutex_init(&snmp_reqid_lock, USYNC_THREAD, NULL);
124 
125 	LOGINIT();
126 }
127 
128 picl_snmphdl_t
129 snmp_init()
130 {
131 	struct picl_snmphdl	*smd;
132 #ifdef USE_SOCKETS
133 	int	sbuf = (1 << 15);	/* 16K */
134 	int	rbuf = (1 << 17);	/* 64K */
135 	char	*snmp_agent_addr;
136 #endif
137 
138 	smd = (struct picl_snmphdl *)calloc(1, sizeof (struct picl_snmphdl));
139 	if (smd == NULL)
140 		return (NULL);
141 
142 #ifdef USE_SOCKETS
143 	if ((snmp_agent_addr = getenv("SNMP_AGENT_IPADDR")) == NULL)
144 		return (NULL);
145 
146 	if ((smd->fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
147 		return (NULL);
148 
149 	(void) setsockopt(smd->fd, SOL_SOCKET, SO_SNDBUF, &sbuf, sizeof (int));
150 	(void) setsockopt(smd->fd, SOL_SOCKET, SO_RCVBUF, &rbuf, sizeof (int));
151 
152 	memset(&smd->agent_addr, 0, sizeof (struct sockaddr_in));
153 	smd->agent_addr.sin_family = AF_INET;
154 	smd->agent_addr.sin_port = htons(SNMP_DEFAULT_PORT);
155 	smd->agent_addr.sin_addr.s_addr = inet_addr(snmp_agent_addr);
156 #else
157 	smd->fd = open(DS_SNMP_DRIVER, O_RDWR);
158 	if (smd->fd < 0) {
159 		free(smd);
160 		return (NULL);
161 	}
162 #endif
163 
164 	return ((picl_snmphdl_t)smd);
165 }
166 
167 void
168 snmp_fini(picl_snmphdl_t hdl)
169 {
170 	struct picl_snmphdl	*smd = (struct picl_snmphdl *)hdl;
171 
172 	if (smd) {
173 		if (smd->fd >= 0) {
174 			(void) close(smd->fd);
175 		}
176 		free(smd);
177 	}
178 }
179 
180 int
181 snmp_reinit(picl_snmphdl_t hdl, int clr_linkreset)
182 {
183 	struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl;
184 	nvlist_t *nvl;
185 	int i;
186 
187 	(void) mutex_lock(&mibcache_lock);
188 
189 	for (i = 0; i < n_mibcache_rows; i++) {
190 		if ((nvl = mibcache[i]) != NULL)
191 			nvlist_free(nvl);
192 	}
193 
194 	n_mibcache_rows = 0;
195 	if (mibcache) {
196 		free(mibcache);
197 		mibcache = NULL;
198 	}
199 
200 	(void) mutex_unlock(&mibcache_lock);
201 
202 	if (clr_linkreset) {
203 		if (smd == NULL || smd->fd < 0)
204 			return (-1);
205 		else
206 			return (ioctl(smd->fd, DSSNMP_CLRLNKRESET, NULL));
207 	}
208 
209 	return (0);
210 }
211 
212 void
213 snmp_register_group(picl_snmphdl_t hdl, char *oidstrs, int n_oids, int is_vol)
214 {
215 	struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl;
216 	oidgroup_t	*oidg;
217 	oidgroup_t	*curr, *prev;
218 	char		*p;
219 	int		i, sz;
220 
221 	/*
222 	 * Allocate a new oidgroup_t
223 	 */
224 	oidg = (oidgroup_t *)calloc(1, sizeof (struct oidgroup));
225 	if (oidg == NULL)
226 		return;
227 
228 	/*
229 	 * Determine how much space is required to register this group
230 	 */
231 	sz = 0;
232 	p = oidstrs;
233 	for (i = 0; i < n_oids; i++) {
234 		sz += strlen(p) + 1;
235 		p = oidstrs + sz;
236 	}
237 
238 	/*
239 	 * Create this oid group
240 	 */
241 	if ((p = (char *)malloc(sz)) == NULL) {
242 		free((void *) oidg);
243 		return;
244 	}
245 
246 	(void) memcpy(p, oidstrs, sz);
247 
248 	oidg->next = NULL;
249 	oidg->oidstrs = p;
250 	oidg->n_oids = n_oids;
251 	oidg->is_volatile = is_vol;
252 
253 	/*
254 	 * Link it to the tail of the list of oid groups
255 	 */
256 	for (prev = NULL, curr = smd->group; curr; curr = curr->next)
257 		prev = curr;
258 
259 	if (prev == NULL)
260 		smd->group = oidg;
261 	else
262 		prev->next = oidg;
263 }
264 
265 /*
266  * snmp_get_int() takes in an OID and returns the integer value
267  * of the object referenced in the passed arg. It returns 0 on
268  * success and -1 on failure.
269  */
270 int
271 snmp_get_int(picl_snmphdl_t hdl, char *prefix, int row, int *val,
272     int *snmp_syserr)
273 {
274 	struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl;
275 	oidgroup_t	*grp;
276 	int	ret;
277 	int	err = 0;
278 
279 	if (smd == NULL || prefix == NULL || val == NULL)
280 		return (-1);
281 
282 	/*
283 	 * If this item should not be cached, fetch it directly from
284 	 * the agent using fetch_single_xxx()
285 	 */
286 	if ((grp = locate_oid_group(smd, prefix)) == NULL) {
287 		ret = fetch_single_int(smd, prefix, row, val, &err);
288 
289 		if (snmp_syserr)
290 			*snmp_syserr = err;
291 
292 		return (ret);
293 	}
294 
295 	/*
296 	 * is it in the cache ?
297 	 */
298 	if (lookup_int(prefix, row, val, grp->is_volatile) == 0)
299 		return (0);
300 
301 	/*
302 	 * fetch it from the agent and populate the cache
303 	 */
304 	fetch_bulk(smd, grp->oidstrs, grp->n_oids, row, grp->is_volatile, &err);
305 	if (snmp_syserr)
306 		*snmp_syserr = err;
307 
308 	/*
309 	 * look it up again and return it
310 	 */
311 	if (lookup_int(prefix, row, val, grp->is_volatile) < 0)
312 		return (-1);
313 
314 	return (0);
315 }
316 
317 /*
318  * snmp_get_str() takes in an OID and returns the string value
319  * of the object referenced in the passed arg. Memory for the string
320  * is allocated within snmp_get_str() and is expected to be freed by
321  * the caller when it is no longer needed. The function returns 0
322  * on success and -1 on failure.
323  */
324 int
325 snmp_get_str(picl_snmphdl_t hdl, char *prefix, int row, char **strp,
326     int *snmp_syserr)
327 {
328 	struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl;
329 	oidgroup_t	*grp;
330 	char	*val;
331 	int	ret;
332 	int	err = 0;
333 
334 	if (smd == NULL || prefix == NULL || strp == NULL)
335 		return (-1);
336 
337 	/*
338 	 * Check if this item is cacheable or not. If not, call
339 	 * fetch_single_* to get it directly from the agent
340 	 */
341 	if ((grp = locate_oid_group(smd, prefix)) == NULL) {
342 		ret = fetch_single_str(smd, prefix, row, strp, &err);
343 
344 		if (snmp_syserr)
345 			*snmp_syserr = err;
346 
347 		return (ret);
348 	}
349 
350 	/*
351 	 * See if it's in the cache already
352 	 */
353 	if (lookup_str(prefix, row, &val, grp->is_volatile) == 0) {
354 		if ((*strp = strdup(val)) == NULL)
355 			return (-1);
356 		else
357 			return (0);
358 	}
359 
360 	/*
361 	 * Fetch it from the agent and populate cache
362 	 */
363 	fetch_bulk(smd, grp->oidstrs, grp->n_oids, row, grp->is_volatile, &err);
364 	if (snmp_syserr)
365 		*snmp_syserr = err;
366 
367 	/*
368 	 * Retry lookup
369 	 */
370 	if (lookup_str(prefix, row, &val, grp->is_volatile) < 0)
371 		return (-1);
372 
373 
374 	if ((*strp = strdup(val)) == NULL)
375 		return (-1);
376 	else
377 		return (0);
378 }
379 
380 /*
381  * snmp_get_bitstr() takes in an OID and returns the bit string value
382  * of the object referenced in the passed args. Memory for the bitstring
383  * is allocated within the function and is expected to be freed by
384  * the caller when it is no longer needed. The function returns 0
385  * on success and -1 on failure.
386  */
387 int
388 snmp_get_bitstr(picl_snmphdl_t hdl, char *prefix, int row, uchar_t **bitstrp,
389     uint_t *nbytes, int *snmp_syserr)
390 {
391 	struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl;
392 	oidgroup_t	*grp;
393 	uchar_t	*val;
394 	int	ret;
395 	int	err = 0;
396 
397 	if (smd == NULL || prefix == NULL || bitstrp == NULL || nbytes == NULL)
398 		return (-1);
399 
400 	/*
401 	 * Check if this item is cacheable or not. If not, call
402 	 * fetch_single_* to get it directly from the agent
403 	 */
404 	if ((grp = locate_oid_group(smd, prefix)) == NULL) {
405 		ret = fetch_single_bitstr(smd, prefix, row, bitstrp,
406 		    nbytes, &err);
407 
408 		if (snmp_syserr)
409 			*snmp_syserr = err;
410 
411 		return (ret);
412 	}
413 
414 	/*
415 	 * See if it's in the cache already
416 	 */
417 	if (lookup_bitstr(prefix, row, &val, nbytes, grp->is_volatile) == 0) {
418 		if ((*bitstrp = (uchar_t *)calloc(*nbytes, 1)) == NULL)
419 			return (-1);
420 		(void) memcpy(*bitstrp, (const void *)val, *nbytes);
421 		return (0);
422 	}
423 
424 	/*
425 	 * Fetch it from the agent and populate cache
426 	 */
427 	fetch_bulk(smd, grp->oidstrs, grp->n_oids, row, grp->is_volatile, &err);
428 	if (snmp_syserr)
429 		*snmp_syserr = err;
430 
431 	/*
432 	 * Retry lookup
433 	 */
434 	if (lookup_bitstr(prefix, row, &val, nbytes, grp->is_volatile) < 0)
435 		return (-1);
436 
437 	if ((*bitstrp = (uchar_t *)calloc(*nbytes, 1)) == NULL)
438 		return (-1);
439 	(void) memcpy(*bitstrp, (const void *)val, *nbytes);
440 
441 	return (0);
442 }
443 
444 /*
445  * snmp_get_nextrow() is similar in operation to SNMP_GETNEXT, but
446  * only just. In particular, this is only expected to return the next
447  * valid row number for the same object, not its value. Since we don't
448  * have any other means, we use this to determine the number of rows
449  * in the table (and the valid ones). This function returns 0 on success
450  * and -1 on failure.
451  */
452 int
453 snmp_get_nextrow(picl_snmphdl_t hdl, char *prefix, int row, int *nextrow,
454     int *snmp_syserr)
455 {
456 	struct picl_snmphdl *smd = (struct picl_snmphdl *)hdl;
457 	snmp_pdu_t *reply_pdu;
458 	pdu_varlist_t *vp;
459 	char	*nxt_oidstr;
460 	int	err = 0;
461 
462 	if (smd == NULL || prefix == NULL || nextrow == NULL) {
463 		if (snmp_syserr)
464 			*snmp_syserr = EINVAL;
465 		return (-1);
466 	}
467 
468 	/*
469 	 * The get_nextrow results should *never* go into any cache,
470 	 * since these relationships are dynamically discovered each time.
471 	 */
472 	if ((reply_pdu = fetch_next(smd, prefix, row, &err)) == NULL) {
473 		if (snmp_syserr)
474 			*snmp_syserr = err;
475 		return (-1);
476 	}
477 
478 	/*
479 	 * We are not concerned about the "value" of the lexicographically
480 	 * next object; we only care about the name of that object and
481 	 * its row number (and whether such an object exists or not).
482 	 */
483 	vp = reply_pdu->vars;
484 
485 	/*
486 	 * This indicates that we're at the end of the MIB view.
487 	 */
488 	if (vp == NULL || vp->name == NULL || vp->type == SNMP_NOSUCHOBJECT ||
489 	    vp->type == SNMP_NOSUCHINSTANCE || vp->type == SNMP_ENDOFMIBVIEW) {
490 		snmp_free_pdu(reply_pdu);
491 		if (snmp_syserr)
492 			*snmp_syserr = ENOSPC;
493 		return (-1);
494 	}
495 
496 	/*
497 	 * need to be able to convert the OID
498 	 */
499 	if ((nxt_oidstr = oid_to_oidstr(vp->name, vp->name_len - 1)) == NULL) {
500 		snmp_free_pdu(reply_pdu);
501 		if (snmp_syserr)
502 			*snmp_syserr = ENOMEM;
503 		return (-1);
504 	}
505 
506 	/*
507 	 * We're on to the next table.
508 	 */
509 	if (strcmp(nxt_oidstr, prefix) != 0) {
510 		free(nxt_oidstr);
511 		snmp_free_pdu(reply_pdu);
512 		if (snmp_syserr)
513 			*snmp_syserr = ENOENT;
514 		return (-1);
515 	}
516 
517 	/*
518 	 * Ok, so we've got an oid that's simply the next valid row of the
519 	 * passed on object, return this row number.
520 	 */
521 	*nextrow = (vp->name)[vp->name_len-1];
522 
523 	free(nxt_oidstr);
524 	snmp_free_pdu(reply_pdu);
525 
526 	return (0);
527 }
528 
529 /*
530  * Request ids for snmp messages to the agent are sequenced here.
531  */
532 int
533 snmp_get_reqid(void)
534 {
535 	int	ret;
536 
537 	(void) mutex_lock(&snmp_reqid_lock);
538 
539 	ret = snmp_reqid++;
540 
541 	(void) mutex_unlock(&snmp_reqid_lock);
542 
543 	return (ret);
544 }
545 
546 static int
547 lookup_int(char *prefix, int row, int *valp, int is_vol)
548 {
549 	int32_t	*val_arr;
550 	uint_t	nelem;
551 	struct timeval tv;
552 	int	elapsed;
553 
554 	(void) mutex_lock(&mibcache_lock);
555 
556 	if (row >= n_mibcache_rows) {
557 		(void) mutex_unlock(&mibcache_lock);
558 		return (-1);
559 	}
560 
561 	if (mibcache[row] == NULL) {
562 		(void) mutex_unlock(&mibcache_lock);
563 		return (-1);
564 	}
565 
566 	/*
567 	 * If this is a volatile property, we should be searching
568 	 * for an integer-timestamp pair
569 	 */
570 	if (is_vol) {
571 		if (nvlist_lookup_int32_array(mibcache[row], prefix,
572 		    &val_arr, &nelem) != 0) {
573 			(void) mutex_unlock(&mibcache_lock);
574 			return (-1);
575 		}
576 		if (nelem != 2 || val_arr[1] < 0) {
577 			(void) mutex_unlock(&mibcache_lock);
578 			return (-1);
579 		}
580 		if (gettimeofday(&tv, NULL) < 0) {
581 			(void) mutex_unlock(&mibcache_lock);
582 			return (-1);
583 		}
584 		elapsed = tv.tv_sec - val_arr[1];
585 		if (elapsed < 0 || elapsed > MAX_INCACHE_TIME) {
586 			(void) mutex_unlock(&mibcache_lock);
587 			return (-1);
588 		}
589 
590 		*valp = (int)val_arr[0];
591 	} else {
592 		if (nvlist_lookup_int32(mibcache[row], prefix, valp) != 0) {
593 			(void) mutex_unlock(&mibcache_lock);
594 			return (-1);
595 		}
596 	}
597 
598 	(void) mutex_unlock(&mibcache_lock);
599 
600 	return (0);
601 }
602 
603 static int
604 lookup_str(char *prefix, int row, char **valp, int is_vol)
605 {
606 	char	**val_arr;
607 	uint_t	nelem;
608 	struct timeval tv;
609 	int	elapsed;
610 
611 	(void) mutex_lock(&mibcache_lock);
612 
613 	if (row >= n_mibcache_rows) {
614 		(void) mutex_unlock(&mibcache_lock);
615 		return (-1);
616 	}
617 
618 	if (mibcache[row] == NULL) {
619 		(void) mutex_unlock(&mibcache_lock);
620 		return (-1);
621 	}
622 
623 	/*
624 	 * If this is a volatile property, we should be searching
625 	 * for a string-timestamp pair
626 	 */
627 	if (is_vol) {
628 		if (nvlist_lookup_string_array(mibcache[row], prefix,
629 		    &val_arr, &nelem) != 0) {
630 			(void) mutex_unlock(&mibcache_lock);
631 			return (-1);
632 		}
633 		if (nelem != 2 || atoi(val_arr[1]) <= 0) {
634 			(void) mutex_unlock(&mibcache_lock);
635 			return (-1);
636 		}
637 		if (gettimeofday(&tv, NULL) < 0) {
638 			(void) mutex_unlock(&mibcache_lock);
639 			return (-1);
640 		}
641 		elapsed = tv.tv_sec - atoi(val_arr[1]);
642 		if (elapsed < 0 || elapsed > MAX_INCACHE_TIME) {
643 			(void) mutex_unlock(&mibcache_lock);
644 			return (-1);
645 		}
646 
647 		*valp = val_arr[0];
648 	} else {
649 		if (nvlist_lookup_string(mibcache[row], prefix, valp) != 0) {
650 			(void) mutex_unlock(&mibcache_lock);
651 			return (-1);
652 		}
653 	}
654 
655 	(void) mutex_unlock(&mibcache_lock);
656 
657 	return (0);
658 }
659 
660 static int
661 lookup_bitstr(char *prefix, int row, uchar_t **valp, uint_t *nelem, int is_vol)
662 {
663 	(void) mutex_lock(&mibcache_lock);
664 
665 	if (row >= n_mibcache_rows) {
666 		(void) mutex_unlock(&mibcache_lock);
667 		return (-1);
668 	}
669 
670 	if (mibcache[row] == NULL) {
671 		(void) mutex_unlock(&mibcache_lock);
672 		return (-1);
673 	}
674 
675 	/*
676 	 * We don't support volatile bit string values yet. The nvlist
677 	 * functions don't support bitstring arrays like they do charstring
678 	 * arrays, so we would need to do things in a convoluted way,
679 	 * probably by attaching the timestamp as part of the byte array
680 	 * itself. However, the need for volatile bitstrings isn't there
681 	 * yet, to justify the effort.
682 	 */
683 	if (is_vol) {
684 		(void) mutex_unlock(&mibcache_lock);
685 		return (-1);
686 	}
687 
688 	if (nvlist_lookup_byte_array(mibcache[row], prefix, valp, nelem) != 0) {
689 		(void) mutex_unlock(&mibcache_lock);
690 		return (-1);
691 	}
692 
693 	(void) mutex_unlock(&mibcache_lock);
694 
695 	return (0);
696 }
697 
698 static int
699 search_oid_in_group(char *prefix, char *oidstrs, int n_oids)
700 {
701 	char	*p;
702 	int	i;
703 
704 	p = oidstrs;
705 	for (i = 0; i < n_oids; i++) {
706 		if (strcmp(p, prefix) == 0)
707 			return (0);
708 
709 		p += strlen(p) + 1;
710 	}
711 
712 	return (-1);
713 }
714 
715 static oidgroup_t *
716 locate_oid_group(struct picl_snmphdl *smd, char *prefix)
717 {
718 	oidgroup_t	*grp;
719 
720 	if (smd == NULL)
721 		return (NULL);
722 
723 	if (smd->group == NULL)
724 		return (NULL);
725 
726 	for (grp = smd->group; grp; grp = grp->next) {
727 		if (search_oid_in_group(prefix, grp->oidstrs,
728 		    grp->n_oids) == 0) {
729 			return (grp);
730 		}
731 	}
732 
733 	return (NULL);
734 }
735 
736 static int
737 fetch_single_int(struct picl_snmphdl *smd, char *prefix, int row, int *ival,
738     int *snmp_syserr)
739 {
740 	snmp_pdu_t *reply_pdu;
741 	pdu_varlist_t *vp;
742 
743 	if ((reply_pdu = fetch_single(smd, prefix, row, snmp_syserr)) == NULL)
744 		return (-1);
745 
746 	/*
747 	 * Note that we don't make any distinction between unsigned int
748 	 * value and signed int value at this point, since we provide
749 	 * only snmp_get_int() at the higher level. While it is possible
750 	 * to provide an entirely separate interface such as snmp_get_uint(),
751 	 * that's quite unnecessary, because we don't do any interpretation
752 	 * of the received value. Besides, the sizes of int and uint are
753 	 * the same and the sizes of all pointers are the same (so val.iptr
754 	 * would be the same as val.uiptr in pdu_varlist_t). If/when we
755 	 * violate any of these assumptions, it will be time to add
756 	 * snmp_get_uint().
757 	 */
758 	vp = reply_pdu->vars;
759 	if (vp == NULL || vp->val.iptr == NULL) {
760 		snmp_free_pdu(reply_pdu);
761 		return (-1);
762 	}
763 
764 	*ival = *(vp->val.iptr);
765 
766 	snmp_free_pdu(reply_pdu);
767 
768 	return (0);
769 }
770 
771 static int
772 fetch_single_str(struct picl_snmphdl *smd, char *prefix, int row, char **valp,
773     int *snmp_syserr)
774 {
775 	snmp_pdu_t *reply_pdu;
776 	pdu_varlist_t *vp;
777 
778 	if ((reply_pdu = fetch_single(smd, prefix, row, snmp_syserr)) == NULL)
779 		return (-1);
780 
781 	vp = reply_pdu->vars;
782 	if (vp == NULL || vp->val.str == NULL) {
783 		snmp_free_pdu(reply_pdu);
784 		return (-1);
785 	}
786 
787 	*valp = strdup((const char *)(vp->val.str));
788 
789 	snmp_free_pdu(reply_pdu);
790 
791 	return (0);
792 }
793 
794 static int
795 fetch_single_bitstr(struct picl_snmphdl *smd, char *prefix, int row,
796     uchar_t **valp, uint_t *nelem, int *snmp_syserr)
797 {
798 	snmp_pdu_t *reply_pdu;
799 	pdu_varlist_t *vp;
800 
801 	if ((reply_pdu = fetch_single(smd, prefix, row, snmp_syserr)) == NULL)
802 		return (-1);
803 
804 	vp = reply_pdu->vars;
805 	if (vp == NULL || vp->val.str == NULL) {
806 		snmp_free_pdu(reply_pdu);
807 		return (-1);
808 	}
809 
810 	if ((*valp = (uchar_t *)calloc(vp->val_len, 1)) == NULL) {
811 		snmp_free_pdu(reply_pdu);
812 		return (-1);
813 	}
814 
815 	*nelem = vp->val_len;
816 	(void) memcpy(*valp, (const void *)(vp->val.str),
817 	    (size_t)(vp->val_len));
818 
819 	snmp_free_pdu(reply_pdu);
820 
821 	return (0);
822 }
823 
824 static snmp_pdu_t *
825 fetch_single(struct picl_snmphdl *smd, char *prefix, int row, int *snmp_syserr)
826 {
827 	snmp_pdu_t	*pdu, *reply_pdu;
828 
829 	LOGGET(TAG_CMD_REQUEST, prefix, row);
830 
831 	if ((pdu = snmp_create_pdu(SNMP_MSG_GET, 0, prefix, 1, row)) == NULL)
832 		return (NULL);
833 
834 	LOGPDU(TAG_REQUEST_PDU, pdu);
835 
836 	if (snmp_make_packet(pdu) < 0) {
837 		snmp_free_pdu(pdu);
838 		return (NULL);
839 	}
840 
841 	LOGPKT(TAG_REQUEST_PKT, pdu->req_pkt, pdu->req_pktsz);
842 
843 	if (snmp_send_request(smd, pdu, snmp_syserr) < 0) {
844 		snmp_free_pdu(pdu);
845 		return (NULL);
846 	}
847 
848 	if (snmp_recv_reply(smd, pdu, snmp_syserr) < 0) {
849 		snmp_free_pdu(pdu);
850 		return (NULL);
851 	}
852 
853 	LOGPKT(TAG_RESPONSE_PKT, pdu->reply_pkt, pdu->reply_pktsz);
854 
855 	reply_pdu = snmp_parse_reply(pdu->reqid, pdu->reply_pkt,
856 	    pdu->reply_pktsz);
857 
858 	LOGPDU(TAG_RESPONSE_PDU, reply_pdu);
859 
860 	snmp_free_pdu(pdu);
861 
862 	return (reply_pdu);
863 }
864 
865 static void
866 fetch_bulk(struct picl_snmphdl *smd, char *oidstrs, int n_oids,
867     int row, int is_vol, int *snmp_syserr)
868 {
869 	snmp_pdu_t	*pdu, *reply_pdu;
870 	int		max_reps;
871 
872 	LOGBULK(TAG_CMD_REQUEST, n_oids, oidstrs, row);
873 
874 	/*
875 	 * If we're fetching volatile properties using BULKGET, don't
876 	 * venture to get multiple rows (passing max_reps=0 will make
877 	 * snmp_create_pdu() fetch SNMP_DEF_MAX_REPETITIONS rows)
878 	 */
879 	max_reps = is_vol ? 1 : 0;
880 
881 	pdu = snmp_create_pdu(SNMP_MSG_GETBULK, max_reps, oidstrs, n_oids, row);
882 	if (pdu == NULL)
883 		return;
884 
885 	LOGPDU(TAG_REQUEST_PDU, pdu);
886 
887 	/*
888 	 * Make an ASN.1 encoded packet from the PDU information
889 	 */
890 	if (snmp_make_packet(pdu) < 0) {
891 		snmp_free_pdu(pdu);
892 		return;
893 	}
894 
895 	LOGPKT(TAG_REQUEST_PKT, pdu->req_pkt, pdu->req_pktsz);
896 
897 	/*
898 	 * Send the request packet to the agent
899 	 */
900 	if (snmp_send_request(smd, pdu, snmp_syserr) < 0) {
901 		snmp_free_pdu(pdu);
902 		return;
903 	}
904 
905 	/*
906 	 * Receive response from the agent into the reply packet buffer
907 	 * in the request PDU
908 	 */
909 	if (snmp_recv_reply(smd, pdu, snmp_syserr) < 0) {
910 		snmp_free_pdu(pdu);
911 		return;
912 	}
913 
914 	LOGPKT(TAG_RESPONSE_PKT, pdu->reply_pkt, pdu->reply_pktsz);
915 
916 	/*
917 	 * Parse the reply, validate the response and create a
918 	 * reply-PDU out of the information. Populate the mibcache
919 	 * with the received values.
920 	 */
921 	reply_pdu = snmp_parse_reply(pdu->reqid, pdu->reply_pkt,
922 	    pdu->reply_pktsz);
923 	if (reply_pdu) {
924 		LOGPDU(TAG_RESPONSE_PDU, reply_pdu);
925 
926 		if (reply_pdu->errstat == SNMP_ERR_NOERROR)
927 			mibcache_populate(reply_pdu, is_vol);
928 
929 		snmp_free_pdu(reply_pdu);
930 	}
931 
932 	snmp_free_pdu(pdu);
933 }
934 
935 static snmp_pdu_t *
936 fetch_next(struct picl_snmphdl *smd, char *prefix, int row, int *snmp_syserr)
937 {
938 	snmp_pdu_t	*pdu, *reply_pdu;
939 
940 	LOGNEXT(TAG_CMD_REQUEST, prefix, row);
941 
942 	pdu = snmp_create_pdu(SNMP_MSG_GETNEXT, 0, prefix, 1, row);
943 	if (pdu == NULL)
944 		return (NULL);
945 
946 	LOGPDU(TAG_REQUEST_PDU, pdu);
947 
948 	if (snmp_make_packet(pdu) < 0) {
949 		snmp_free_pdu(pdu);
950 		return (NULL);
951 	}
952 
953 	LOGPKT(TAG_REQUEST_PKT, pdu->req_pkt, pdu->req_pktsz);
954 
955 	if (snmp_send_request(smd, pdu, snmp_syserr) < 0) {
956 		snmp_free_pdu(pdu);
957 		return (NULL);
958 	}
959 
960 	if (snmp_recv_reply(smd, pdu, snmp_syserr) < 0) {
961 		snmp_free_pdu(pdu);
962 		return (NULL);
963 	}
964 
965 	LOGPKT(TAG_RESPONSE_PKT, pdu->reply_pkt, pdu->reply_pktsz);
966 
967 	reply_pdu = snmp_parse_reply(pdu->reqid, pdu->reply_pkt,
968 	    pdu->reply_pktsz);
969 
970 	LOGPDU(TAG_RESPONSE_PDU, reply_pdu);
971 
972 	snmp_free_pdu(pdu);
973 
974 	return (reply_pdu);
975 }
976 
977 static int
978 snmp_send_request(struct picl_snmphdl *smd, snmp_pdu_t *pdu, int *snmp_syserr)
979 {
980 	extern int	errno;
981 #ifdef USE_SOCKETS
982 	int		ret;
983 #endif
984 
985 	if (smd->fd < 0)
986 		return (-1);
987 
988 	if (pdu == NULL || pdu->req_pkt == NULL)
989 		return (-1);
990 
991 #ifdef USE_SOCKETS
992 	ret = -1;
993 	while (ret < 0) {
994 		LOGIO(TAG_SENDTO, smd->fd, pdu->req_pkt, pdu->req_pktsz);
995 
996 		ret = sendto(smd->fd, pdu->req_pkt, pdu->req_pktsz, 0,
997 		    (struct sockaddr *)&smd->agent_addr,
998 		    sizeof (struct sockaddr));
999 		if (ret < 0 && errno != EINTR) {
1000 			return (-1);
1001 		}
1002 	}
1003 #else
1004 	LOGIO(TAG_WRITE, smd->fd, pdu->req_pkt, pdu->req_pktsz);
1005 
1006 	if (write(smd->fd, pdu->req_pkt, pdu->req_pktsz) < 0) {
1007 		if (snmp_syserr)
1008 			*snmp_syserr = errno;
1009 		return (-1);
1010 	}
1011 #endif
1012 
1013 #ifdef SNMP_DEBUG
1014 	snmp_nsends++;
1015 	snmp_sentbytes += pdu->req_pktsz;
1016 #endif
1017 
1018 	return (0);
1019 }
1020 
1021 static int
1022 snmp_recv_reply(struct picl_snmphdl *smd, snmp_pdu_t *pdu, int *snmp_syserr)
1023 {
1024 	struct dssnmp_info	snmp_info;
1025 	size_t	pktsz;
1026 	uchar_t	*pkt;
1027 	extern int errno;
1028 #ifdef USE_SOCKETS
1029 	struct sockaddr_in 	from;
1030 	int	fromlen;
1031 	ssize_t	msgsz;
1032 #endif
1033 
1034 	if (smd->fd < 0 || pdu == NULL)
1035 		return (-1);
1036 
1037 #ifdef USE_SOCKETS
1038 	if ((pkt = (uchar_t *)calloc(1, SNMP_MAX_RECV_PKTSZ)) == NULL)
1039 		return (-1);
1040 
1041 	fromlen = sizeof (struct sockaddr_in);
1042 
1043 	LOGIO(TAG_RECVFROM, smd->fd, pkt, SNMP_MAX_RECV_PKTSZ);
1044 
1045 	msgsz = recvfrom(smd->fd, pkt, SNMP_MAX_RECV_PKTSZ, 0,
1046 	    (struct sockaddr *)&from, &fromlen);
1047 	if (msgsz  < 0 || msgsz >= SNMP_MAX_RECV_PKTSZ) {
1048 		free(pkt);
1049 		return (-1);
1050 	}
1051 
1052 	pktsz = (size_t)msgsz;
1053 #else
1054 	LOGIO(TAG_IOCTL, smd->fd, DSSNMP_GETINFO, &snmp_info);
1055 
1056 	/*
1057 	 * The ioctl will block until we have snmp data available
1058 	 */
1059 	if (ioctl(smd->fd, DSSNMP_GETINFO, &snmp_info) < 0) {
1060 		if (snmp_syserr)
1061 			*snmp_syserr = errno;
1062 		return (-1);
1063 	}
1064 
1065 	pktsz = snmp_info.size;
1066 	if ((pkt = (uchar_t *)calloc(1, pktsz)) == NULL)
1067 		return (-1);
1068 
1069 	LOGIO(TAG_READ, smd->fd, pkt, pktsz);
1070 
1071 	if (read(smd->fd, pkt, pktsz) < 0) {
1072 		free(pkt);
1073 		if (snmp_syserr)
1074 			*snmp_syserr = errno;
1075 		return (-1);
1076 	}
1077 #endif
1078 
1079 	pdu->reply_pkt = pkt;
1080 	pdu->reply_pktsz = pktsz;
1081 
1082 #ifdef SNMP_DEBUG
1083 	snmp_nrecvs++;
1084 	snmp_rcvdbytes += pktsz;
1085 #endif
1086 
1087 	return (0);
1088 }
1089 
1090 static int
1091 mibcache_realloc(int hint)
1092 {
1093 	uint_t		count = (uint_t)hint;
1094 	nvlist_t	**p;
1095 
1096 	if (hint < 0)
1097 		return (-1);
1098 
1099 	(void) mutex_lock(&mibcache_lock);
1100 
1101 	if (hint < n_mibcache_rows) {
1102 		(void) mutex_unlock(&mibcache_lock);
1103 		return (0);
1104 	}
1105 
1106 	count =  ((count >> MIBCACHE_BLK_SHIFT) + 1) << MIBCACHE_BLK_SHIFT;
1107 
1108 	p = (nvlist_t **)calloc(count, sizeof (nvlist_t *));
1109 	if (p == NULL) {
1110 		(void) mutex_unlock(&mibcache_lock);
1111 		return (-1);
1112 	}
1113 
1114 	if (mibcache) {
1115 		(void) memcpy((void *) p, (void *) mibcache,
1116 		    n_mibcache_rows * sizeof (nvlist_t *));
1117 		free((void *) mibcache);
1118 	}
1119 
1120 	mibcache = p;
1121 	n_mibcache_rows = count;
1122 
1123 	(void) mutex_unlock(&mibcache_lock);
1124 
1125 	return (0);
1126 }
1127 
1128 
1129 /*
1130  * Scan each variable in the returned PDU's bindings and populate
1131  * the cache appropriately
1132  */
1133 static void
1134 mibcache_populate(snmp_pdu_t *pdu, int is_vol)
1135 {
1136 	pdu_varlist_t	*vp;
1137 	int		row, ret;
1138 	char		*oidstr;
1139 	struct timeval	tv;
1140 	int		tod;	/* in secs */
1141 	char		tod_str[MAX_INT_LEN];
1142 	int		ival_arr[2];
1143 	char		*sval_arr[2];
1144 
1145 	/*
1146 	 * If we're populating volatile properties, we also store a
1147 	 * timestamp with each property value. When we lookup, we
1148 	 * check the current time against this timestamp to determine
1149 	 * if we need to refetch the value or not (refetch if it has
1150 	 * been in for far too long).
1151 	 */
1152 	if (is_vol) {
1153 		if (gettimeofday(&tv, NULL) < 0)
1154 			tod = -1;
1155 		else
1156 			tod = (int)tv.tv_sec;
1157 
1158 		tod_str[0] = 0;
1159 		(void) snprintf(tod_str, MAX_INT_LEN, "%d", tod);
1160 
1161 		ival_arr[1] = tod;
1162 		sval_arr[1] = (char *)tod_str;
1163 	}
1164 
1165 	for (vp = pdu->vars; vp; vp = vp->nextvar) {
1166 		if (vp->type != ASN_INTEGER && vp->type != ASN_OCTET_STR &&
1167 		    vp->type != ASN_BIT_STR) {
1168 			continue;
1169 		}
1170 
1171 		if (vp->name == NULL || vp->val.str == NULL)
1172 			continue;
1173 
1174 		row = (vp->name)[vp->name_len-1];
1175 
1176 		(void) mutex_lock(&mibcache_lock);
1177 
1178 		if (row >= n_mibcache_rows) {
1179 			(void) mutex_unlock(&mibcache_lock);
1180 			if (mibcache_realloc(row) < 0)
1181 				continue;
1182 			(void) mutex_lock(&mibcache_lock);
1183 		}
1184 		ret = 0;
1185 		if (mibcache[row] == NULL)
1186 			ret = nvlist_alloc(&mibcache[row], NV_UNIQUE_NAME, 0);
1187 
1188 		(void) mutex_unlock(&mibcache_lock);
1189 
1190 		if (ret != 0)
1191 			continue;
1192 
1193 		/*
1194 		 * Convert the standard OID form into an oid string that
1195 		 * we can use as the key to lookup. Since we only search
1196 		 * by the prefix (mibcache is really an array of nvlist_t
1197 		 * pointers), ignore the leaf subid.
1198 		 */
1199 		oidstr = oid_to_oidstr(vp->name, vp->name_len - 1);
1200 		if (oidstr == NULL)
1201 			continue;
1202 
1203 		(void) mutex_lock(&mibcache_lock);
1204 
1205 		if (vp->type == ASN_INTEGER) {
1206 			if (is_vol) {
1207 				ival_arr[0] = *(vp->val.iptr);
1208 				(void) nvlist_add_int32_array(mibcache[row],
1209 				    oidstr, ival_arr, 2);
1210 			} else {
1211 				(void) nvlist_add_int32(mibcache[row],
1212 				    oidstr, *(vp->val.iptr));
1213 			}
1214 
1215 		} else if (vp->type == ASN_OCTET_STR) {
1216 			if (is_vol) {
1217 				sval_arr[0] = (char *)vp->val.str;
1218 				(void) nvlist_add_string_array(mibcache[row],
1219 				    oidstr, sval_arr, 2);
1220 			} else {
1221 				(void) nvlist_add_string(mibcache[row],
1222 				    oidstr, (const char *)(vp->val.str));
1223 			}
1224 		} else if (vp->type == ASN_BIT_STR) {
1225 			/*
1226 			 * We don't support yet bit string objects that are
1227 			 * volatile values.
1228 			 */
1229 			if (!is_vol) {
1230 				(void) nvlist_add_byte_array(mibcache[row],
1231 				    oidstr, (uchar_t *)(vp->val.str),
1232 				    (uint_t)vp->val_len);
1233 			}
1234 		}
1235 		(void) mutex_unlock(&mibcache_lock);
1236 
1237 		free(oidstr);
1238 	}
1239 }
1240 
1241 static char *
1242 oid_to_oidstr(oid *objid, size_t n_subids)
1243 {
1244 	char	*oidstr;
1245 	char	subid_str[MAX_INT_LEN];
1246 	int	i, isize;
1247 	size_t	oidstr_sz;
1248 
1249 	/*
1250 	 * ugly, but for now this will have to do.
1251 	 */
1252 	oidstr_sz = sizeof (subid_str) * n_subids;
1253 	oidstr = calloc(1, oidstr_sz);
1254 
1255 	for (i = 0; i < n_subids; i++) {
1256 		(void) memset(subid_str, 0, sizeof (subid_str));
1257 		isize = snprintf(subid_str, sizeof (subid_str), "%d",
1258 		    objid[i]);
1259 		if (isize >= sizeof (subid_str))
1260 			return (NULL);
1261 
1262 		(void) strlcat(oidstr, subid_str, oidstr_sz);
1263 		if (i < (n_subids - 1))
1264 			(void) strlcat(oidstr, ".", oidstr_sz);
1265 	}
1266 
1267 	return (oidstr);
1268 }
1269