xref: /illumos-gate/usr/src/common/smbsrv/smb_msgbuf.c (revision b6805bf78d2bbbeeaea8909a05623587b42d58b3)
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