xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_dfs.c (revision 7a6d80f1660abd4755c68cbd094d4a914681d26e)
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 2010 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
26  * Copyright 2021 RackTop Systems, Inc.
27  */
28 
29 #include <smbsrv/smb_kproto.h>
30 #include <smbsrv/smb_dfs.h>
31 #include <smbsrv/smb_door.h>
32 #include <smb/winioctl.h>
33 
34 /*
35  * Get Referral response header flags
36  * For exact meaning refer to MS-DFSC spec.
37  *
38  * R: ReferralServers
39  * S: StorageServers
40  * T: TargetFailback
41  */
42 #define	DFS_HDRFLG_R		0x00000001
43 #define	DFS_HDRFLG_S		0x00000002
44 #define	DFS_HDRFLG_T		0x00000004
45 
46 /*
47  * Entry flags
48  */
49 #define	DFS_ENTFLG_T		0x0004
50 
51 /*
52  * Referral entry types/versions
53  */
54 #define	DFS_REFERRAL_V1		0x0001
55 #define	DFS_REFERRAL_V2		0x0002
56 #define	DFS_REFERRAL_V3		0x0003
57 #define	DFS_REFERRAL_V4		0x0004
58 
59 /*
60  * Valid values for ServerType field in referral entries
61  */
62 #define	DFS_SRVTYPE_NONROOT	0x0000
63 #define	DFS_SRVTYPE_ROOT	0x0001
64 
65 /*
66  * Size of the fix part for each referral entry type
67  */
68 #define	DFS_REFV1_ENTSZ		8
69 #define	DFS_REFV2_ENTSZ		22
70 #define	DFS_REFV3_ENTSZ		34
71 #define	DFS_REFV4_ENTSZ		34
72 
73 static dfs_reftype_t smb_dfs_get_reftype(const char *);
74 static void smb_dfs_encode_hdr(mbuf_chain_t *, dfs_info_t *);
75 static uint32_t smb_dfs_encode_refv1(smb_request_t *, mbuf_chain_t *,
76     dfs_info_t *);
77 static uint32_t smb_dfs_encode_refv2(smb_request_t *, mbuf_chain_t *,
78     dfs_info_t *);
79 static uint32_t smb_dfs_encode_refv3x(smb_request_t *, mbuf_chain_t *,
80     dfs_info_t *, uint16_t);
81 static void smb_dfs_encode_targets(mbuf_chain_t *, dfs_info_t *);
82 static uint32_t smb_dfs_referrals_get(smb_request_t *, char *, dfs_reftype_t,
83     dfs_referral_response_t *);
84 static void smb_dfs_referrals_free(dfs_referral_response_t *);
85 static uint16_t smb_dfs_referrals_unclen(dfs_info_t *, uint16_t);
86 static uint32_t smb_dfs_get_referrals_ex(smb_request_t *, smb_fsctl_t *);
87 
88 /*
89  * Handle device type FILE_DEVICE_DFS
90  * for smb2_ioctl
91  */
92 uint32_t
93 smb_dfs_fsctl(smb_request_t *sr, smb_fsctl_t *fsctl)
94 {
95 	uint32_t status;
96 
97 	if (!STYPE_ISIPC(sr->tid_tree->t_res_type))
98 		return (NT_STATUS_INVALID_DEVICE_REQUEST);
99 
100 	/*
101 	 * If the connection is not DFS capable, we should return
102 	 * NT_STATUS_FS_DRIVER_REQUIRED for both of these DFS ioctls.
103 	 * See [MS-SMB2] 3.3.5.15.2.
104 	 */
105 	if ((sr->session->srv_cap & SMB2_CAP_DFS) == 0)
106 		return (NT_STATUS_FS_DRIVER_REQUIRED);
107 
108 	switch (fsctl->CtlCode) {
109 	case FSCTL_DFS_GET_REFERRALS:
110 		status = smb_dfs_get_referrals(sr, fsctl);
111 		break;
112 	case FSCTL_DFS_GET_REFERRALS_EX:
113 		status = smb_dfs_get_referrals_ex(sr, fsctl);
114 		break;
115 	default:
116 		/*
117 		 * MS-SMB2 suggests INVALID_DEVICE_REQUEST
118 		 * for unknown control codes, but using that
119 		 * here makes Windows unhappy.
120 		 */
121 		status = NT_STATUS_FS_DRIVER_REQUIRED;
122 	}
123 
124 	return (status);
125 }
126 
127 /*
128  * XXX Instead of decoding the referral request and encoding
129  * the response here (in-kernel) we could pass the given
130  * request buffer in our door call, and let that return the
131  * response buffer ready to stuff into out_mbc.  That would
132  * allow all this decoding/encoding to happen at user-level.
133  * (and most of this file would go away. :-)
134  */
135 
136 /*
137  * See [MS-DFSC] for details about this command
138  * Handles FSCTL_DFS_GET_REFERRALS_EX (only)
139  */
140 uint32_t
141 smb_dfs_get_referrals_ex(smb_request_t *sr, smb_fsctl_t *fsctl)
142 {
143 	dfs_info_t *referrals;
144 	dfs_referral_response_t refrsp;
145 	dfs_reftype_t reftype;
146 	char *path;
147 	uint16_t maxver;
148 	uint16_t flags;
149 	uint16_t fnlen;
150 	uint32_t datalen;
151 	uint32_t status;
152 	int rc;
153 
154 	/*
155 	 * The caller checks this, because the error reporting method
156 	 * varies across SMB versions.
157 	 */
158 	ASSERT(STYPE_ISIPC(sr->tid_tree->t_res_type));
159 
160 	/*
161 	 * Decode fixed part.
162 	 * Input data is (w) MaxReferralLevel, (w) Flags,
163 	 * (l) RequestDataLength, ... variable data ...
164 	 */
165 	rc = smb_mbc_decodef(fsctl->in_mbc, "wwl",
166 	    &maxver, &flags, &datalen);
167 	if (rc != 0)
168 		return (NT_STATUS_INVALID_PARAMETER);
169 
170 	/*
171 	 * Decode variable part:
172 	 * (w) file name length, (u) filename,
173 	 * ( if flags & 1 )
174 	 *	(w) site name len, (u) site name
175 	 * We don't decode or use the site name
176 	 */
177 	if (MBC_ROOM_FOR(fsctl->in_mbc, datalen) == 0)
178 		return (NT_STATUS_INVALID_PARAMETER);
179 	rc = smb_mbc_decodef(fsctl->in_mbc, "%wu",
180 	    sr, &fnlen, &path);
181 	if (rc != 0)
182 		return (NT_STATUS_INVALID_PARAMETER);
183 
184 	reftype = smb_dfs_get_reftype((const char *)path);
185 	switch (reftype) {
186 	case DFS_REFERRAL_INVALID:
187 		/* Need to check the error for this case */
188 		return (NT_STATUS_INVALID_PARAMETER);
189 
190 	case DFS_REFERRAL_DOMAIN:
191 	case DFS_REFERRAL_DC:
192 		/* MS-DFSC: this error is returned by non-DC root */
193 		return (NT_STATUS_INVALID_PARAMETER);
194 
195 	case DFS_REFERRAL_SYSVOL:
196 		/* MS-DFSC: this error is returned by non-DC root */
197 		return (NT_STATUS_NO_SUCH_DEVICE);
198 
199 	default:
200 		break;
201 	}
202 
203 	status = smb_dfs_referrals_get(sr, path, reftype, &refrsp);
204 	if (status != NT_STATUS_SUCCESS)
205 		return (status);
206 
207 	referrals = &refrsp.rp_referrals;
208 	smb_dfs_encode_hdr(fsctl->out_mbc, referrals);
209 
210 	/*
211 	 * Server may respond with any referral version at or below
212 	 * the maximum specified in the request.
213 	 */
214 	switch (maxver) {
215 	case DFS_REFERRAL_V1:
216 		status = smb_dfs_encode_refv1(sr, fsctl->out_mbc, referrals);
217 		break;
218 
219 	case DFS_REFERRAL_V2:
220 		status = smb_dfs_encode_refv2(sr, fsctl->out_mbc, referrals);
221 		break;
222 
223 	case DFS_REFERRAL_V3:
224 		status = smb_dfs_encode_refv3x(sr, fsctl->out_mbc, referrals,
225 		    DFS_REFERRAL_V3);
226 		break;
227 
228 	case DFS_REFERRAL_V4:
229 	default:
230 		status = smb_dfs_encode_refv3x(sr, fsctl->out_mbc, referrals,
231 		    DFS_REFERRAL_V4);
232 		break;
233 	}
234 
235 	smb_dfs_referrals_free(&refrsp);
236 
237 	return (status);
238 }
239 
240 /*
241  * Note: SMB1 callers in smb_trans2_dfs.c
242  * smb_com_trans2_report_dfs_inconsistency
243  * smb_com_trans2_get_dfs_referral
244  */
245 
246 /*
247  * See [MS-DFSC] for details about this command
248  * Handles FSCTL_DFS_GET_REFERRALS (only)
249  */
250 uint32_t
251 smb_dfs_get_referrals(smb_request_t *sr, smb_fsctl_t *fsctl)
252 {
253 	dfs_info_t *referrals;
254 	dfs_referral_response_t refrsp;
255 	dfs_reftype_t reftype;
256 	char *path;
257 	uint16_t maxver;
258 	uint32_t status;
259 	int rc;
260 
261 	/*
262 	 * The caller checks this, because the error reporting method
263 	 * varies across SMB versions.
264 	 */
265 	ASSERT(STYPE_ISIPC(sr->tid_tree->t_res_type));
266 
267 	/*
268 	 * Input data is (w) MaxReferralLevel, (U) path
269 	 */
270 	rc = smb_mbc_decodef(fsctl->in_mbc, "%wu",
271 	    sr, &maxver, &path);
272 	if (rc != 0)
273 		return (NT_STATUS_INVALID_PARAMETER);
274 
275 	reftype = smb_dfs_get_reftype((const char *)path);
276 	switch (reftype) {
277 	case DFS_REFERRAL_INVALID:
278 		/* Need to check the error for this case */
279 		return (NT_STATUS_INVALID_PARAMETER);
280 
281 	case DFS_REFERRAL_DOMAIN:
282 	case DFS_REFERRAL_DC:
283 		/* MS-DFSC: this error is returned by non-DC root */
284 		return (NT_STATUS_INVALID_PARAMETER);
285 
286 	case DFS_REFERRAL_SYSVOL:
287 		/* MS-DFSC: this error is returned by non-DC root */
288 		return (NT_STATUS_NO_SUCH_DEVICE);
289 
290 	default:
291 		break;
292 	}
293 
294 	status = smb_dfs_referrals_get(sr, path, reftype, &refrsp);
295 	if (status != NT_STATUS_SUCCESS)
296 		return (status);
297 
298 	referrals = &refrsp.rp_referrals;
299 	smb_dfs_encode_hdr(fsctl->out_mbc, referrals);
300 
301 	/*
302 	 * Server may respond with any referral version at or below
303 	 * the maximum specified in the request.
304 	 */
305 	switch (maxver) {
306 	case DFS_REFERRAL_V1:
307 		status = smb_dfs_encode_refv1(sr, fsctl->out_mbc, referrals);
308 		break;
309 
310 	case DFS_REFERRAL_V2:
311 		status = smb_dfs_encode_refv2(sr, fsctl->out_mbc, referrals);
312 		break;
313 
314 	case DFS_REFERRAL_V3:
315 		status = smb_dfs_encode_refv3x(sr, fsctl->out_mbc, referrals,
316 		    DFS_REFERRAL_V3);
317 		break;
318 
319 	case DFS_REFERRAL_V4:
320 	default:
321 		status = smb_dfs_encode_refv3x(sr, fsctl->out_mbc, referrals,
322 		    DFS_REFERRAL_V4);
323 		break;
324 	}
325 
326 	smb_dfs_referrals_free(&refrsp);
327 
328 	return (status);
329 }
330 
331 /*
332  * [MS-DFSC]: REQ_GET_DFS_REFERRAL
333  *
334  * Determines the referral type based on the specified path:
335  *
336  * Domain referral:
337  *    ""
338  *
339  * DC referral:
340  *    \<domain>
341  *
342  * Sysvol referral:
343  *    \<domain>\SYSVOL
344  *    \<domain>\NETLOGON
345  *
346  * Root referral:
347  *    \<domain>\<dfsname>
348  *    \<server>\<dfsname>
349  *
350  * Link referral:
351  *    \<domain>\<dfsname>\<linkpath>
352  *    \<server>\<dfsname>\<linkpath>
353  */
354 static dfs_reftype_t
355 smb_dfs_get_reftype(const char *path)
356 {
357 	smb_unc_t unc;
358 	dfs_reftype_t reftype = 0;
359 
360 	if (*path == '\0')
361 		return (DFS_REFERRAL_DOMAIN);
362 
363 	if (smb_unc_init(path, &unc) != 0)
364 		return (DFS_REFERRAL_INVALID);
365 
366 	if (unc.unc_path != NULL) {
367 		reftype = DFS_REFERRAL_LINK;
368 	} else if (unc.unc_share != NULL) {
369 		if ((smb_strcasecmp(unc.unc_share, "SYSVOL", 0) == 0) ||
370 		    (smb_strcasecmp(unc.unc_share, "NETLOGON", 0) == 0)) {
371 			reftype = DFS_REFERRAL_SYSVOL;
372 		} else {
373 			reftype = DFS_REFERRAL_ROOT;
374 		}
375 	} else if (unc.unc_server != NULL) {
376 		reftype = DFS_REFERRAL_DC;
377 	}
378 
379 	smb_unc_free(&unc);
380 	return (reftype);
381 }
382 
383 static void
384 smb_dfs_encode_hdr(mbuf_chain_t *mbc, dfs_info_t *referrals)
385 {
386 	uint16_t path_consumed;
387 	uint32_t flags;
388 
389 	path_consumed = smb_wcequiv_strlen(referrals->i_uncpath);
390 	flags = DFS_HDRFLG_S;
391 	if (referrals->i_type == DFS_OBJECT_ROOT)
392 		flags |= DFS_HDRFLG_R;
393 
394 	/* Fill rep_param_mb in SMB1 caller. */
395 	(void) smb_mbc_encodef(mbc, "wwl", path_consumed,
396 	    referrals->i_ntargets, flags);
397 }
398 
399 static uint32_t
400 smb_dfs_encode_refv1(smb_request_t *sr, mbuf_chain_t *mbc,
401     dfs_info_t *referrals)
402 {
403 	_NOTE(ARGUNUSED(sr))
404 	uint16_t entsize, rep_bufsize;
405 	uint16_t server_type;
406 	uint16_t flags = 0;
407 	uint16_t r;
408 	char *target;
409 
410 	rep_bufsize = MBC_MAXBYTES(mbc);
411 
412 	server_type = (referrals->i_type == DFS_OBJECT_ROOT) ?
413 	    DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT;
414 
415 	target = kmem_alloc(MAXPATHLEN, KM_SLEEP);
416 
417 	for (r = 0; r < referrals->i_ntargets; r++) {
418 		(void) snprintf(target, MAXPATHLEN, "\\%s\\%s",
419 		    referrals->i_targets[r].t_server,
420 		    referrals->i_targets[r].t_share);
421 
422 		entsize = DFS_REFV1_ENTSZ + smb_wcequiv_strlen(target) + 2;
423 		if (entsize > rep_bufsize)
424 			break;
425 
426 		(void) smb_mbc_encodef(mbc, "wwwwU",
427 		    DFS_REFERRAL_V1, entsize, server_type, flags, target);
428 		rep_bufsize -= entsize;
429 	}
430 
431 	kmem_free(target, MAXPATHLEN);
432 
433 	/*
434 	 * Need room for at least one entry.
435 	 * Windows will silently drop targets that do not fit in
436 	 * the response buffer.
437 	 */
438 	if (r == 0) {
439 		return (NT_STATUS_BUFFER_OVERFLOW);
440 	}
441 
442 	return (NT_STATUS_SUCCESS);
443 }
444 
445 /*
446  * Prepare a response with V2 referral format.
447  *
448  * Here is the response packet format.
449  * All the strings come after all the fixed size entry headers.
450  * These headers contain offsets to the strings at the end. Note
451  * that the two "dfs_path" after the last entry is shared between
452  * all the entries.
453  *
454  * ent1-hdr
455  * ent2-hdr
456  * ...
457  * entN-hdr
458  *   dfs_path
459  *   dfs_path
460  *   target1
461  *   target2
462  *   ...
463  *   targetN
464  *
465  * MS-DFSC mentions that strings can come after each entry header or all after
466  * the last entry header. Windows responses are in the format above.
467  */
468 static uint32_t
469 smb_dfs_encode_refv2(smb_request_t *sr, mbuf_chain_t *mbc,
470     dfs_info_t *referrals)
471 {
472 	_NOTE(ARGUNUSED(sr))
473 	uint16_t entsize, rep_bufsize;
474 	uint16_t server_type;
475 	uint16_t flags = 0;
476 	uint32_t proximity = 0;
477 	uint16_t path_offs, altpath_offs, netpath_offs;
478 	uint16_t targetsz, total_targetsz = 0;
479 	uint16_t dfs_pathsz;
480 	uint16_t r;
481 
482 	rep_bufsize = MBC_MAXBYTES(mbc);
483 	dfs_pathsz = smb_wcequiv_strlen(referrals->i_uncpath) + 2;
484 	entsize = DFS_REFV2_ENTSZ + dfs_pathsz + dfs_pathsz +
485 	    smb_dfs_referrals_unclen(referrals, 0);
486 
487 	if (entsize > rep_bufsize) {
488 		/* need room for at least one referral */
489 		return (NT_STATUS_BUFFER_OVERFLOW);
490 	}
491 
492 	server_type = (referrals->i_type == DFS_OBJECT_ROOT) ?
493 	    DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT;
494 
495 	rep_bufsize -= entsize;
496 	entsize = DFS_REFV2_ENTSZ;
497 
498 	for (r = 0; r < referrals->i_ntargets; r++) {
499 		path_offs = (referrals->i_ntargets - r) * DFS_REFV2_ENTSZ;
500 		altpath_offs = path_offs + dfs_pathsz;
501 		netpath_offs = altpath_offs + dfs_pathsz + total_targetsz;
502 		targetsz = smb_dfs_referrals_unclen(referrals, r);
503 
504 		if (r != 0) {
505 			entsize = DFS_REFV2_ENTSZ + targetsz;
506 			if (entsize > rep_bufsize)
507 				/* silently drop targets that do not fit */
508 				break;
509 			rep_bufsize -= entsize;
510 		}
511 
512 		(void) smb_mbc_encodef(mbc, "wwwwllwww",
513 		    DFS_REFERRAL_V2, DFS_REFV2_ENTSZ, server_type, flags,
514 		    proximity, referrals->i_timeout, path_offs, altpath_offs,
515 		    netpath_offs);
516 
517 		total_targetsz += targetsz;
518 	}
519 
520 	smb_dfs_encode_targets(mbc, referrals);
521 
522 	return (NT_STATUS_SUCCESS);
523 }
524 
525 /*
526  * Prepare a response with V3/V4 referral format.
527  *
528  * For more details, see comments for smb_dfs_encode_refv2() or see
529  * MS-DFSC specification.
530  */
531 static uint32_t
532 smb_dfs_encode_refv3x(smb_request_t *sr, mbuf_chain_t *mbc,
533     dfs_info_t *referrals, uint16_t ver)
534 {
535 	_NOTE(ARGUNUSED(sr))
536 	uint16_t entsize, rep_bufsize, hdrsize;
537 	uint16_t server_type;
538 	uint16_t flags = 0;
539 	uint16_t path_offs, altpath_offs, netpath_offs;
540 	uint16_t targetsz, total_targetsz = 0;
541 	uint16_t dfs_pathsz;
542 	uint16_t r;
543 
544 	hdrsize = (ver == DFS_REFERRAL_V3) ? DFS_REFV3_ENTSZ : DFS_REFV4_ENTSZ;
545 	rep_bufsize = MBC_MAXBYTES(mbc);
546 	dfs_pathsz = smb_wcequiv_strlen(referrals->i_uncpath) + 2;
547 	entsize = hdrsize + dfs_pathsz + dfs_pathsz +
548 	    smb_dfs_referrals_unclen(referrals, 0);
549 
550 	if (entsize > rep_bufsize) {
551 		/* need room for at least one referral */
552 		return (NT_STATUS_BUFFER_OVERFLOW);
553 	}
554 
555 	server_type = (referrals->i_type == DFS_OBJECT_ROOT) ?
556 	    DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT;
557 
558 	rep_bufsize -= entsize;
559 
560 	for (r = 0; r < referrals->i_ntargets; r++) {
561 		path_offs = (referrals->i_ntargets - r) * hdrsize;
562 		altpath_offs = path_offs + dfs_pathsz;
563 		netpath_offs = altpath_offs + dfs_pathsz + total_targetsz;
564 		targetsz = smb_dfs_referrals_unclen(referrals, r);
565 
566 		if (r != 0) {
567 			entsize = hdrsize + targetsz;
568 			if (entsize > rep_bufsize)
569 				/* silently drop targets that do not fit */
570 				break;
571 			rep_bufsize -= entsize;
572 			flags = 0;
573 		} else if (ver == DFS_REFERRAL_V4) {
574 			flags = DFS_ENTFLG_T;
575 		}
576 
577 		(void) smb_mbc_encodef(mbc, "wwwwlwww16.",
578 		    ver, hdrsize, server_type, flags,
579 		    referrals->i_timeout, path_offs, altpath_offs,
580 		    netpath_offs);
581 
582 		total_targetsz += targetsz;
583 	}
584 
585 	smb_dfs_encode_targets(mbc, referrals);
586 
587 	return (NT_STATUS_SUCCESS);
588 }
589 
590 /*
591  * Encodes DFS path, and target strings which come after fixed header
592  * entries.
593  *
594  * Windows 2000 and earlier set the DFSAlternatePathOffset to point to
595  * an 8.3 string representation of the string pointed to by
596  * DFSPathOffset if it is not a legal 8.3 string. Otherwise, if
597  * DFSPathOffset points to a legal 8.3 string, DFSAlternatePathOffset
598  * points to a separate copy of the same string. Windows Server 2003,
599  * Windows Server 2008 and Windows Server 2008 R2 set the
600  * DFSPathOffset and DFSAlternatePathOffset fields to point to separate
601  * copies of the identical string.
602  *
603  * Following Windows 2003 and later here.
604  */
605 static void
606 smb_dfs_encode_targets(mbuf_chain_t *mbc, dfs_info_t *referrals)
607 {
608 	char *target;
609 	int r;
610 
611 	(void) smb_mbc_encodef(mbc, "UU", referrals->i_uncpath,
612 	    referrals->i_uncpath);
613 
614 	target = kmem_alloc(MAXPATHLEN, KM_SLEEP);
615 	for (r = 0; r < referrals->i_ntargets; r++) {
616 		(void) snprintf(target, MAXPATHLEN, "\\%s\\%s",
617 		    referrals->i_targets[r].t_server,
618 		    referrals->i_targets[r].t_share);
619 		(void) smb_mbc_encodef(mbc, "U", target);
620 	}
621 	kmem_free(target, MAXPATHLEN);
622 }
623 
624 /*
625  * Get referral information for the specified path from user space
626  * using a door call.
627  */
628 static uint32_t
629 smb_dfs_referrals_get(smb_request_t *sr, char *dfs_path, dfs_reftype_t reftype,
630     dfs_referral_response_t *refrsp)
631 {
632 	dfs_referral_query_t	req;
633 	int			rc;
634 
635 	req.rq_type = reftype;
636 	req.rq_path = dfs_path;
637 
638 	bzero(refrsp, sizeof (dfs_referral_response_t));
639 	refrsp->rp_status = NT_STATUS_NOT_FOUND;
640 
641 	rc = smb_kdoor_upcall(sr->sr_server, SMB_DR_DFS_GET_REFERRALS,
642 	    &req, dfs_referral_query_xdr, refrsp, dfs_referral_response_xdr);
643 
644 	if (rc != 0)
645 		return (NT_STATUS_FS_DRIVER_REQUIRED);
646 
647 	/*
648 	 * Map the Win error to one of the NT status codes
649 	 * documented in MS-DFSC. The most common, when we
650 	 * have no DFS root configured, is NOT_FOUND.
651 	 */
652 	switch (refrsp->rp_status) {
653 	case ERROR_SUCCESS:
654 		break;
655 	case ERROR_INVALID_PARAMETER:
656 		return (NT_STATUS_INVALID_PARAMETER);
657 	case ERROR_NOT_ENOUGH_MEMORY:
658 		return (NT_STATUS_INSUFFICIENT_RESOURCES);
659 	case ERROR_NOT_FOUND:
660 		return (NT_STATUS_NOT_FOUND);
661 	default:
662 		return (NT_STATUS_UNEXPECTED_NETWORK_ERROR);
663 	}
664 
665 	(void) strsubst(refrsp->rp_referrals.i_uncpath, '/', '\\');
666 	return (NT_STATUS_SUCCESS);
667 }
668 
669 static void
670 smb_dfs_referrals_free(dfs_referral_response_t *refrsp)
671 {
672 	xdr_free(dfs_referral_response_xdr, (char *)refrsp);
673 }
674 
675 /*
676  * Returns the Unicode string length for the target UNC of
677  * the specified entry by 'refno'
678  *
679  * Note that the UNC path should be encoded with ONE leading
680  * slash not two as is common to user-visible UNC paths.
681  */
682 static uint16_t
683 smb_dfs_referrals_unclen(dfs_info_t *referrals, uint16_t refno)
684 {
685 	uint16_t len;
686 
687 	if (refno >= referrals->i_ntargets)
688 		return (0);
689 
690 	/* Encoded target UNC \server\share */
691 	len = smb_wcequiv_strlen(referrals->i_targets[refno].t_server) +
692 	    smb_wcequiv_strlen(referrals->i_targets[refno].t_share) +
693 	    smb_wcequiv_strlen("\\\\") + 2; /* two '\' + NULL */
694 
695 	return (len);
696 }
697