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