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