xref: /illumos-gate/usr/src/cmd/smbsrv/smbd/smbd_ntlmssp.c (revision 5328fc53d11d7151861fa272e4fb0248b8f0e145)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
14  */
15 
16 /*
17  * SPNEGO back-end for NTLMSSP.  See [MS-NLMP]
18  */
19 
20 #include <sys/types.h>
21 #include <sys/byteorder.h>
22 #include <strings.h>
23 #include "smbd.h"
24 #include "smbd_authsvc.h"
25 #include "netsmb/ntlmssp.h"
26 #include <assert.h>
27 
28 /* A shorter alias for a crazy long name from [MS-NLMP] */
29 #define	NTLMSSP_NEGOTIATE_NTLM2 \
30 	NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY
31 
32 /* Need this in a header somewhere */
33 #ifdef _LITTLE_ENDIAN
34 /* little-endian values on little-endian */
35 #define	htolel(x)	((uint32_t)(x))
36 #define	letohl(x)	((uint32_t)(x))
37 #else	/* (BYTE_ORDER == LITTLE_ENDIAN) */
38 /* little-endian values on big-endian (swap) */
39 #define	letohl(x)	BSWAP_32(x)
40 #define	htolel(x)	BSWAP_32(x)
41 #endif	/* (BYTE_ORDER == LITTLE_ENDIAN) */
42 
43 typedef struct ntlmssp_backend {
44 	uint32_t expect_type;
45 	uint32_t clnt_flags;
46 	uint32_t srv_flags;
47 	char srv_challenge[8];
48 } ntlmssp_backend_t;
49 
50 struct genhdr {
51 	char h_id[8];	/* "NTLMSSP" */
52 	uint32_t h_type;
53 };
54 
55 struct sec_buf {
56 	uint16_t sb_length;
57 	uint16_t sb_maxlen;
58 	uint32_t sb_offset;
59 };
60 
61 struct nego_hdr {
62 	char h_id[8];
63 	uint32_t h_type;
64 	uint32_t h_flags;
65 	/* workstation domain, name (place holders) */
66 	uint16_t ws_dom[4];
67 	uint16_t ws_name[4];
68 };
69 
70 struct auth_hdr {
71 	char h_id[8];
72 	uint32_t h_type;
73 	struct sec_buf h_lm_resp;
74 	struct sec_buf h_nt_resp;
75 	struct sec_buf h_domain;
76 	struct sec_buf h_user;
77 	struct sec_buf h_wksta;
78 	struct sec_buf h_essn_key; /* encrypted session key */
79 	uint32_t h_flags;
80 	/* Version struct (optional) */
81 	/* MIC hash (optional) */
82 };
83 
84 /* Allow turning these off for debugging, etc. */
85 int smbd_signing_enabled = 1;
86 
87 int smbd_constant_challenge = 0;
88 static uint8_t constant_chal[8] = {
89     0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 };
90 
91 static int smbd_ntlmssp_negotiate(authsvc_context_t *);
92 static int smbd_ntlmssp_authenticate(authsvc_context_t *);
93 static int encode_avpair_str(smb_msgbuf_t *, uint16_t, char *);
94 static int decode_secbuf_bin(smb_msgbuf_t *, struct sec_buf *, void **);
95 static int decode_secbuf_str(smb_msgbuf_t *, struct sec_buf *, char **);
96 
97 /*
98  * Initialize this context for NTLMSSP, if possible.
99  */
100 int
101 smbd_ntlmssp_init(authsvc_context_t *ctx)
102 {
103 	ntlmssp_backend_t *be;
104 
105 	be = malloc(sizeof (*be));
106 	if (be == 0)
107 		return (NT_STATUS_NO_MEMORY);
108 	bzero(be, sizeof (*be));
109 	be->expect_type = NTLMSSP_MSGTYPE_NEGOTIATE;
110 	ctx->ctx_backend = be;
111 
112 	return (0);
113 }
114 
115 void
116 smbd_ntlmssp_fini(authsvc_context_t *ctx)
117 {
118 	free(ctx->ctx_backend);
119 }
120 
121 /*
122  * Handle an auth message
123  */
124 int
125 smbd_ntlmssp_work(authsvc_context_t *ctx)
126 {
127 	struct genhdr *ihdr = ctx->ctx_ibodybuf;
128 	ntlmssp_backend_t *be = ctx->ctx_backend;
129 	uint32_t mtype;
130 	int rc;
131 
132 	if (ctx->ctx_ibodylen < sizeof (*ihdr))
133 		return (NT_STATUS_INVALID_PARAMETER);
134 
135 	if (bcmp(ihdr->h_id, "NTLMSSP", 8))
136 		return (NT_STATUS_INVALID_PARAMETER);
137 	mtype = letohl(ihdr->h_type);
138 	if (mtype != be->expect_type)
139 		return (NT_STATUS_INVALID_PARAMETER);
140 
141 	switch (mtype) {
142 	case NTLMSSP_MSGTYPE_NEGOTIATE:
143 		ctx->ctx_orawtype = LSA_MTYPE_ES_CONT;
144 		rc = smbd_ntlmssp_negotiate(ctx);
145 		break;
146 	case NTLMSSP_MSGTYPE_AUTHENTICATE:
147 		ctx->ctx_orawtype = LSA_MTYPE_ES_DONE;
148 		rc = smbd_ntlmssp_authenticate(ctx);
149 		break;
150 
151 	default:
152 	case NTLMSSP_MSGTYPE_CHALLENGE:
153 		/* Sent by servers, not received. */
154 		rc = NT_STATUS_INVALID_PARAMETER;
155 		break;
156 	}
157 
158 	return (rc);
159 }
160 
161 #if (MAXHOSTNAMELEN < NETBIOS_NAME_SZ)
162 #error "MAXHOSTNAMELEN < NETBIOS_NAME_SZ"
163 #endif
164 
165 /*
166  * Handle an NTLMSSP_MSGTYPE_NEGOTIATE message, and reply
167  * with an NTLMSSP_MSGTYPE_CHALLENGE message.
168  * See: [MS-NLMP] 2.2.1.1, 3.2.5.1.1
169  */
170 static int
171 smbd_ntlmssp_negotiate(authsvc_context_t *ctx)
172 {
173 	char tmp_name[MAXHOSTNAMELEN];
174 	ntlmssp_backend_t *be = ctx->ctx_backend;
175 	struct nego_hdr *ihdr = ctx->ctx_ibodybuf;
176 	smb_msgbuf_t mb;
177 	uint8_t *save_scan;
178 	int secmode;
179 	int mbflags;
180 	int rc;
181 	size_t var_start, var_end;
182 	uint16_t var_size;
183 
184 	if (ctx->ctx_ibodylen < sizeof (*ihdr))
185 		return (NT_STATUS_INVALID_PARAMETER);
186 	be->clnt_flags = letohl(ihdr->h_flags);
187 
188 	/*
189 	 * Looks like we can ignore ws_dom, ws_name.
190 	 * Otherwise would parse those here.
191 	 */
192 
193 	secmode = smb_config_get_secmode();
194 	if (smbd_constant_challenge) {
195 		(void) memcpy(be->srv_challenge, constant_chal,
196 		    sizeof (be->srv_challenge));
197 	} else {
198 		randomize(be->srv_challenge, sizeof (be->srv_challenge));
199 	}
200 
201 	/*
202 	 * Compute srv_flags
203 	 */
204 	be->srv_flags =
205 	    NTLMSSP_REQUEST_TARGET |
206 	    NTLMSSP_NEGOTIATE_NTLM |
207 	    NTLMSSP_NEGOTIATE_TARGET_INFO;
208 	be->srv_flags |= be->clnt_flags & (
209 	    NTLMSSP_NEGOTIATE_NTLM2 |
210 	    NTLMSSP_NEGOTIATE_128 |
211 	    NTLMSSP_NEGOTIATE_KEY_EXCH |
212 	    NTLMSSP_NEGOTIATE_56);
213 
214 	if (smbd_signing_enabled) {
215 		be->srv_flags |= be->clnt_flags & (
216 		    NTLMSSP_NEGOTIATE_SIGN |
217 		    NTLMSSP_NEGOTIATE_SEAL |
218 		    NTLMSSP_NEGOTIATE_ALWAYS_SIGN);
219 	}
220 
221 	if (be->clnt_flags & NTLMSSP_NEGOTIATE_UNICODE)
222 		be->srv_flags |= NTLMSSP_NEGOTIATE_UNICODE;
223 	else if (be->clnt_flags & NTLMSSP_NEGOTIATE_OEM)
224 		be->srv_flags |= NTLMSSP_NEGOTIATE_OEM;
225 
226 	/* LM Key is mutually exclusive with NTLM2 */
227 	if ((be->srv_flags & NTLMSSP_NEGOTIATE_NTLM2) == 0 &&
228 	    (be->clnt_flags & NTLMSSP_NEGOTIATE_LM_KEY) != 0)
229 		be->srv_flags |= NTLMSSP_NEGOTIATE_LM_KEY;
230 
231 	/* Get our "target name" */
232 	if (secmode == SMB_SECMODE_DOMAIN) {
233 		be->srv_flags |= NTLMSSP_TARGET_TYPE_DOMAIN;
234 		rc = smb_getdomainname(tmp_name, NETBIOS_NAME_SZ);
235 	} else {
236 		be->srv_flags |= NTLMSSP_TARGET_TYPE_SERVER;
237 		rc = smb_getnetbiosname(tmp_name, NETBIOS_NAME_SZ);
238 	}
239 	if (rc)
240 		goto errout;
241 
242 	/*
243 	 * Build the NTLMSSP_MSGTYPE_CHALLENGE message.
244 	 */
245 	mbflags = SMB_MSGBUF_NOTERM;
246 	if (be->srv_flags & NTLMSSP_NEGOTIATE_UNICODE)
247 		mbflags |= SMB_MSGBUF_UNICODE;
248 	smb_msgbuf_init(&mb, ctx->ctx_obodybuf, ctx->ctx_obodylen, mbflags);
249 
250 	/*
251 	 * Fixed size parts
252 	 */
253 	rc = smb_msgbuf_encode(
254 	    &mb, "8clwwll8cllwwl",	/* offset, name (fmt) */
255 	    "NTLMSSP",			/* 0: signature (8c) */
256 	    NTLMSSP_MSGTYPE_CHALLENGE,	/* 8: type	(l) */
257 	    0, 0, 0,	/* filled later:   12: target name (wwl) */
258 	    be->srv_flags,		/* 20: flags	(l) */
259 	    be->srv_challenge,		/* 24:		(8c) */
260 	    0, 0,			/* 32: reserved (ll) */
261 	    0, 0, 0);	/* filled later:   40: target info (wwl) */
262 #define	TARGET_NAME_OFFSET	12
263 #define	TARGET_INFO_OFFSET	40
264 	if (rc < 0)
265 		goto errout;
266 
267 	/*
268 	 * Variable length parts.
269 	 *
270 	 * Target name
271 	 */
272 	var_start = smb_msgbuf_used(&mb);
273 	rc = smb_msgbuf_encode(&mb, "u", tmp_name);
274 	var_end = smb_msgbuf_used(&mb);
275 	var_size = (uint16_t)(var_end - var_start);
276 	if (rc < 0)
277 		goto errout;
278 
279 	/* overwrite target name offset+lengths */
280 	save_scan = mb.scan;
281 	mb.scan = mb.base + TARGET_NAME_OFFSET;
282 	(void) smb_msgbuf_encode(&mb, "wwl", var_size, var_size, var_start);
283 	mb.scan = save_scan;
284 
285 	/*
286 	 * Target info (AvPairList)
287 	 *
288 	 * These AV pairs are like our name/value pairs, but have
289 	 * numeric identifiers instead of names.  There are many
290 	 * of these, but we put only the four expected by Windows:
291 	 *	NetBIOS computer name
292 	 *	NetBIOS domain name
293 	 *	DNS computer name
294 	 *	DNS domain name
295 	 * Note that "domain" above (even "DNS domain") refers to
296 	 * the AD domain of which we're a member, which may be
297 	 * _different_ from the configured DNS domain.
298 	 *
299 	 * Also note that in "workgroup" mode (not a domain member)
300 	 * all "domain" fields should be set to the same values as
301 	 * the "computer" fields ("bare" host name, not FQDN).
302 	 */
303 	var_start = smb_msgbuf_used(&mb);
304 
305 	/* NetBIOS Computer Name */
306 	if (smb_getnetbiosname(tmp_name, NETBIOS_NAME_SZ))
307 		goto errout;
308 	if (encode_avpair_str(&mb, MsvAvNbComputerName, tmp_name) < 0)
309 		goto errout;
310 
311 	if (secmode != SMB_SECMODE_DOMAIN) {
312 		/*
313 		 * Workgroup mode.  Set all to hostname.
314 		 * tmp_name = netbios hostname from above.
315 		 */
316 		if (encode_avpair_str(&mb, MsvAvNbDomainName, tmp_name) < 0)
317 			goto errout;
318 		/*
319 		 * Want the bare computer name here (not FQDN).
320 		 */
321 		if (smb_gethostname(tmp_name, MAXHOSTNAMELEN, SMB_CASE_LOWER))
322 			goto errout;
323 		if (encode_avpair_str(&mb, MsvAvDnsComputerName, tmp_name) < 0)
324 			goto errout;
325 		if (encode_avpair_str(&mb, MsvAvDnsDomainName, tmp_name) < 0)
326 			goto errout;
327 	} else {
328 		/*
329 		 * Domain mode.  Use real host and domain values.
330 		 */
331 
332 		/* NetBIOS Domain Name */
333 		if (smb_getdomainname(tmp_name, NETBIOS_NAME_SZ))
334 			goto errout;
335 		if (encode_avpair_str(&mb, MsvAvNbDomainName, tmp_name) < 0)
336 			goto errout;
337 
338 		/* DNS Computer Name */
339 		if (smb_getfqhostname(tmp_name, MAXHOSTNAMELEN))
340 			goto errout;
341 		if (encode_avpair_str(&mb, MsvAvDnsComputerName, tmp_name) < 0)
342 			goto errout;
343 
344 		/* DNS Domain Name */
345 		if (smb_getfqdomainname(tmp_name, MAXHOSTNAMELEN))
346 			goto errout;
347 		if (encode_avpair_str(&mb, MsvAvDnsDomainName, tmp_name) < 0)
348 			goto errout;
349 	}
350 
351 	/* End marker */
352 	if (smb_msgbuf_encode(&mb, "ww", MsvAvEOL, 0) < 0)
353 		goto errout;
354 	var_end = smb_msgbuf_used(&mb);
355 	var_size = (uint16_t)(var_end - var_start);
356 
357 	/* overwrite target  offset+lengths */
358 	save_scan = mb.scan;
359 	mb.scan = mb.base + TARGET_INFO_OFFSET;
360 	(void) smb_msgbuf_encode(&mb, "wwl", var_size, var_size, var_start);
361 	mb.scan = save_scan;
362 
363 	ctx->ctx_obodylen = smb_msgbuf_used(&mb);
364 	smb_msgbuf_term(&mb);
365 
366 	be->expect_type = NTLMSSP_MSGTYPE_AUTHENTICATE;
367 
368 	return (0);
369 
370 errout:
371 	smb_msgbuf_term(&mb);
372 	return (NT_STATUS_INTERNAL_ERROR);
373 }
374 
375 static int
376 encode_avpair_str(smb_msgbuf_t *mb, uint16_t AvId, char *name)
377 {
378 	int rc;
379 	uint16_t len;
380 
381 	len = smb_wcequiv_strlen(name);
382 	rc = smb_msgbuf_encode(mb, "wwU", AvId, len, name);
383 	return (rc);
384 }
385 
386 /*
387  * Handle an NTLMSSP_MSGTYPE_AUTHENTICATE message.
388  * See: [MS-NLMP] 2.2.1.3, 3.2.5.1.2
389  */
390 static int
391 smbd_ntlmssp_authenticate(authsvc_context_t *ctx)
392 {
393 	struct auth_hdr hdr;
394 	smb_msgbuf_t mb;
395 	smb_logon_t	user_info;
396 	smb_token_t	*token = NULL;
397 	ntlmssp_backend_t *be = ctx->ctx_backend;
398 	void *lm_resp;
399 	void *nt_resp;
400 	char *domain;
401 	char *user;
402 	char *wksta;
403 	void *essn_key;	/* encrypted session key (optional) */
404 	int mbflags;
405 	uint_t status = NT_STATUS_INTERNAL_ERROR;
406 	char combined_challenge[SMBAUTH_CHAL_SZ];
407 	unsigned char kxkey[SMBAUTH_HASH_SZ];
408 	boolean_t ntlm_v1x = B_FALSE;
409 
410 	bzero(&user_info, sizeof (user_info));
411 
412 	/*
413 	 * Parse the NTLMSSP_MSGTYPE_AUTHENTICATE message.
414 	 */
415 	if (ctx->ctx_ibodylen < sizeof (hdr))
416 		return (NT_STATUS_INVALID_PARAMETER);
417 	mbflags = SMB_MSGBUF_NOTERM;
418 	if (be->srv_flags & NTLMSSP_NEGOTIATE_UNICODE)
419 		mbflags |= SMB_MSGBUF_UNICODE;
420 	smb_msgbuf_init(&mb, ctx->ctx_ibodybuf, ctx->ctx_ibodylen, mbflags);
421 	bzero(&hdr, sizeof (hdr));
422 
423 	if (smb_msgbuf_decode(&mb, "12.") < 0)
424 		goto errout;
425 	if (decode_secbuf_bin(&mb, &hdr.h_lm_resp, &lm_resp) < 0)
426 		goto errout;
427 	if (decode_secbuf_bin(&mb, &hdr.h_nt_resp, &nt_resp) < 0)
428 		goto errout;
429 	if (decode_secbuf_str(&mb, &hdr.h_domain, &domain) < 0)
430 		goto errout;
431 	if (decode_secbuf_str(&mb, &hdr.h_user, &user) < 0)
432 		goto errout;
433 	if (decode_secbuf_str(&mb, &hdr.h_wksta, &wksta) < 0)
434 		goto errout;
435 	if (decode_secbuf_bin(&mb, &hdr.h_essn_key, &essn_key) < 0)
436 		goto errout;
437 	if (smb_msgbuf_decode(&mb, "l", &be->clnt_flags) < 0)
438 		goto errout;
439 
440 	if (be->clnt_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) {
441 		if (hdr.h_essn_key.sb_length < 16 || essn_key == NULL)
442 			goto errout;
443 	}
444 
445 	user_info.lg_level = NETR_NETWORK_LOGON;
446 	user_info.lg_flags = 0;
447 
448 	user_info.lg_ntlm_flags = be->clnt_flags;
449 	user_info.lg_username = (user) ? user : "";
450 	user_info.lg_domain = (domain) ? domain : "";
451 	user_info.lg_workstation = (wksta) ? wksta : "";
452 
453 	user_info.lg_clnt_ipaddr =
454 	    ctx->ctx_clinfo.lci_clnt_ipaddr;
455 	user_info.lg_local_port = 445;
456 
457 	user_info.lg_challenge_key.len = SMBAUTH_CHAL_SZ;
458 	user_info.lg_challenge_key.val = (uint8_t *)be->srv_challenge;
459 
460 	user_info.lg_nt_password.len = hdr.h_nt_resp.sb_length;
461 	user_info.lg_nt_password.val = nt_resp;
462 
463 	user_info.lg_lm_password.len = hdr.h_lm_resp.sb_length;
464 	user_info.lg_lm_password.val = lm_resp;
465 
466 	user_info.lg_native_os = ctx->ctx_clinfo.lci_native_os;
467 	user_info.lg_native_lm = ctx->ctx_clinfo.lci_native_lm;
468 
469 	/*
470 	 * If we're doing extended session security, the challenge
471 	 * this OWF was computed with is different. [MS-NLMP 3.3.1]
472 	 * It's: MD5(concat(ServerChallenge,ClientChallenge))
473 	 * where the ClientChallenge is in the LM resp. field.
474 	 */
475 	if (user_info.lg_nt_password.len == SMBAUTH_LM_RESP_SZ &&
476 	    user_info.lg_lm_password.len >= SMBAUTH_CHAL_SZ &&
477 	    (be->clnt_flags & NTLMSSP_NEGOTIATE_NTLM2) != 0) {
478 		smb_auth_ntlm2_mkchallenge(combined_challenge,
479 		    be->srv_challenge, lm_resp);
480 		user_info.lg_challenge_key.val =
481 		    (uint8_t *)combined_challenge;
482 		user_info.lg_lm_password.len = 0;
483 		ntlm_v1x = B_TRUE;
484 	}
485 
486 	/*
487 	 * This (indirectly) calls smb_auth_validate() to
488 	 * check that the client gave us a valid hash.
489 	 */
490 	token = smbd_user_auth_logon(&user_info);
491 	if (token == NULL) {
492 		status = user_info.lg_status;
493 		if (status == 0) /* should not happen */
494 			status = NT_STATUS_INTERNAL_ERROR;
495 		goto errout;
496 	}
497 
498 	if (token->tkn_ssnkey.val != NULL &&
499 	    token->tkn_ssnkey.len == SMBAUTH_HASH_SZ) {
500 
501 		/*
502 		 * At this point, token->tkn_session_key is the
503 		 * "Session Base Key" [MS-NLMP] 3.2.5.1.2
504 		 * Compute the final session key.  First need the
505 		 * "Key Exchange Key" [MS-NLMP] 3.4.5.1
506 		 */
507 		if (ntlm_v1x) {
508 			smb_auth_ntlm2_kxkey(kxkey,
509 			    be->srv_challenge, lm_resp,
510 			    token->tkn_ssnkey.val);
511 		} else {
512 			/* KXKEY is the Session Base Key. */
513 			(void) memcpy(kxkey, token->tkn_ssnkey.val,
514 			    SMBAUTH_HASH_SZ);
515 		}
516 
517 		/*
518 		 * If the client give us an encrypted session key,
519 		 * decrypt it (RC4) using the "key exchange key".
520 		 */
521 		if (be->clnt_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) {
522 			/* RC4 args: result, key, data */
523 			(void) smb_auth_RC4(token->tkn_ssnkey.val,
524 			    SMBAUTH_HASH_SZ, kxkey, SMBAUTH_HASH_SZ,
525 			    essn_key, hdr.h_essn_key.sb_length);
526 		} else {
527 			/* Final key is the KXKEY */
528 			(void) memcpy(token->tkn_ssnkey.val, kxkey,
529 			    SMBAUTH_HASH_SZ);
530 		}
531 	}
532 
533 	ctx->ctx_token = token;
534 	ctx->ctx_obodylen = 0;
535 
536 	smb_msgbuf_term(&mb);
537 	return (0);
538 
539 errout:
540 	smb_msgbuf_term(&mb);
541 	return (status);
542 }
543 
544 static int
545 decode_secbuf_bin(smb_msgbuf_t *mb, struct sec_buf *sb, void **binp)
546 {
547 	int rc;
548 
549 	*binp = NULL;
550 	rc = smb_msgbuf_decode(
551 	    mb, "wwl",
552 	    &sb->sb_length,
553 	    &sb->sb_maxlen,
554 	    &sb->sb_offset);
555 	if (rc < 0)
556 		return (rc);
557 
558 	if (sb->sb_offset > mb->max)
559 		return (SMB_MSGBUF_UNDERFLOW);
560 	if (sb->sb_length > (mb->max - sb->sb_offset))
561 		return (SMB_MSGBUF_UNDERFLOW);
562 	if (sb->sb_length == 0)
563 		return (rc);
564 
565 	*binp = mb->base + sb->sb_offset;
566 	return (0);
567 }
568 
569 static int
570 decode_secbuf_str(smb_msgbuf_t *mb, struct sec_buf *sb, char **cpp)
571 {
572 	uint8_t *save_scan;
573 	int rc;
574 
575 	*cpp = NULL;
576 	rc = smb_msgbuf_decode(
577 	    mb, "wwl",
578 	    &sb->sb_length,
579 	    &sb->sb_maxlen,
580 	    &sb->sb_offset);
581 	if (rc < 0)
582 		return (rc);
583 
584 	if (sb->sb_offset > mb->max)
585 		return (SMB_MSGBUF_UNDERFLOW);
586 	if (sb->sb_length > (mb->max - sb->sb_offset))
587 		return (SMB_MSGBUF_UNDERFLOW);
588 	if (sb->sb_length == 0)
589 		return (rc);
590 
591 	save_scan = mb->scan;
592 	mb->scan = mb->base + sb->sb_offset;
593 	rc = smb_msgbuf_decode(mb, "#u", (int)sb->sb_length, cpp);
594 	mb->scan = save_scan;
595 
596 	return (rc);
597 }
598