8322 nl: misleading-indentation
[unleashed/tickless.git] / usr / src / cmd / picl / plugins / sun4v / lib / snmp / pdu.c
blob8703a74aa5d9d6510a62c11f9d77d3351d236646
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
23 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
27 #pragma ident "%Z%%M% %I% %E% SMI"
30 * SNMP PDU and packet transport related routines
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/types.h>
37 #include "asn1.h"
38 #include "pdu.h"
39 #include "debug.h"
42 * Static declarations
44 static int snmp_add_null_vars(snmp_pdu_t *, char *, int, int);
45 static oid *snmp_oidstr_to_oid(int, char *, int, size_t *);
46 static uchar_t *snmp_build_pdu(snmp_pdu_t *, uchar_t *, size_t *);
47 static uchar_t *snmp_build_variable(uchar_t *, size_t *, oid *, size_t,
48 uchar_t, void *, size_t);
49 static uchar_t *snmp_parse_pdu(int, uchar_t *, size_t *, snmp_pdu_t *);
50 static uchar_t *snmp_parse_variable(uchar_t *, size_t *, pdu_varlist_t *);
51 static void snmp_free_null_vars(pdu_varlist_t *);
53 static uchar_t *snmp_def_community = (uchar_t *)SNMP_DEF_COMMUNITY;
56 * Allocates and creates a PDU for the specified SNMP command. Currently
57 * only SNMP_MSG_GET, SNMP_MSG_GETNEXT and SNMP_MSG_GETBULK are supported
59 snmp_pdu_t *
60 snmp_create_pdu(int cmd, int max_reps, char *oidstrs, int n_oids, int row)
62 snmp_pdu_t *pdu;
64 if ((cmd != SNMP_MSG_GET) && (cmd != SNMP_MSG_GETNEXT) &&
65 (cmd != SNMP_MSG_GETBULK)) {
66 return (NULL);
69 pdu = (snmp_pdu_t *)calloc(1, sizeof (snmp_pdu_t));
70 if (pdu == NULL)
71 return (NULL);
73 if (cmd == SNMP_MSG_GET || cmd == SNMP_MSG_GETNEXT) {
74 pdu->version = SNMP_VERSION_1;
75 pdu->errstat = 0;
76 pdu->errindex = 0;
77 } else if (cmd == SNMP_MSG_GETBULK) {
78 pdu->version = SNMP_VERSION_2c;
79 pdu->non_repeaters = 0;
80 pdu->max_repetitions = max_reps ?
81 max_reps : SNMP_DEF_MAX_REPETITIONS;
84 pdu->command = cmd;
85 pdu->reqid = snmp_get_reqid();
86 pdu->community = snmp_def_community;
87 pdu->community_len = SNMP_DEF_COMMUNITY_LEN;
89 if (snmp_add_null_vars(pdu, oidstrs, n_oids, row) < 0) {
90 free((void *) pdu);
91 return (NULL);
94 pdu->req_pkt = NULL;
95 pdu->req_pktsz = 0;
96 pdu->reply_pkt = NULL;
97 pdu->reply_pktsz = 0;
99 return (pdu);
103 * Builds a complete ASN.1 encoded snmp message packet out of the PDU.
104 * Currently the maximum request packet is limited to SNMP_DEF_PKTBUF_SZ.
105 * Since we only send SNMP_MSG_GET, SNMP_MSG_GETNEXT and SNMP_MSG_GETBULK,
106 * as long as the number of bulk oids are not *too* many, we're safe with
107 * this limit (the typical packet size of a bulk request of 10 vars is
108 * around 250 bytes).
111 snmp_make_packet(snmp_pdu_t *pdu)
113 uchar_t *buf, *p;
114 uchar_t *msg_seq_end;
115 uchar_t id;
116 size_t bufsz = SNMP_DEF_PKTBUF_SZ;
117 size_t seqlen;
119 if ((buf = (uchar_t *)calloc(1, SNMP_DEF_PKTBUF_SZ)) == NULL)
120 return (-1);
123 * Let's start with the ASN sequence tag. Set the length
124 * to 0 initially and fill it up once the message packetizing
125 * is complete.
127 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
128 if ((p = asn_build_sequence(buf, &bufsz, id, 0)) == NULL) {
129 free((void *) buf);
130 return (-1);
132 msg_seq_end = p;
135 * Store the version
137 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
138 if ((p = asn_build_int(p, &bufsz, id, pdu->version)) == NULL) {
139 free((void *) buf);
140 return (-1);
144 * Store the community string
146 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR;
147 p = asn_build_string(p, &bufsz, id, pdu->community, pdu->community_len);
148 if (p == NULL) {
149 free((void *) buf);
150 return (-1);
154 * Build the PDU
156 if ((p = snmp_build_pdu(pdu, p, &bufsz)) == NULL) {
157 free((void *) buf);
158 return (-1);
162 * Complete the message pkt by updating the message sequence length
164 seqlen = p - msg_seq_end;
165 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
166 (void) asn_build_sequence(buf, NULL, id, seqlen);
169 * Calculate packet size and return
171 pdu->req_pkt = buf;
172 pdu->req_pktsz = p - buf;
174 return (0);
178 * Makes a PDU out of a reply packet. The reply message is parsed
179 * and if the reqid of the incoming packet does not match the reqid
180 * we're waiting for, an error is returned. The PDU is allocated
181 * inside this routine and must be freed by the caller once it is no
182 * longer needed.
184 snmp_pdu_t *
185 snmp_parse_reply(int reqid, uchar_t *reply_pkt, size_t reply_pktsz)
187 snmp_pdu_t *reply_pdu;
188 uchar_t *p;
189 size_t msgsz = reply_pktsz;
190 uchar_t exp_id;
192 reply_pdu = (snmp_pdu_t *)calloc(1, sizeof (snmp_pdu_t));
193 if (reply_pdu == NULL)
194 return (NULL);
197 * Try to parse the ASN sequence out of the beginning of the reply
198 * packet. If we don't find a sequence at the beginning, something's
199 * wrong.
201 exp_id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
202 if ((p = asn_parse_sequence(reply_pkt, &msgsz, exp_id)) == NULL) {
203 snmp_free_pdu(reply_pdu);
204 return (NULL);
208 * Now try to parse the version out of the packet
210 if ((p = asn_parse_int(p, &msgsz, &reply_pdu->version)) == NULL) {
211 snmp_free_pdu(reply_pdu);
212 return (NULL);
214 if ((reply_pdu->version != SNMP_VERSION_1) &&
215 (reply_pdu->version != SNMP_VERSION_2c)) {
216 snmp_free_pdu(reply_pdu);
217 return (NULL);
221 * Parse the community string (space allocated by asn_parse_string)
223 p = asn_parse_string(p, &msgsz, &reply_pdu->community,
224 &reply_pdu->community_len);
225 if (p == NULL) {
226 snmp_free_pdu(reply_pdu);
227 return (NULL);
231 * Parse the PDU part of the message
233 if ((p = snmp_parse_pdu(reqid, p, &msgsz, reply_pdu)) == NULL) {
234 snmp_free_pdu(reply_pdu);
235 return (NULL);
238 return (reply_pdu);
243 * Convert the OID strings into the standard PDU oid form (sequence of
244 * integer subids) and add them to the PDU's variable list. Note that
245 * this is used only for preparing the request messages (GET, GETNEXT
246 * and GETBULK), so the values of the variables are always null.
248 static int
249 snmp_add_null_vars(snmp_pdu_t *pdu, char *oidstrs, int n_oids, int row)
251 pdu_varlist_t *vp, *prev;
252 pdu_varlist_t *varblock_p = NULL;
253 char *p;
254 int i;
256 prev = NULL;
257 p = oidstrs;
258 for (i = 0; i < n_oids; i++) {
259 if ((vp = calloc(1, sizeof (pdu_varlist_t))) == NULL) {
260 snmp_free_null_vars(varblock_p);
261 return (-1);
262 } else if (i == 0) {
263 varblock_p = vp;
264 } else {
265 prev->nextvar = vp;
268 vp->name = snmp_oidstr_to_oid(pdu->command,
269 p, row, &vp->name_len);
270 if (vp->name == NULL) {
271 snmp_free_null_vars(varblock_p);
272 return (-1);
274 vp->val.str = NULL;
275 vp->val_len = 0;
276 vp->type = ASN_NULL;
277 vp->nextvar = NULL;
279 LOGVAR(TAG_NULL_VAR, vp);
281 prev = vp;
282 p += strlen(p) + 1;
286 * append the varlist to the PDU
288 if (pdu->vars == NULL)
289 pdu->vars = varblock_p;
290 else {
291 for (vp = pdu->vars; vp->nextvar; vp = vp->nextvar)
293 vp->nextvar = varblock_p;
296 return (0);
300 * Some assumptions are in place here to eliminate unnecessary complexity.
301 * All OID strings passed are assumed to be in the numeric string form, have
302 * no leading/trailing '.' or spaces. Since PICL plugin is currently the
303 * only customer, this is quite reasonable.
305 static oid *
306 snmp_oidstr_to_oid(int cmd, char *oidstr, int row, size_t *n_subids)
308 int i, count;
309 char *p, *q;
310 char *oidstr_dup;
311 oid *objid;
313 if ((oidstr == NULL) || (n_subids == NULL))
314 return (NULL);
316 for (count = 1, p = oidstr; p; count++, p++) {
317 if ((p = strchr(p, '.')) == NULL)
318 break;
322 * Add one more to count for 'row'. Need special processing
323 * for SNMP_MSG_GETNEXT and SNMP_MSG_GETBULK requests; see
324 * comment below.
326 if ((cmd == SNMP_MSG_GET) || (cmd == SNMP_MSG_GETBULK && row > 0) ||
327 (cmd == SNMP_MSG_GETNEXT && row >= 0)) {
328 count++;
331 if ((oidstr_dup = strdup(oidstr)) == NULL)
332 return (NULL);
334 objid = (oid *) calloc(count, sizeof (oid));
335 if (objid == NULL) {
336 free((void *) p);
337 return (NULL);
340 p = oidstr_dup;
341 for (i = 0; i < count - 1; i++) {
342 if (q = strchr(p, '.'))
343 *q = 0;
344 objid[i] = (oid) strtoul(p, NULL, 10);
345 p = q + 1;
349 * For SNMP_MSG_GET, the leaf subid will simply be the row#.
351 * For SNMP_MSG_GETBULK, if the row# passed is greater than 0,
352 * we pass 'row-1' as the leaf subid, to include the item that
353 * is of interest to us. If the row# is less than or equal to 0,
354 * we will simply ignore it and pass only the prefix part of the
355 * oidstr. For this case, our count would have been 1 less than
356 * usual, and we are yet to save the last subid.
358 * For SNMP_MSG_GETNEXT, if the row# passed is less than 0,
359 * we'll simply ignore it and pass only the prefix part of the
360 * oidstr. For this case, our count would have been 1 less than
361 * usual, and we are yet to save the last subid. If the row#
362 * passed is greater than or equal to 0, we'll simply pass it
363 * verbatim, as the leaf subid.
365 switch (cmd) {
366 case SNMP_MSG_GET:
367 objid[i] = (oid) row;
368 break;
370 case SNMP_MSG_GETBULK:
371 if (row > 0)
372 objid[i] = (oid) (row - 1);
373 else
374 objid[i] = (oid) strtoul(p, NULL, 10);
375 break;
377 case SNMP_MSG_GETNEXT:
378 if (row < 0)
379 objid[i] = (oid) strtoul(p, NULL, 10);
380 else
381 objid[i] = (oid) row;
382 break;
385 *n_subids = count;
387 free((void *) oidstr_dup);
389 return (objid);
393 * Builds the PDU part of the snmp message packet.
395 static uchar_t *
396 snmp_build_pdu(snmp_pdu_t *pdu, uchar_t *buf, size_t *bufsz_p)
398 uchar_t *p;
399 uchar_t *pdu_seq_begin, *pdu_seq_end;
400 uchar_t *varlist_seq_begin, *varlist_seq_end;
401 uchar_t id;
402 size_t seqlen;
403 pdu_varlist_t *vp;
406 * Build ASN sequence for the PDU command (length will be
407 * updated later once the entire command is completely formed)
409 pdu_seq_begin = buf;
410 p = asn_build_sequence(buf, bufsz_p, (uchar_t)pdu->command, 0);
411 if (p == NULL)
412 return (NULL);
413 pdu_seq_end = p;
416 * Build the request id
418 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
419 if ((p = asn_build_int(p, bufsz_p, id, pdu->reqid)) == NULL)
420 return (NULL);
423 * Build the non-repeaters and max-repetitions for SNMP_MSG_GETBULK
424 * (same as error status and error index for other message types)
426 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
427 if ((p = asn_build_int(p, bufsz_p, id, pdu->non_repeaters)) == NULL)
428 return (NULL);
430 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
431 if ((p = asn_build_int(p, bufsz_p, id, pdu->max_repetitions)) == NULL)
432 return (NULL);
435 * Build ASN sequence for the variables list (update length
436 * after building the varlist)
438 varlist_seq_begin = p;
439 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
440 if ((p = asn_build_sequence(p, bufsz_p, id, 0)) == NULL)
441 return (NULL);
442 varlist_seq_end = p;
445 * Build the variables list
447 for (vp = pdu->vars; vp; vp = vp->nextvar) {
448 p = snmp_build_variable(p, bufsz_p, vp->name, vp->name_len,
449 vp->type, vp->val.str, vp->val_len);
450 if (p == NULL)
451 return (NULL);
455 * Now update the varlist sequence length
457 seqlen = p - varlist_seq_end;
458 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
459 (void) asn_build_sequence(varlist_seq_begin, NULL, id, seqlen);
462 * And finally, update the length for the PDU sequence
464 seqlen = p - pdu_seq_end;
465 (void) asn_build_sequence(pdu_seq_begin, NULL, (uchar_t)pdu->command,
466 seqlen);
468 return (p);
472 * Builds an object variable into the snmp message packet. Although the
473 * code is here to build variables of basic types such as integer, object id
474 * and strings, the only type of variable we ever send via snmp request
475 * messages is the ASN_NULL type.
477 static uchar_t *
478 snmp_build_variable(uchar_t *buf, size_t *bufsz_p, oid *name, size_t name_len,
479 uchar_t val_type, void *val, size_t val_len)
481 uchar_t *p, *varseq_end;
482 size_t seqlen;
483 uchar_t id;
486 * Each variable binding is in turn defined as a 'SEQUENCE of' by
487 * the SNMP PDU format, so we'll prepare the sequence and fill up
488 * the length later. Sigh!
490 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
491 if ((p = asn_build_sequence(buf, bufsz_p, id, 0)) == NULL)
492 return (NULL);
493 varseq_end = p;
496 * Build the object id
498 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OBJECT_ID;
499 if ((p = asn_build_objid(p, bufsz_p, id, name, name_len)) == NULL)
500 return (NULL);
503 * Currently we only ever build ASN_NULL vars while sending requests,
504 * since we support only SNMP_MSG_GET, SNMP_MSG_GETNEXT and
505 * SNMP_MSG_GETBULK.
507 id = ASN_UNIVERSAL | ASN_PRIMITIVE | val_type;
508 switch (val_type) {
509 case ASN_INTEGER:
510 p = asn_build_int(p, bufsz_p, id, *((int *)val));
511 if (p == NULL)
512 return (NULL);
513 break;
515 case ASN_OBJECT_ID:
516 p = asn_build_objid(p, bufsz_p, id, val,
517 val_len / sizeof (oid));
518 if (p == NULL)
519 return (NULL);
520 break;
522 case ASN_OCTET_STR:
523 p = asn_build_string(p, bufsz_p, id, (uchar_t *)val, val_len);
524 if (p == NULL)
525 return (NULL);
526 break;
528 case ASN_NULL:
529 if ((p = asn_build_null(p, bufsz_p, id)) == NULL)
530 return (NULL);
531 break;
533 default:
534 return (NULL);
538 * Rebuild the variable sequence length
540 seqlen = p - varseq_end;
541 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
542 (void) asn_build_sequence(buf, NULL, id, seqlen);
544 return (p);
548 * Parse the PDU portion of the incoming snmp message into the reply_pdu.
549 * Space for all structure members are allocated as needed and must be freed
550 * by the caller when these are no longer needed.
552 static uchar_t *
553 snmp_parse_pdu(int reqid, uchar_t *msg, size_t *msgsz_p, snmp_pdu_t *reply_pdu)
555 uchar_t *p;
556 uchar_t id, exp_id;
557 pdu_varlist_t *newvp, *vp = NULL;
560 * Parse the PDU header out of the message
562 if ((p = asn_parse_header(msg, msgsz_p, &id)) == NULL)
563 return (NULL);
564 if (id != SNMP_MSG_RESPONSE && id != SNMP_MSG_REPORT)
565 return (NULL);
566 reply_pdu->command = (int)id;
569 * Parse the request id and verify that this is the response
570 * we're expecting.
572 if ((p = asn_parse_int(p, msgsz_p, &reply_pdu->reqid)) == NULL)
573 return (NULL);
574 if (reply_pdu->reqid != reqid)
575 return (NULL);
578 * Parse the error-status and error-index values
580 if ((p = asn_parse_int(p, msgsz_p, &reply_pdu->errstat)) == NULL)
581 return (NULL);
582 if ((p = asn_parse_int(p, msgsz_p, &reply_pdu->errindex)) == NULL)
583 return (NULL);
586 * Parse the header for the variables list sequence.
588 exp_id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
589 if ((p = asn_parse_sequence(p, msgsz_p, exp_id)) == NULL)
590 return (NULL);
592 while (((int)*msgsz_p) > 0) {
593 if ((newvp = calloc(1, sizeof (pdu_varlist_t))) == NULL)
594 return (NULL);
596 if (vp == NULL)
597 reply_pdu->vars = newvp;
598 else
599 vp->nextvar = newvp;
601 vp = newvp;
602 if ((p = snmp_parse_variable(p, msgsz_p, vp)) == NULL)
603 return (NULL);
605 LOGVAR(TAG_RESPONSE_VAR, vp);
608 return (p);
612 * Allocate and parse the next variable into the varlist
614 static uchar_t *
615 snmp_parse_variable(uchar_t *msg, size_t *msgsz_p, pdu_varlist_t *vp)
617 uchar_t *p;
618 uchar_t exp_id;
621 * Parse this variable's sequence
623 exp_id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
624 if ((p = asn_parse_sequence(msg, msgsz_p, exp_id)) == NULL)
625 return (NULL);
628 * Parse the variable's object identifier
630 p = asn_parse_objid(p, msgsz_p, &vp->name, &vp->name_len);
631 if (p == NULL)
632 return (NULL);
635 * Parse the object's value
637 if ((p = asn_parse_objval(p, msgsz_p, vp)) == NULL)
638 return (NULL);
640 return (p);
643 void
644 snmp_free_pdu(snmp_pdu_t *pdu)
646 pdu_varlist_t *vp, *nxt;
648 if (pdu) {
649 if ((pdu->community) && (pdu->community != snmp_def_community))
650 free((void *) pdu->community);
652 for (vp = pdu->vars; vp; vp = nxt) {
653 nxt = vp->nextvar;
655 if (vp->name)
656 free((void *) vp->name);
657 if (vp->val.str)
658 free((void *) vp->val.str);
659 free((void *) vp);
662 if (pdu->req_pkt)
663 free((void *) pdu->req_pkt);
665 if (pdu->reply_pkt)
666 free((void *) pdu->reply_pkt);
668 free((void *) pdu);
672 static void
673 snmp_free_null_vars(pdu_varlist_t *varblock_p)
675 pdu_varlist_t *vp, *nxt;
677 for (vp = varblock_p; vp; vp = nxt) {
678 nxt = vp->nextvar;
680 if (vp->name)
681 free(vp->name);
682 free(vp);