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 * 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 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 * 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 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 * 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 * 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 * 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 * 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 * 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 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 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