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 2007 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 * Copyright 2019 Peter Tribble.
26 */
27
28 /*
29 * SNMP PDU and packet transport related routines
30 */
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/types.h>
36 #include "asn1.h"
37 #include "pdu.h"
38
39 /*
40 * Static declarations
41 */
42 static int snmp_add_null_vars(snmp_pdu_t *, char *, int, int);
43 static oid *snmp_oidstr_to_oid(int, char *, int, size_t *);
44 static uchar_t *snmp_build_pdu(snmp_pdu_t *, uchar_t *, size_t *);
45 static uchar_t *snmp_build_variable(uchar_t *, size_t *, oid *, size_t,
46 uchar_t, void *, size_t);
47 static uchar_t *snmp_parse_pdu(int, uchar_t *, size_t *, snmp_pdu_t *);
48 static uchar_t *snmp_parse_variable(uchar_t *, size_t *, pdu_varlist_t *);
49 static void snmp_free_null_vars(pdu_varlist_t *);
50
51 static uchar_t *snmp_def_community = (uchar_t *)SNMP_DEF_COMMUNITY;
52
53 /*
54 * Allocates and creates a PDU for the specified SNMP command. Currently
55 * only SNMP_MSG_GET, SNMP_MSG_GETNEXT and SNMP_MSG_GETBULK are supported
56 */
57 snmp_pdu_t *
snmp_create_pdu(int cmd,int max_reps,char * oidstrs,int n_oids,int row)58 snmp_create_pdu(int cmd, int max_reps, char *oidstrs, int n_oids, int row)
59 {
60 snmp_pdu_t *pdu;
61
62 if ((cmd != SNMP_MSG_GET) && (cmd != SNMP_MSG_GETNEXT) &&
63 (cmd != SNMP_MSG_GETBULK)) {
64 return (NULL);
65 }
66
67 pdu = (snmp_pdu_t *)calloc(1, sizeof (snmp_pdu_t));
68 if (pdu == NULL)
69 return (NULL);
70
71 if (cmd == SNMP_MSG_GET || cmd == SNMP_MSG_GETNEXT) {
72 pdu->version = SNMP_VERSION_1;
73 pdu->errstat = 0;
74 pdu->errindex = 0;
75 } else if (cmd == SNMP_MSG_GETBULK) {
76 pdu->version = SNMP_VERSION_2c;
77 pdu->non_repeaters = 0;
78 pdu->max_repetitions = max_reps ?
79 max_reps : SNMP_DEF_MAX_REPETITIONS;
80 }
81
82 pdu->command = cmd;
83 pdu->reqid = snmp_get_reqid();
84 pdu->community = snmp_def_community;
85 pdu->community_len = SNMP_DEF_COMMUNITY_LEN;
86
87 if (snmp_add_null_vars(pdu, oidstrs, n_oids, row) < 0) {
88 free((void *) pdu);
89 return (NULL);
90 }
91
92 pdu->req_pkt = NULL;
93 pdu->req_pktsz = 0;
94 pdu->reply_pkt = NULL;
95 pdu->reply_pktsz = 0;
96
97 return (pdu);
98 }
99
100 /*
101 * Builds a complete ASN.1 encoded snmp message packet out of the PDU.
102 * Currently the maximum request packet is limited to SNMP_DEF_PKTBUF_SZ.
103 * Since we only send SNMP_MSG_GET, SNMP_MSG_GETNEXT and SNMP_MSG_GETBULK,
104 * as long as the number of bulk oids are not *too* many, we're safe with
105 * this limit (the typical packet size of a bulk request of 10 vars is
106 * around 250 bytes).
107 */
108 int
snmp_make_packet(snmp_pdu_t * pdu)109 snmp_make_packet(snmp_pdu_t *pdu)
110 {
111 uchar_t *buf, *p;
112 uchar_t *msg_seq_end;
113 uchar_t id;
114 size_t bufsz = SNMP_DEF_PKTBUF_SZ;
115 size_t seqlen;
116
117 if ((buf = (uchar_t *)calloc(1, SNMP_DEF_PKTBUF_SZ)) == NULL)
118 return (-1);
119
120 /*
121 * Let's start with the ASN sequence tag. Set the length
122 * to 0 initially and fill it up once the message packetizing
123 * is complete.
124 */
125 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
126 if ((p = asn_build_sequence(buf, &bufsz, id, 0)) == NULL) {
127 free((void *) buf);
128 return (-1);
129 }
130 msg_seq_end = p;
131
132 /*
133 * Store the version
134 */
135 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
136 if ((p = asn_build_int(p, &bufsz, id, pdu->version)) == NULL) {
137 free((void *) buf);
138 return (-1);
139 }
140
141 /*
142 * Store the community string
143 */
144 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR;
145 p = asn_build_string(p, &bufsz, id, pdu->community, pdu->community_len);
146 if (p == NULL) {
147 free((void *) buf);
148 return (-1);
149 }
150
151 /*
152 * Build the PDU
153 */
154 if ((p = snmp_build_pdu(pdu, p, &bufsz)) == NULL) {
155 free((void *) buf);
156 return (-1);
157 }
158
159 /*
160 * Complete the message pkt by updating the message sequence length
161 */
162 seqlen = p - msg_seq_end;
163 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
164 (void) asn_build_sequence(buf, NULL, id, seqlen);
165
166 /*
167 * Calculate packet size and return
168 */
169 pdu->req_pkt = buf;
170 pdu->req_pktsz = p - buf;
171
172 return (0);
173 }
174
175 /*
176 * Makes a PDU out of a reply packet. The reply message is parsed
177 * and if the reqid of the incoming packet does not match the reqid
178 * we're waiting for, an error is returned. The PDU is allocated
179 * inside this routine and must be freed by the caller once it is no
180 * longer needed.
181 */
182 snmp_pdu_t *
snmp_parse_reply(int reqid,uchar_t * reply_pkt,size_t reply_pktsz)183 snmp_parse_reply(int reqid, uchar_t *reply_pkt, size_t reply_pktsz)
184 {
185 snmp_pdu_t *reply_pdu;
186 uchar_t *p;
187 size_t msgsz = reply_pktsz;
188 uchar_t exp_id;
189
190 reply_pdu = (snmp_pdu_t *)calloc(1, sizeof (snmp_pdu_t));
191 if (reply_pdu == NULL)
192 return (NULL);
193
194 /*
195 * Try to parse the ASN sequence out of the beginning of the reply
196 * packet. If we don't find a sequence at the beginning, something's
197 * wrong.
198 */
199 exp_id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
200 if ((p = asn_parse_sequence(reply_pkt, &msgsz, exp_id)) == NULL) {
201 snmp_free_pdu(reply_pdu);
202 return (NULL);
203 }
204
205 /*
206 * Now try to parse the version out of the packet
207 */
208 if ((p = asn_parse_int(p, &msgsz, &reply_pdu->version)) == NULL) {
209 snmp_free_pdu(reply_pdu);
210 return (NULL);
211 }
212 if ((reply_pdu->version != SNMP_VERSION_1) &&
213 (reply_pdu->version != SNMP_VERSION_2c)) {
214 snmp_free_pdu(reply_pdu);
215 return (NULL);
216 }
217
218 /*
219 * Parse the community string (space allocated by asn_parse_string)
220 */
221 p = asn_parse_string(p, &msgsz, &reply_pdu->community,
222 &reply_pdu->community_len);
223 if (p == NULL) {
224 snmp_free_pdu(reply_pdu);
225 return (NULL);
226 }
227
228 /*
229 * Parse the PDU part of the message
230 */
231 if ((p = snmp_parse_pdu(reqid, p, &msgsz, reply_pdu)) == NULL) {
232 snmp_free_pdu(reply_pdu);
233 return (NULL);
234 }
235
236 return (reply_pdu);
237 }
238
239
240 /*
241 * Convert the OID strings into the standard PDU oid form (sequence of
242 * integer subids) and add them to the PDU's variable list. Note that
243 * this is used only for preparing the request messages (GET, GETNEXT
244 * and GETBULK), so the values of the variables are always null.
245 */
246 static int
snmp_add_null_vars(snmp_pdu_t * pdu,char * oidstrs,int n_oids,int row)247 snmp_add_null_vars(snmp_pdu_t *pdu, char *oidstrs, int n_oids, int row)
248 {
249 pdu_varlist_t *vp, *prev;
250 pdu_varlist_t *varblock_p = NULL;
251 char *p;
252 int i;
253
254 prev = NULL;
255 p = oidstrs;
256 for (i = 0; i < n_oids; i++) {
257 if ((vp = calloc(1, sizeof (pdu_varlist_t))) == NULL) {
258 snmp_free_null_vars(varblock_p);
259 return (-1);
260 } else if (i == 0) {
261 varblock_p = vp;
262 } else {
263 prev->nextvar = vp;
264 }
265
266 vp->name = snmp_oidstr_to_oid(pdu->command,
267 p, row, &vp->name_len);
268 if (vp->name == NULL) {
269 snmp_free_null_vars(varblock_p);
270 return (-1);
271 }
272 vp->val.str = NULL;
273 vp->val_len = 0;
274 vp->type = ASN_NULL;
275 vp->nextvar = NULL;
276
277 prev = vp;
278 p += strlen(p) + 1;
279 }
280
281 /*
282 * append the varlist to the PDU
283 */
284 if (pdu->vars == NULL)
285 pdu->vars = varblock_p;
286 else {
287 for (vp = pdu->vars; vp->nextvar; vp = vp->nextvar)
288 ;
289 vp->nextvar = varblock_p;
290 }
291
292 return (0);
293 }
294
295 /*
296 * Some assumptions are in place here to eliminate unnecessary complexity.
297 * All OID strings passed are assumed to be in the numeric string form, have
298 * no leading/trailing '.' or spaces. Since PICL plugin is currently the
299 * only customer, this is quite reasonable.
300 */
301 static oid *
snmp_oidstr_to_oid(int cmd,char * oidstr,int row,size_t * n_subids)302 snmp_oidstr_to_oid(int cmd, char *oidstr, int row, size_t *n_subids)
303 {
304 int i, count;
305 char *p, *q;
306 char *oidstr_dup;
307 oid *objid;
308
309 if ((oidstr == NULL) || (n_subids == NULL))
310 return (NULL);
311
312 for (count = 1, p = oidstr; p; count++, p++) {
313 if ((p = strchr(p, '.')) == NULL)
314 break;
315 }
316
317 /*
318 * Add one more to count for 'row'. Need special processing
319 * for SNMP_MSG_GETNEXT and SNMP_MSG_GETBULK requests; see
320 * comment below.
321 */
322 if ((cmd == SNMP_MSG_GET) || (cmd == SNMP_MSG_GETBULK && row > 0) ||
323 (cmd == SNMP_MSG_GETNEXT && row >= 0)) {
324 count++;
325 }
326
327 if ((oidstr_dup = strdup(oidstr)) == NULL)
328 return (NULL);
329
330 objid = (oid *) calloc(count, sizeof (oid));
331 if (objid == NULL) {
332 free((void *) p);
333 return (NULL);
334 }
335
336 p = oidstr_dup;
337 for (i = 0; i < count - 1; i++) {
338 if (q = strchr(p, '.'))
339 *q = 0;
340 objid[i] = (oid) strtoul(p, NULL, 10);
341 p = q + 1;
342 }
343
344 /*
345 * For SNMP_MSG_GET, the leaf subid will simply be the row#.
346 *
347 * For SNMP_MSG_GETBULK, if the row# passed is greater than 0,
348 * we pass 'row-1' as the leaf subid, to include the item that
349 * is of interest to us. If the row# is less than or equal to 0,
350 * we will simply ignore it and pass only the prefix part of the
351 * oidstr. For this case, our count would have been 1 less than
352 * usual, and we are yet to save the last subid.
353 *
354 * For SNMP_MSG_GETNEXT, if the row# passed is less than 0,
355 * we'll simply ignore it and pass only the prefix part of the
356 * oidstr. For this case, our count would have been 1 less than
357 * usual, and we are yet to save the last subid. If the row#
358 * passed is greater than or equal to 0, we'll simply pass it
359 * verbatim, as the leaf subid.
360 */
361 switch (cmd) {
362 case SNMP_MSG_GET:
363 objid[i] = (oid) row;
364 break;
365
366 case SNMP_MSG_GETBULK:
367 if (row > 0)
368 objid[i] = (oid) (row - 1);
369 else
370 objid[i] = (oid) strtoul(p, NULL, 10);
371 break;
372
373 case SNMP_MSG_GETNEXT:
374 if (row < 0)
375 objid[i] = (oid) strtoul(p, NULL, 10);
376 else
377 objid[i] = (oid) row;
378 break;
379 }
380
381 *n_subids = count;
382
383 free((void *) oidstr_dup);
384
385 return (objid);
386 }
387
388 /*
389 * Builds the PDU part of the snmp message packet.
390 */
391 static uchar_t *
snmp_build_pdu(snmp_pdu_t * pdu,uchar_t * buf,size_t * bufsz_p)392 snmp_build_pdu(snmp_pdu_t *pdu, uchar_t *buf, size_t *bufsz_p)
393 {
394 uchar_t *p;
395 uchar_t *pdu_seq_begin, *pdu_seq_end;
396 uchar_t *varlist_seq_begin, *varlist_seq_end;
397 uchar_t id;
398 size_t seqlen;
399 pdu_varlist_t *vp;
400
401 /*
402 * Build ASN sequence for the PDU command (length will be
403 * updated later once the entire command is completely formed)
404 */
405 pdu_seq_begin = buf;
406 p = asn_build_sequence(buf, bufsz_p, (uchar_t)pdu->command, 0);
407 if (p == NULL)
408 return (NULL);
409 pdu_seq_end = p;
410
411 /*
412 * Build the request id
413 */
414 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
415 if ((p = asn_build_int(p, bufsz_p, id, pdu->reqid)) == NULL)
416 return (NULL);
417
418 /*
419 * Build the non-repeaters and max-repetitions for SNMP_MSG_GETBULK
420 * (same as error status and error index for other message types)
421 */
422 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
423 if ((p = asn_build_int(p, bufsz_p, id, pdu->non_repeaters)) == NULL)
424 return (NULL);
425
426 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
427 if ((p = asn_build_int(p, bufsz_p, id, pdu->max_repetitions)) == NULL)
428 return (NULL);
429
430 /*
431 * Build ASN sequence for the variables list (update length
432 * after building the varlist)
433 */
434 varlist_seq_begin = p;
435 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
436 if ((p = asn_build_sequence(p, bufsz_p, id, 0)) == NULL)
437 return (NULL);
438 varlist_seq_end = p;
439
440 /*
441 * Build the variables list
442 */
443 for (vp = pdu->vars; vp; vp = vp->nextvar) {
444 p = snmp_build_variable(p, bufsz_p, vp->name, vp->name_len,
445 vp->type, vp->val.str, vp->val_len);
446 if (p == NULL)
447 return (NULL);
448 }
449
450 /*
451 * Now update the varlist sequence length
452 */
453 seqlen = p - varlist_seq_end;
454 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
455 (void) asn_build_sequence(varlist_seq_begin, NULL, id, seqlen);
456
457 /*
458 * And finally, update the length for the PDU sequence
459 */
460 seqlen = p - pdu_seq_end;
461 (void) asn_build_sequence(pdu_seq_begin, NULL, (uchar_t)pdu->command,
462 seqlen);
463
464 return (p);
465 }
466
467 /*
468 * Builds an object variable into the snmp message packet. Although the
469 * code is here to build variables of basic types such as integer, object id
470 * and strings, the only type of variable we ever send via snmp request
471 * messages is the ASN_NULL type.
472 */
473 static uchar_t *
snmp_build_variable(uchar_t * buf,size_t * bufsz_p,oid * name,size_t name_len,uchar_t val_type,void * val,size_t val_len)474 snmp_build_variable(uchar_t *buf, size_t *bufsz_p, oid *name, size_t name_len,
475 uchar_t val_type, void *val, size_t val_len)
476 {
477 uchar_t *p, *varseq_end;
478 size_t seqlen;
479 uchar_t id;
480
481 /*
482 * Each variable binding is in turn defined as a 'SEQUENCE of' by
483 * the SNMP PDU format, so we'll prepare the sequence and fill up
484 * the length later. Sigh!
485 */
486 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
487 if ((p = asn_build_sequence(buf, bufsz_p, id, 0)) == NULL)
488 return (NULL);
489 varseq_end = p;
490
491 /*
492 * Build the object id
493 */
494 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OBJECT_ID;
495 if ((p = asn_build_objid(p, bufsz_p, id, name, name_len)) == NULL)
496 return (NULL);
497
498 /*
499 * Currently we only ever build ASN_NULL vars while sending requests,
500 * since we support only SNMP_MSG_GET, SNMP_MSG_GETNEXT and
501 * SNMP_MSG_GETBULK.
502 */
503 id = ASN_UNIVERSAL | ASN_PRIMITIVE | val_type;
504 switch (val_type) {
505 case ASN_INTEGER:
506 p = asn_build_int(p, bufsz_p, id, *((int *)val));
507 if (p == NULL)
508 return (NULL);
509 break;
510
511 case ASN_OBJECT_ID:
512 p = asn_build_objid(p, bufsz_p, id, val,
513 val_len / sizeof (oid));
514 if (p == NULL)
515 return (NULL);
516 break;
517
518 case ASN_OCTET_STR:
519 p = asn_build_string(p, bufsz_p, id, (uchar_t *)val, val_len);
520 if (p == NULL)
521 return (NULL);
522 break;
523
524 case ASN_NULL:
525 if ((p = asn_build_null(p, bufsz_p, id)) == NULL)
526 return (NULL);
527 break;
528
529 default:
530 return (NULL);
531 }
532
533 /*
534 * Rebuild the variable sequence length
535 */
536 seqlen = p - varseq_end;
537 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
538 (void) asn_build_sequence(buf, NULL, id, seqlen);
539
540 return (p);
541 }
542
543 /*
544 * Parse the PDU portion of the incoming snmp message into the reply_pdu.
545 * Space for all structure members are allocated as needed and must be freed
546 * by the caller when these are no longer needed.
547 */
548 static uchar_t *
snmp_parse_pdu(int reqid,uchar_t * msg,size_t * msgsz_p,snmp_pdu_t * reply_pdu)549 snmp_parse_pdu(int reqid, uchar_t *msg, size_t *msgsz_p, snmp_pdu_t *reply_pdu)
550 {
551 uchar_t *p;
552 uchar_t id, exp_id;
553 pdu_varlist_t *newvp, *vp = NULL;
554
555 /*
556 * Parse the PDU header out of the message
557 */
558 if ((p = asn_parse_header(msg, msgsz_p, &id)) == NULL)
559 return (NULL);
560 if (id != SNMP_MSG_RESPONSE && id != SNMP_MSG_REPORT)
561 return (NULL);
562 reply_pdu->command = (int)id;
563
564 /*
565 * Parse the request id and verify that this is the response
566 * we're expecting.
567 */
568 if ((p = asn_parse_int(p, msgsz_p, &reply_pdu->reqid)) == NULL)
569 return (NULL);
570 if (reply_pdu->reqid != reqid)
571 return (NULL);
572
573 /*
574 * Parse the error-status and error-index values
575 */
576 if ((p = asn_parse_int(p, msgsz_p, &reply_pdu->errstat)) == NULL)
577 return (NULL);
578 if ((p = asn_parse_int(p, msgsz_p, &reply_pdu->errindex)) == NULL)
579 return (NULL);
580
581 /*
582 * Parse the header for the variables list sequence.
583 */
584 exp_id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
585 if ((p = asn_parse_sequence(p, msgsz_p, exp_id)) == NULL)
586 return (NULL);
587
588 while (((int)*msgsz_p) > 0) {
589 if ((newvp = calloc(1, sizeof (pdu_varlist_t))) == NULL)
590 return (NULL);
591
592 if (vp == NULL)
593 reply_pdu->vars = newvp;
594 else
595 vp->nextvar = newvp;
596
597 vp = newvp;
598 if ((p = snmp_parse_variable(p, msgsz_p, vp)) == NULL)
599 return (NULL);
600 }
601
602 return (p);
603 }
604
605 /*
606 * Allocate and parse the next variable into the varlist
607 */
608 static uchar_t *
snmp_parse_variable(uchar_t * msg,size_t * msgsz_p,pdu_varlist_t * vp)609 snmp_parse_variable(uchar_t *msg, size_t *msgsz_p, pdu_varlist_t *vp)
610 {
611 uchar_t *p;
612 uchar_t exp_id;
613
614 /*
615 * Parse this variable's sequence
616 */
617 exp_id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
618 if ((p = asn_parse_sequence(msg, msgsz_p, exp_id)) == NULL)
619 return (NULL);
620
621 /*
622 * Parse the variable's object identifier
623 */
624 p = asn_parse_objid(p, msgsz_p, &vp->name, &vp->name_len);
625 if (p == NULL)
626 return (NULL);
627
628 /*
629 * Parse the object's value
630 */
631 if ((p = asn_parse_objval(p, msgsz_p, vp)) == NULL)
632 return (NULL);
633
634 return (p);
635 }
636
637 void
snmp_free_pdu(snmp_pdu_t * pdu)638 snmp_free_pdu(snmp_pdu_t *pdu)
639 {
640 pdu_varlist_t *vp, *nxt;
641
642 if (pdu) {
643 if ((pdu->community) && (pdu->community != snmp_def_community))
644 free((void *) pdu->community);
645
646 for (vp = pdu->vars; vp; vp = nxt) {
647 nxt = vp->nextvar;
648
649 if (vp->name)
650 free((void *) vp->name);
651 if (vp->val.str)
652 free((void *) vp->val.str);
653 free((void *) vp);
654 }
655
656 if (pdu->req_pkt)
657 free((void *) pdu->req_pkt);
658
659 if (pdu->reply_pkt)
660 free((void *) pdu->reply_pkt);
661
662 free((void *) pdu);
663 }
664 }
665
666 static void
snmp_free_null_vars(pdu_varlist_t * varblock_p)667 snmp_free_null_vars(pdu_varlist_t *varblock_p)
668 {
669 pdu_varlist_t *vp, *nxt;
670
671 for (vp = varblock_p; vp; vp = nxt) {
672 nxt = vp->nextvar;
673
674 if (vp->name)
675 free(vp->name);
676 free(vp);
677 }
678 }
679