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 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* 27 * Msgbuf buffer management implementation. The smb_msgbuf interface is 28 * typically used to encode or decode SMB data using sprintf/scanf 29 * style operations. It contains special handling for the SMB header. 30 * It can also be used for general purpose encoding and decoding. 31 */ 32 33 #include <sys/types.h> 34 #include <sys/varargs.h> 35 #include <sys/byteorder.h> 36 #ifndef _KERNEL 37 #include <stdlib.h> 38 #include <syslog.h> 39 #include <string.h> 40 #include <strings.h> 41 #else 42 #include <sys/sunddi.h> 43 #include <sys/kmem.h> 44 #endif 45 #include <smbsrv/string.h> 46 #include <smbsrv/msgbuf.h> 47 #include <smbsrv/smb.h> 48 49 static int buf_decode(smb_msgbuf_t *, char *, va_list ap); 50 static int buf_encode(smb_msgbuf_t *, char *, va_list ap); 51 static void *smb_msgbuf_malloc(smb_msgbuf_t *, size_t); 52 static int smb_msgbuf_chkerc(char *text, int erc); 53 static void buf_decode_wcs(smb_wchar_t *, smb_wchar_t *, int wcstrlen); 54 55 /* 56 * Returns the offset or number of bytes used within the buffer. 57 */ 58 size_t 59 smb_msgbuf_used(smb_msgbuf_t *mb) 60 { 61 /*LINTED E_PTRDIFF_OVERFLOW*/ 62 return (mb->scan - mb->base); 63 } 64 65 /* 66 * Returns the actual buffer size. 67 */ 68 size_t 69 smb_msgbuf_size(smb_msgbuf_t *mb) 70 { 71 return (mb->max); 72 } 73 74 uint8_t * 75 smb_msgbuf_base(smb_msgbuf_t *mb) 76 { 77 return (mb->base); 78 } 79 80 /* 81 * Ensure that the scan is aligned on a word (16-bit) boundary. 82 */ 83 void 84 smb_msgbuf_word_align(smb_msgbuf_t *mb) 85 { 86 mb->scan = (uint8_t *)((uintptr_t)(mb->scan + 1) & ~1); 87 } 88 89 /* 90 * Ensure that the scan is aligned on a dword (32-bit) boundary. 91 */ 92 void 93 smb_msgbuf_dword_align(smb_msgbuf_t *mb) 94 { 95 mb->scan = (uint8_t *)((uintptr_t)(mb->scan + 3) & ~3); 96 } 97 98 /* 99 * Checks whether or not the buffer has space for the amount of data 100 * specified. Returns 1 if there is space, otherwise returns 0. 101 */ 102 int 103 smb_msgbuf_has_space(smb_msgbuf_t *mb, size_t size) 104 { 105 if (size > mb->max || (mb->scan + size) > mb->end) 106 return (0); 107 108 return (1); 109 } 110 111 /* 112 * Set flags the smb_msgbuf. 113 */ 114 void 115 smb_msgbuf_fset(smb_msgbuf_t *mb, uint32_t flags) 116 { 117 mb->flags |= flags; 118 } 119 120 /* 121 * Clear flags the smb_msgbuf. 122 */ 123 void 124 smb_msgbuf_fclear(smb_msgbuf_t *mb, uint32_t flags) 125 { 126 mb->flags &= ~flags; 127 } 128 129 /* 130 * smb_msgbuf_init 131 * 132 * Initialize a smb_msgbuf_t structure based on the buffer and size 133 * specified. Both scan and base initially point to the beginning 134 * of the buffer and end points to the limit of the buffer. As 135 * data is added scan should be incremented to point to the next 136 * offset at which data will be written. Max and count are set 137 * to the actual buffer size. 138 */ 139 void 140 smb_msgbuf_init(smb_msgbuf_t *mb, uint8_t *buf, size_t size, uint32_t flags) 141 { 142 mb->scan = mb->base = buf; 143 mb->max = mb->count = size; 144 mb->end = &buf[size]; 145 mb->flags = flags; 146 mb->mlist.next = 0; 147 } 148 149 150 /* 151 * smb_msgbuf_term 152 * 153 * Destruct a smb_msgbuf_t. Free any memory hanging off the mlist. 154 */ 155 void 156 smb_msgbuf_term(smb_msgbuf_t *mb) 157 { 158 smb_msgbuf_mlist_t *item = mb->mlist.next; 159 smb_msgbuf_mlist_t *tmp; 160 161 while (item) { 162 tmp = item; 163 item = item->next; 164 #ifndef _KERNEL 165 free(tmp); 166 #else 167 kmem_free(tmp, tmp->size); 168 #endif 169 } 170 } 171 172 173 /* 174 * smb_msgbuf_decode 175 * 176 * Decode a smb_msgbuf buffer as indicated by the format string into 177 * the variable arg list. This is similar to a scanf operation. 178 * 179 * On success, returns the number of bytes encoded. Otherwise 180 * returns a -ve error code. 181 */ 182 int 183 smb_msgbuf_decode(smb_msgbuf_t *mb, char *fmt, ...) 184 { 185 int rc; 186 uint8_t *orig_scan; 187 va_list ap; 188 189 va_start(ap, fmt); 190 orig_scan = mb->scan; 191 rc = buf_decode(mb, fmt, ap); 192 va_end(ap); 193 194 if (rc != SMB_MSGBUF_SUCCESS) { 195 (void) smb_msgbuf_chkerc("smb_msgbuf_decode", rc); 196 mb->scan = orig_scan; 197 return (rc); 198 } 199 200 /*LINTED E_PTRDIFF_OVERFLOW*/ 201 return (mb->scan - orig_scan); 202 } 203 204 205 /* 206 * buf_decode 207 * 208 * Private decode function, where the real work of decoding the smb_msgbuf 209 * is done. This function should only be called via smb_msgbuf_decode to 210 * ensure correct behaviour and error handling. 211 */ 212 static int 213 buf_decode(smb_msgbuf_t *mb, char *fmt, va_list ap) 214 { 215 uint32_t ival; 216 uint8_t c; 217 uint8_t *cvalp; 218 uint8_t **cvalpp; 219 uint16_t *wvalp; 220 uint32_t *lvalp; 221 uint64_t *llvalp; 222 smb_wchar_t *wcs; 223 int repc; 224 int rc; 225 226 while ((c = *fmt++) != 0) { 227 repc = 1; 228 229 if (c == ' ' || c == '\t') 230 continue; 231 232 if (c == '(') { 233 while (((c = *fmt++) != 0) && c != ')') 234 ; 235 236 if (!c) 237 return (SMB_MSGBUF_SUCCESS); 238 239 continue; 240 } 241 242 if ('0' <= c && c <= '9') { 243 repc = 0; 244 do { 245 repc = repc * 10 + c - '0'; 246 c = *fmt++; 247 } while ('0' <= c && c <= '9'); 248 } else if (c == '#') { 249 repc = va_arg(ap, int); 250 c = *fmt++; 251 } 252 253 switch (c) { 254 case '.': 255 if (smb_msgbuf_has_space(mb, repc) == 0) 256 return (SMB_MSGBUF_UNDERFLOW); 257 258 mb->scan += repc; 259 break; 260 261 case 'c': 262 if (smb_msgbuf_has_space(mb, repc) == 0) 263 return (SMB_MSGBUF_UNDERFLOW); 264 265 cvalp = va_arg(ap, uint8_t *); 266 bcopy(mb->scan, cvalp, repc); 267 mb->scan += repc; 268 break; 269 270 case 'b': 271 if (smb_msgbuf_has_space(mb, repc) == 0) 272 return (SMB_MSGBUF_UNDERFLOW); 273 274 cvalp = va_arg(ap, uint8_t *); 275 while (repc-- > 0) { 276 *cvalp++ = *mb->scan++; 277 } 278 break; 279 280 case 'w': 281 rc = smb_msgbuf_has_space(mb, repc * sizeof (uint16_t)); 282 if (rc == 0) 283 return (SMB_MSGBUF_UNDERFLOW); 284 285 wvalp = va_arg(ap, uint16_t *); 286 while (repc-- > 0) { 287 *wvalp++ = LE_IN16(mb->scan); 288 mb->scan += sizeof (uint16_t); 289 } 290 break; 291 292 case 'l': 293 rc = smb_msgbuf_has_space(mb, repc * sizeof (int32_t)); 294 if (rc == 0) 295 return (SMB_MSGBUF_UNDERFLOW); 296 297 lvalp = va_arg(ap, uint32_t *); 298 while (repc-- > 0) { 299 *lvalp++ = LE_IN32(mb->scan); 300 mb->scan += sizeof (int32_t); 301 } 302 break; 303 304 case 'q': 305 rc = smb_msgbuf_has_space(mb, repc * sizeof (int64_t)); 306 if (rc == 0) 307 return (SMB_MSGBUF_UNDERFLOW); 308 309 llvalp = va_arg(ap, uint64_t *); 310 while (repc-- > 0) { 311 *llvalp++ = LE_IN64(mb->scan); 312 mb->scan += sizeof (int64_t); 313 } 314 break; 315 316 case 'u': /* Convert from unicode if flags are set */ 317 if (mb->flags & SMB_MSGBUF_UNICODE) 318 goto unicode_translation; 319 /*FALLTHROUGH*/ 320 321 case 's': 322 ival = strlen((const char *)mb->scan) + 1; 323 if (smb_msgbuf_has_space(mb, ival) == 0) 324 return (SMB_MSGBUF_UNDERFLOW); 325 326 if ((cvalp = smb_msgbuf_malloc(mb, ival * 2)) == 0) 327 return (SMB_MSGBUF_UNDERFLOW); 328 329 if ((ival = smb_stombs((char *)cvalp, 330 (char *)mb->scan, ival * 2)) == 331 (uint32_t)-1) { 332 return (SMB_MSGBUF_DATA_ERROR); 333 } 334 335 cvalpp = va_arg(ap, uint8_t **); 336 *cvalpp = cvalp; 337 mb->scan += (ival+1); 338 break; 339 340 case 'U': /* Convert from unicode */ 341 unicode_translation: 342 /* 343 * Unicode strings are always word aligned. 344 * The malloc'd area is larger than the 345 * original string because the UTF-8 chars 346 * may be longer than the wide-chars. 347 */ 348 smb_msgbuf_word_align(mb); 349 /*LINTED E_BAD_PTR_CAST_ALIGN*/ 350 wcs = (smb_wchar_t *)mb->scan; 351 352 /* count the null wchar */ 353 repc = sizeof (smb_wchar_t); 354 while (*wcs++) 355 repc += sizeof (smb_wchar_t); 356 357 if (smb_msgbuf_has_space(mb, repc) == 0) 358 return (SMB_MSGBUF_UNDERFLOW); 359 360 /* Decode wchar string into host byte-order */ 361 if ((wcs = smb_msgbuf_malloc(mb, repc)) == 0) 362 return (SMB_MSGBUF_UNDERFLOW); 363 364 /*LINTED E_BAD_PTR_CAST_ALIGN*/ 365 buf_decode_wcs(wcs, (smb_wchar_t *)mb->scan, 366 repc / sizeof (smb_wchar_t)); 367 368 /* Get space for translated string */ 369 if ((cvalp = smb_msgbuf_malloc(mb, repc * 2)) == 0) 370 return (SMB_MSGBUF_UNDERFLOW); 371 372 /* Translate string */ 373 (void) smb_wcstombs((char *)cvalp, wcs, repc * 2); 374 375 cvalpp = va_arg(ap, uint8_t **); 376 *cvalpp = cvalp; 377 mb->scan += repc; 378 break; 379 380 case 'M': 381 if (smb_msgbuf_has_space(mb, 4) == 0) 382 return (SMB_MSGBUF_UNDERFLOW); 383 384 if (mb->scan[0] != 0xFF || 385 mb->scan[1] != 'S' || 386 mb->scan[2] != 'M' || 387 mb->scan[3] != 'B') { 388 return (SMB_MSGBUF_INVALID_HEADER); 389 } 390 mb->scan += 4; 391 break; 392 393 default: 394 return (SMB_MSGBUF_INVALID_FORMAT); 395 } 396 } 397 398 return (SMB_MSGBUF_SUCCESS); 399 } 400 401 402 /* 403 * smb_msgbuf_encode 404 * 405 * Encode a smb_msgbuf buffer as indicated by the format string using 406 * the variable arg list. This is similar to a sprintf operation. 407 * 408 * On success, returns the number of bytes encoded. Otherwise 409 * returns a -ve error code. 410 */ 411 int 412 smb_msgbuf_encode(smb_msgbuf_t *mb, char *fmt, ...) 413 { 414 int rc; 415 uint8_t *orig_scan; 416 va_list ap; 417 418 va_start(ap, fmt); 419 orig_scan = mb->scan; 420 rc = buf_encode(mb, fmt, ap); 421 va_end(ap); 422 423 if (rc != SMB_MSGBUF_SUCCESS) { 424 (void) smb_msgbuf_chkerc("smb_msgbuf_encode", rc); 425 mb->scan = orig_scan; 426 return (rc); 427 } 428 429 /*LINTED E_PTRDIFF_OVERFLOW*/ 430 return (mb->scan - orig_scan); 431 } 432 433 434 /* 435 * buf_encode 436 * 437 * Private encode function, where the real work of encoding the smb_msgbuf 438 * is done. This function should only be called via smb_msgbuf_encode to 439 * ensure correct behaviour and error handling. 440 */ 441 static int 442 buf_encode(smb_msgbuf_t *mb, char *fmt, va_list ap) 443 { 444 uint8_t cval; 445 uint16_t wval; 446 uint32_t lval; 447 uint64_t llval; 448 uint32_t ival; 449 uint8_t *cvalp; 450 uint8_t c; 451 smb_wchar_t wcval; 452 int count; 453 int repc = 1; 454 int rc; 455 456 while ((c = *fmt++) != 0) { 457 repc = 1; 458 459 if (c == ' ' || c == '\t') 460 continue; 461 462 if (c == '(') { 463 while (((c = *fmt++) != 0) && c != ')') 464 ; 465 466 if (!c) 467 return (SMB_MSGBUF_SUCCESS); 468 469 continue; 470 } 471 472 if ('0' <= c && c <= '9') { 473 repc = 0; 474 do { 475 repc = repc * 10 + c - '0'; 476 c = *fmt++; 477 } while ('0' <= c && c <= '9'); 478 } else if (c == '#') { 479 repc = va_arg(ap, int); 480 c = *fmt++; 481 } 482 483 switch (c) { 484 case '.': 485 if (smb_msgbuf_has_space(mb, repc) == 0) 486 return (SMB_MSGBUF_OVERFLOW); 487 488 while (repc-- > 0) 489 *mb->scan++ = 0; 490 break; 491 492 case 'c': 493 if (smb_msgbuf_has_space(mb, repc) == 0) 494 return (SMB_MSGBUF_OVERFLOW); 495 496 cvalp = va_arg(ap, uint8_t *); 497 bcopy(cvalp, mb->scan, repc); 498 mb->scan += repc; 499 break; 500 501 case 'b': 502 if (smb_msgbuf_has_space(mb, repc) == 0) 503 return (SMB_MSGBUF_OVERFLOW); 504 505 while (repc-- > 0) { 506 cval = va_arg(ap, int); 507 *mb->scan++ = cval; 508 } 509 break; 510 511 case 'w': 512 rc = smb_msgbuf_has_space(mb, repc * sizeof (uint16_t)); 513 if (rc == 0) 514 return (SMB_MSGBUF_OVERFLOW); 515 516 while (repc-- > 0) { 517 wval = va_arg(ap, int); 518 LE_OUT16(mb->scan, wval); 519 mb->scan += sizeof (uint16_t); 520 } 521 break; 522 523 case 'l': 524 rc = smb_msgbuf_has_space(mb, repc * sizeof (int32_t)); 525 if (rc == 0) 526 return (SMB_MSGBUF_OVERFLOW); 527 528 while (repc-- > 0) { 529 lval = va_arg(ap, uint32_t); 530 LE_OUT32(mb->scan, lval); 531 mb->scan += sizeof (int32_t); 532 } 533 break; 534 535 case 'q': 536 rc = smb_msgbuf_has_space(mb, repc * sizeof (int64_t)); 537 if (rc == 0) 538 return (SMB_MSGBUF_OVERFLOW); 539 540 while (repc-- > 0) { 541 llval = va_arg(ap, uint64_t); 542 LE_OUT64(mb->scan, llval); 543 mb->scan += sizeof (uint64_t); 544 } 545 break; 546 547 case 'u': /* conditional unicode */ 548 if (mb->flags & SMB_MSGBUF_UNICODE) 549 goto unicode_translation; 550 /* FALLTHROUGH */ 551 552 case 's': 553 cvalp = va_arg(ap, uint8_t *); 554 ival = strlen((const char *)cvalp) + 1; 555 556 if (smb_msgbuf_has_space(mb, ival) == 0) 557 return (SMB_MSGBUF_OVERFLOW); 558 559 ival = 560 smb_mbstos((char *)mb->scan, (const char *)cvalp); 561 mb->scan += ival + 1; 562 break; 563 564 case 'U': /* unicode */ 565 unicode_translation: 566 /* 567 * Unicode strings are always word aligned. 568 */ 569 smb_msgbuf_word_align(mb); 570 cvalp = va_arg(ap, uint8_t *); 571 572 for (;;) { 573 rc = smb_msgbuf_has_space(mb, 574 sizeof (smb_wchar_t)); 575 if (rc == 0) 576 return (SMB_MSGBUF_OVERFLOW); 577 578 count = smb_mbtowc(&wcval, (const char *)cvalp, 579 MTS_MB_CHAR_MAX); 580 581 if (count < 0) { 582 return (SMB_MSGBUF_DATA_ERROR); 583 } else if (count == 0) { 584 /* 585 * No longer need to do this now that 586 * mbtowc correctly writes the null 587 * before returning zero but paranoia 588 * wins. 589 */ 590 wcval = 0; 591 count = 1; 592 } 593 594 /* Write wchar in wire-format */ 595 LE_OUT16(mb->scan, wcval); 596 597 if (*cvalp == 0) { 598 /* 599 * End of string. Check to see whether 600 * or not to include the null 601 * terminator. 602 */ 603 if ((mb->flags & SMB_MSGBUF_NOTERM) == 604 0) 605 mb->scan += 606 sizeof (smb_wchar_t); 607 break; 608 } 609 610 mb->scan += sizeof (smb_wchar_t); 611 cvalp += count; 612 } 613 break; 614 615 case 'M': 616 if (smb_msgbuf_has_space(mb, 4) == 0) 617 return (SMB_MSGBUF_OVERFLOW); 618 619 *mb->scan++ = 0xFF; 620 *mb->scan++ = 'S'; 621 *mb->scan++ = 'M'; 622 *mb->scan++ = 'B'; 623 break; 624 625 default: 626 return (SMB_MSGBUF_INVALID_FORMAT); 627 } 628 } 629 630 return (SMB_MSGBUF_SUCCESS); 631 } 632 633 634 /* 635 * smb_msgbuf_malloc 636 * 637 * Allocate some memory for use with this smb_msgbuf. We increase the 638 * requested size to hold the list pointer and return a pointer 639 * to the area for use by the caller. 640 */ 641 static void * 642 smb_msgbuf_malloc(smb_msgbuf_t *mb, size_t size) 643 { 644 smb_msgbuf_mlist_t *item; 645 646 size += sizeof (smb_msgbuf_mlist_t); 647 648 #ifndef _KERNEL 649 if ((item = malloc(size)) == NULL) 650 return (NULL); 651 #else 652 item = kmem_alloc(size, KM_SLEEP); 653 #endif 654 item->next = mb->mlist.next; 655 item->size = size; 656 mb->mlist.next = item; 657 658 /* 659 * The caller gets a pointer to the address 660 * immediately after the smb_msgbuf_mlist_t. 661 */ 662 return ((void *)(item + 1)); 663 } 664 665 666 /* 667 * smb_msgbuf_chkerc 668 * 669 * Diagnostic function to write an appropriate message to the system log. 670 */ 671 static int 672 smb_msgbuf_chkerc(char *text, int erc) 673 { 674 static struct { 675 int erc; 676 char *name; 677 } etable[] = { 678 { SMB_MSGBUF_SUCCESS, "success" }, 679 { SMB_MSGBUF_UNDERFLOW, "overflow/underflow" }, 680 { SMB_MSGBUF_INVALID_FORMAT, "invalid format" }, 681 { SMB_MSGBUF_INVALID_HEADER, "invalid header" }, 682 { SMB_MSGBUF_DATA_ERROR, "data error" } 683 }; 684 685 int i; 686 687 for (i = 0; i < sizeof (etable)/sizeof (etable[0]); ++i) { 688 if (etable[i].erc == erc) { 689 if (text == 0) 690 text = "smb_msgbuf_chkerc"; 691 break; 692 } 693 } 694 return (erc); 695 } 696 697 static void 698 buf_decode_wcs(smb_wchar_t *dst_wcstr, smb_wchar_t *src_wcstr, int wcstrlen) 699 { 700 int i; 701 702 for (i = 0; i < wcstrlen; i++) { 703 *dst_wcstr = LE_IN16(src_wcstr); 704 dst_wcstr++; 705 src_wcstr++; 706 } 707 } 708