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 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * SNMP PDU and packet transport related routines 31 */ 32 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" 40 41 /* 42 * Static declarations 43 */ 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 *); 52 53 static uchar_t *snmp_def_community = (uchar_t *)SNMP_DEF_COMMUNITY; 54 55 /* 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 58 */ 59 snmp_pdu_t * 60 snmp_create_pdu(int cmd, int max_reps, char *oidstrs, int n_oids, int row) 61 { 62 snmp_pdu_t *pdu; 63 64 if ((cmd != SNMP_MSG_GET) && (cmd != SNMP_MSG_GETNEXT) && 65 (cmd != SNMP_MSG_GETBULK)) { 66 return (NULL); 67 } 68 69 pdu = (snmp_pdu_t *)calloc(1, sizeof (snmp_pdu_t)); 70 if (pdu == NULL) 71 return (NULL); 72 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; 82 } 83 84 pdu->command = cmd; 85 pdu->reqid = snmp_get_reqid(); 86 pdu->community = snmp_def_community; 87 pdu->community_len = SNMP_DEF_COMMUNITY_LEN; 88 89 if (snmp_add_null_vars(pdu, oidstrs, n_oids, row) < 0) { 90 free((void *) pdu); 91 return (NULL); 92 } 93 94 pdu->req_pkt = NULL; 95 pdu->req_pktsz = 0; 96 pdu->reply_pkt = NULL; 97 pdu->reply_pktsz = 0; 98 99 return (pdu); 100 } 101 102 /* 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). 109 */ 110 int 111 snmp_make_packet(snmp_pdu_t *pdu) 112 { 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; 118 119 if ((buf = (uchar_t *)calloc(1, SNMP_DEF_PKTBUF_SZ)) == NULL) 120 return (-1); 121 122 /* 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. 126 */ 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); 131 } 132 msg_seq_end = p; 133 134 /* 135 * Store the version 136 */ 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); 141 } 142 143 /* 144 * Store the community string 145 */ 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); 151 } 152 153 /* 154 * Build the PDU 155 */ 156 if ((p = snmp_build_pdu(pdu, p, &bufsz)) == NULL) { 157 free((void *) buf); 158 return (-1); 159 } 160 161 /* 162 * Complete the message pkt by updating the message sequence length 163 */ 164 seqlen = p - msg_seq_end; 165 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE; 166 (void) asn_build_sequence(buf, NULL, id, seqlen); 167 168 /* 169 * Calculate packet size and return 170 */ 171 pdu->req_pkt = buf; 172 pdu->req_pktsz = p - buf; 173 174 return (0); 175 } 176 177 /* 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. 183 */ 184 snmp_pdu_t * 185 snmp_parse_reply(int reqid, uchar_t *reply_pkt, size_t reply_pktsz) 186 { 187 snmp_pdu_t *reply_pdu; 188 uchar_t *p; 189 size_t msgsz = reply_pktsz; 190 uchar_t exp_id; 191 192 reply_pdu = (snmp_pdu_t *)calloc(1, sizeof (snmp_pdu_t)); 193 if (reply_pdu == NULL) 194 return (NULL); 195 196 /* 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. 200 */ 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); 205 } 206 207 /* 208 * Now try to parse the version out of the packet 209 */ 210 if ((p = asn_parse_int(p, &msgsz, &reply_pdu->version)) == NULL) { 211 snmp_free_pdu(reply_pdu); 212 return (NULL); 213 } 214 if ((reply_pdu->version != SNMP_VERSION_1) && 215 (reply_pdu->version != SNMP_VERSION_2c)) { 216 snmp_free_pdu(reply_pdu); 217 return (NULL); 218 } 219 220 /* 221 * Parse the community string (space allocated by asn_parse_string) 222 */ 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); 228 } 229 230 /* 231 * Parse the PDU part of the message 232 */ 233 if ((p = snmp_parse_pdu(reqid, p, &msgsz, reply_pdu)) == NULL) { 234 snmp_free_pdu(reply_pdu); 235 return (NULL); 236 } 237 238 return (reply_pdu); 239 } 240 241 242 /* 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. 247 */ 248 static int 249 snmp_add_null_vars(snmp_pdu_t *pdu, char *oidstrs, int n_oids, int row) 250 { 251 pdu_varlist_t *vp, *prev; 252 pdu_varlist_t *varblock_p = NULL; 253 char *p; 254 int i; 255 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; 266 } 267 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); 273 } 274 vp->val.str = NULL; 275 vp->val_len = 0; 276 vp->type = ASN_NULL; 277 vp->nextvar = NULL; 278 279 LOGVAR(TAG_NULL_VAR, vp); 280 281 prev = vp; 282 p += strlen(p) + 1; 283 } 284 285 /* 286 * append the varlist to the PDU 287 */ 288 if (pdu->vars == NULL) 289 pdu->vars = varblock_p; 290 else { 291 for (vp = pdu->vars; vp->nextvar; vp = vp->nextvar) 292 ; 293 vp->nextvar = varblock_p; 294 } 295 296 return (0); 297 } 298 299 /* 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. 304 */ 305 static oid * 306 snmp_oidstr_to_oid(int cmd, char *oidstr, int row, size_t *n_subids) 307 { 308 int i, count; 309 char *p, *q; 310 char *oidstr_dup; 311 oid *objid; 312 313 if ((oidstr == NULL) || (n_subids == NULL)) 314 return (NULL); 315 316 for (count = 1, p = oidstr; p; count++, p++) { 317 if ((p = strchr(p, '.')) == NULL) 318 break; 319 } 320 321 /* 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. 325 */ 326 if ((cmd == SNMP_MSG_GET) || (cmd == SNMP_MSG_GETBULK && row > 0) || 327 (cmd == SNMP_MSG_GETNEXT && row >= 0)) { 328 count++; 329 } 330 331 if ((oidstr_dup = strdup(oidstr)) == NULL) 332 return (NULL); 333 334 objid = (oid *) calloc(count, sizeof (oid)); 335 if (objid == NULL) { 336 free((void *) p); 337 return (NULL); 338 } 339 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; 346 } 347 348 /* 349 * For SNMP_MSG_GET, the leaf subid will simply be the row#. 350 * 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. 357 * 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. 364 */ 365 switch (cmd) { 366 case SNMP_MSG_GET: 367 objid[i] = (oid) row; 368 break; 369 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; 376 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; 383 } 384 385 *n_subids = count; 386 387 free((void *) oidstr_dup); 388 389 return (objid); 390 } 391 392 /* 393 * Builds the PDU part of the snmp message packet. 394 */ 395 static uchar_t * 396 snmp_build_pdu(snmp_pdu_t *pdu, uchar_t *buf, size_t *bufsz_p) 397 { 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; 404 405 /* 406 * Build ASN sequence for the PDU command (length will be 407 * updated later once the entire command is completely formed) 408 */ 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; 414 415 /* 416 * Build the request id 417 */ 418 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER; 419 if ((p = asn_build_int(p, bufsz_p, id, pdu->reqid)) == NULL) 420 return (NULL); 421 422 /* 423 * Build the non-repeaters and max-repetitions for SNMP_MSG_GETBULK 424 * (same as error status and error index for other message types) 425 */ 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); 429 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); 433 434 /* 435 * Build ASN sequence for the variables list (update length 436 * after building the varlist) 437 */ 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; 443 444 /* 445 * Build the variables list 446 */ 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); 452 } 453 454 /* 455 * Now update the varlist sequence length 456 */ 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); 460 461 /* 462 * And finally, update the length for the PDU sequence 463 */ 464 seqlen = p - pdu_seq_end; 465 (void) asn_build_sequence(pdu_seq_begin, NULL, (uchar_t)pdu->command, 466 seqlen); 467 468 return (p); 469 } 470 471 /* 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. 476 */ 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) 480 { 481 uchar_t *p, *varseq_end; 482 size_t seqlen; 483 uchar_t id; 484 485 /* 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! 489 */ 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; 494 495 /* 496 * Build the object id 497 */ 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); 501 502 /* 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. 506 */ 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; 514 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; 521 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; 527 528 case ASN_NULL: 529 if ((p = asn_build_null(p, bufsz_p, id)) == NULL) 530 return (NULL); 531 break; 532 533 default: 534 return (NULL); 535 } 536 537 /* 538 * Rebuild the variable sequence length 539 */ 540 seqlen = p - varseq_end; 541 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE; 542 (void) asn_build_sequence(buf, NULL, id, seqlen); 543 544 return (p); 545 } 546 547 /* 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. 551 */ 552 static uchar_t * 553 snmp_parse_pdu(int reqid, uchar_t *msg, size_t *msgsz_p, snmp_pdu_t *reply_pdu) 554 { 555 uchar_t *p; 556 uchar_t id, exp_id; 557 pdu_varlist_t *newvp, *vp = NULL; 558 559 /* 560 * Parse the PDU header out of the message 561 */ 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; 567 568 /* 569 * Parse the request id and verify that this is the response 570 * we're expecting. 571 */ 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); 576 577 /* 578 * Parse the error-status and error-index values 579 */ 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); 584 585 /* 586 * Parse the header for the variables list sequence. 587 */ 588 exp_id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE; 589 if ((p = asn_parse_sequence(p, msgsz_p, exp_id)) == NULL) 590 return (NULL); 591 592 while (((int)*msgsz_p) > 0) { 593 if ((newvp = calloc(1, sizeof (pdu_varlist_t))) == NULL) 594 return (NULL); 595 596 if (vp == NULL) 597 reply_pdu->vars = newvp; 598 else 599 vp->nextvar = newvp; 600 601 vp = newvp; 602 if ((p = snmp_parse_variable(p, msgsz_p, vp)) == NULL) 603 return (NULL); 604 605 LOGVAR(TAG_RESPONSE_VAR, vp); 606 } 607 608 return (p); 609 } 610 611 /* 612 * Allocate and parse the next variable into the varlist 613 */ 614 static uchar_t * 615 snmp_parse_variable(uchar_t *msg, size_t *msgsz_p, pdu_varlist_t *vp) 616 { 617 uchar_t *p; 618 uchar_t exp_id; 619 620 /* 621 * Parse this variable's sequence 622 */ 623 exp_id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE; 624 if ((p = asn_parse_sequence(msg, msgsz_p, exp_id)) == NULL) 625 return (NULL); 626 627 /* 628 * Parse the variable's object identifier 629 */ 630 p = asn_parse_objid(p, msgsz_p, &vp->name, &vp->name_len); 631 if (p == NULL) 632 return (NULL); 633 634 /* 635 * Parse the object's value 636 */ 637 if ((p = asn_parse_objval(p, msgsz_p, vp)) == NULL) 638 return (NULL); 639 640 return (p); 641 } 642 643 void 644 snmp_free_pdu(snmp_pdu_t *pdu) 645 { 646 pdu_varlist_t *vp, *nxt; 647 648 if (pdu) { 649 if ((pdu->community) && (pdu->community != snmp_def_community)) 650 free((void *) pdu->community); 651 652 for (vp = pdu->vars; vp; vp = nxt) { 653 nxt = vp->nextvar; 654 655 if (vp->name) 656 free((void *) vp->name); 657 if (vp->val.str) 658 free((void *) vp->val.str); 659 free((void *) vp); 660 } 661 662 if (pdu->req_pkt) 663 free((void *) pdu->req_pkt); 664 665 if (pdu->reply_pkt) 666 free((void *) pdu->reply_pkt); 667 668 free((void *) pdu); 669 } 670 } 671 672 static void 673 snmp_free_null_vars(pdu_varlist_t *varblock_p) 674 { 675 pdu_varlist_t *vp, *nxt; 676 677 for (vp = varblock_p; vp; vp = nxt) { 678 nxt = vp->nextvar; 679 680 if (vp->name) 681 free(vp->name); 682 free(vp); 683 } 684 } 685