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