xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_dfs.c (revision a44ccde211b3554142d11535037f8ef996519c3b)
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 
87 /*
88  * Handle device type FILE_DEVICE_DFS
89  * for smb2_ioctl
90  */
91 uint32_t
92 smb_dfs_fsctl(smb_request_t *sr, smb_fsctl_t *fsctl)
93 {
94 	uint32_t status;
95 
96 	if (!STYPE_ISIPC(sr->tid_tree->t_res_type))
97 		return (NT_STATUS_INVALID_DEVICE_REQUEST);
98 
99 	/*
100 	 * If the connection is not DFS capable, we should return
101 	 * NT_STATUS_FS_DRIVER_REQUIRED for both of these DFS ioctls.
102 	 * See [MS-SMB2] 3.3.5.15.2.
103 	 */
104 	if ((sr->session->srv_cap & SMB2_CAP_DFS) == 0)
105 		return (NT_STATUS_FS_DRIVER_REQUIRED);
106 
107 	switch (fsctl->CtlCode) {
108 	case FSCTL_DFS_GET_REFERRALS:
109 		status = smb_dfs_get_referrals(sr, fsctl);
110 		break;
111 	case FSCTL_DFS_GET_REFERRALS_EX: /* XXX - todo */
112 	default:
113 		/*
114 		 * MS-SMB2 suggests INVALID_DEVICE_REQUEST
115 		 * for unknown control codes, but using that
116 		 * here makes Windows unhappy.
117 		 */
118 		status = NT_STATUS_FS_DRIVER_REQUIRED;
119 	}
120 
121 	return (status);
122 }
123 
124 /*
125  * Note: SMB1 callers in smb_trans2_dfs.c
126  * smb_com_trans2_report_dfs_inconsistency
127  * smb_com_trans2_get_dfs_referral
128  */
129 
130 /*
131  * See [MS-DFSC] for details about this command
132  * Handles FSCTL_DFS_GET_REFERRALS (only)
133  */
134 uint32_t
135 smb_dfs_get_referrals(smb_request_t *sr, smb_fsctl_t *fsctl)
136 {
137 	dfs_info_t *referrals;
138 	dfs_referral_response_t refrsp;
139 	dfs_reftype_t reftype;
140 	char *path;
141 	uint16_t maxver;
142 	uint32_t status;
143 	int rc;
144 
145 	/*
146 	 * The caller checks this, because the error reporting method
147 	 * varies across SMB versions.
148 	 */
149 	ASSERT(STYPE_ISIPC(sr->tid_tree->t_res_type));
150 
151 	/*
152 	 * XXX Instead of decoding the referral request and encoding
153 	 * the response here (in-kernel) we could pass the given
154 	 * request buffer in our door call, and let that return the
155 	 * response buffer ready to stuff into out_mbc.  That would
156 	 * allow all this decoding/encoding to happen at user-level.
157 	 * (and most of this file would go away. :-)
158 	 */
159 
160 	/*
161 	 * Input data is (w) MaxReferralLevel, (U) path
162 	 */
163 	rc = smb_mbc_decodef(fsctl->in_mbc, "%wu",
164 	    sr, &maxver, &path);
165 	if (rc != 0)
166 		return (NT_STATUS_INVALID_PARAMETER);
167 
168 	reftype = smb_dfs_get_reftype((const char *)path);
169 	switch (reftype) {
170 	case DFS_REFERRAL_INVALID:
171 		/* Need to check the error for this case */
172 		return (NT_STATUS_INVALID_PARAMETER);
173 
174 	case DFS_REFERRAL_DOMAIN:
175 	case DFS_REFERRAL_DC:
176 		/* MS-DFSC: this error is returned by non-DC root */
177 		return (NT_STATUS_INVALID_PARAMETER);
178 
179 	case DFS_REFERRAL_SYSVOL:
180 		/* MS-DFSC: this error is returned by non-DC root */
181 		return (NT_STATUS_NO_SUCH_DEVICE);
182 
183 	default:
184 		break;
185 	}
186 
187 	status = smb_dfs_referrals_get(sr, path, reftype, &refrsp);
188 	if (status != NT_STATUS_SUCCESS)
189 		return (status);
190 
191 	referrals = &refrsp.rp_referrals;
192 	smb_dfs_encode_hdr(fsctl->out_mbc, referrals);
193 
194 	/*
195 	 * Server may respond with any referral version at or below
196 	 * the maximum specified in the request.
197 	 */
198 	switch (maxver) {
199 	case DFS_REFERRAL_V1:
200 		status = smb_dfs_encode_refv1(sr, fsctl->out_mbc, referrals);
201 		break;
202 
203 	case DFS_REFERRAL_V2:
204 		status = smb_dfs_encode_refv2(sr, fsctl->out_mbc, referrals);
205 		break;
206 
207 	case DFS_REFERRAL_V3:
208 		status = smb_dfs_encode_refv3x(sr, fsctl->out_mbc, referrals,
209 		    DFS_REFERRAL_V3);
210 		break;
211 
212 	case DFS_REFERRAL_V4:
213 	default:
214 		status = smb_dfs_encode_refv3x(sr, fsctl->out_mbc, referrals,
215 		    DFS_REFERRAL_V4);
216 		break;
217 	}
218 
219 	smb_dfs_referrals_free(&refrsp);
220 
221 	return (status);
222 }
223 
224 /*
225  * [MS-DFSC]: REQ_GET_DFS_REFERRAL
226  *
227  * Determines the referral type based on the specified path:
228  *
229  * Domain referral:
230  *    ""
231  *
232  * DC referral:
233  *    \<domain>
234  *
235  * Sysvol referral:
236  *    \<domain>\SYSVOL
237  *    \<domain>\NETLOGON
238  *
239  * Root referral:
240  *    \<domain>\<dfsname>
241  *    \<server>\<dfsname>
242  *
243  * Link referral:
244  *    \<domain>\<dfsname>\<linkpath>
245  *    \<server>\<dfsname>\<linkpath>
246  */
247 static dfs_reftype_t
248 smb_dfs_get_reftype(const char *path)
249 {
250 	smb_unc_t unc;
251 	dfs_reftype_t reftype = 0;
252 
253 	if (*path == '\0')
254 		return (DFS_REFERRAL_DOMAIN);
255 
256 	if (smb_unc_init(path, &unc) != 0)
257 		return (DFS_REFERRAL_INVALID);
258 
259 	if (unc.unc_path != NULL) {
260 		reftype = DFS_REFERRAL_LINK;
261 	} else if (unc.unc_share != NULL) {
262 		if ((smb_strcasecmp(unc.unc_share, "SYSVOL", 0) == 0) ||
263 		    (smb_strcasecmp(unc.unc_share, "NETLOGON", 0) == 0)) {
264 			reftype = DFS_REFERRAL_SYSVOL;
265 		} else {
266 			reftype = DFS_REFERRAL_ROOT;
267 		}
268 	} else if (unc.unc_server != NULL) {
269 		reftype = DFS_REFERRAL_DC;
270 	}
271 
272 	smb_unc_free(&unc);
273 	return (reftype);
274 }
275 
276 static void
277 smb_dfs_encode_hdr(mbuf_chain_t *mbc, dfs_info_t *referrals)
278 {
279 	uint16_t path_consumed;
280 	uint32_t flags;
281 
282 	path_consumed = smb_wcequiv_strlen(referrals->i_uncpath);
283 	flags = DFS_HDRFLG_S;
284 	if (referrals->i_type == DFS_OBJECT_ROOT)
285 		flags |= DFS_HDRFLG_R;
286 
287 	/* Fill rep_param_mb in SMB1 caller. */
288 	(void) smb_mbc_encodef(mbc, "wwl", path_consumed,
289 	    referrals->i_ntargets, flags);
290 }
291 
292 static uint32_t
293 smb_dfs_encode_refv1(smb_request_t *sr, mbuf_chain_t *mbc,
294     dfs_info_t *referrals)
295 {
296 	_NOTE(ARGUNUSED(sr))
297 	uint16_t entsize, rep_bufsize;
298 	uint16_t server_type;
299 	uint16_t flags = 0;
300 	uint16_t r;
301 	char *target;
302 
303 	rep_bufsize = MBC_MAXBYTES(mbc);
304 
305 	server_type = (referrals->i_type == DFS_OBJECT_ROOT) ?
306 	    DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT;
307 
308 	target = kmem_alloc(MAXPATHLEN, KM_SLEEP);
309 
310 	for (r = 0; r < referrals->i_ntargets; r++) {
311 		(void) snprintf(target, MAXPATHLEN, "\\%s\\%s",
312 		    referrals->i_targets[r].t_server,
313 		    referrals->i_targets[r].t_share);
314 
315 		entsize = DFS_REFV1_ENTSZ + smb_wcequiv_strlen(target) + 2;
316 		if (entsize > rep_bufsize)
317 			break;
318 
319 		(void) smb_mbc_encodef(mbc, "wwwwU",
320 		    DFS_REFERRAL_V1, entsize, server_type, flags, target);
321 		rep_bufsize -= entsize;
322 	}
323 
324 	kmem_free(target, MAXPATHLEN);
325 
326 	/*
327 	 * Need room for at least one entry.
328 	 * Windows will silently drop targets that do not fit in
329 	 * the response buffer.
330 	 */
331 	if (r == 0) {
332 		return (NT_STATUS_BUFFER_OVERFLOW);
333 	}
334 
335 	return (NT_STATUS_SUCCESS);
336 }
337 
338 /*
339  * Prepare a response with V2 referral format.
340  *
341  * Here is the response packet format.
342  * All the strings come after all the fixed size entry headers.
343  * These headers contain offsets to the strings at the end. Note
344  * that the two "dfs_path" after the last entry is shared between
345  * all the entries.
346  *
347  * ent1-hdr
348  * ent2-hdr
349  * ...
350  * entN-hdr
351  *   dfs_path
352  *   dfs_path
353  *   target1
354  *   target2
355  *   ...
356  *   targetN
357  *
358  * MS-DFSC mentions that strings can come after each entry header or all after
359  * the last entry header. Windows responses are in the format above.
360  */
361 static uint32_t
362 smb_dfs_encode_refv2(smb_request_t *sr, mbuf_chain_t *mbc,
363     dfs_info_t *referrals)
364 {
365 	_NOTE(ARGUNUSED(sr))
366 	uint16_t entsize, rep_bufsize;
367 	uint16_t server_type;
368 	uint16_t flags = 0;
369 	uint32_t proximity = 0;
370 	uint16_t path_offs, altpath_offs, netpath_offs;
371 	uint16_t targetsz, total_targetsz = 0;
372 	uint16_t dfs_pathsz;
373 	uint16_t r;
374 
375 	rep_bufsize = MBC_MAXBYTES(mbc);
376 	dfs_pathsz = smb_wcequiv_strlen(referrals->i_uncpath) + 2;
377 	entsize = DFS_REFV2_ENTSZ + dfs_pathsz + dfs_pathsz +
378 	    smb_dfs_referrals_unclen(referrals, 0);
379 
380 	if (entsize > rep_bufsize) {
381 		/* need room for at least one referral */
382 		return (NT_STATUS_BUFFER_OVERFLOW);
383 	}
384 
385 	server_type = (referrals->i_type == DFS_OBJECT_ROOT) ?
386 	    DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT;
387 
388 	rep_bufsize -= entsize;
389 	entsize = DFS_REFV2_ENTSZ;
390 
391 	for (r = 0; r < referrals->i_ntargets; r++) {
392 		path_offs = (referrals->i_ntargets - r) * DFS_REFV2_ENTSZ;
393 		altpath_offs = path_offs + dfs_pathsz;
394 		netpath_offs = altpath_offs + dfs_pathsz + total_targetsz;
395 		targetsz = smb_dfs_referrals_unclen(referrals, r);
396 
397 		if (r != 0) {
398 			entsize = DFS_REFV2_ENTSZ + targetsz;
399 			if (entsize > rep_bufsize)
400 				/* silently drop targets that do not fit */
401 				break;
402 			rep_bufsize -= entsize;
403 		}
404 
405 		(void) smb_mbc_encodef(mbc, "wwwwllwww",
406 		    DFS_REFERRAL_V2, DFS_REFV2_ENTSZ, server_type, flags,
407 		    proximity, referrals->i_timeout, path_offs, altpath_offs,
408 		    netpath_offs);
409 
410 		total_targetsz += targetsz;
411 	}
412 
413 	smb_dfs_encode_targets(mbc, referrals);
414 
415 	return (NT_STATUS_SUCCESS);
416 }
417 
418 /*
419  * Prepare a response with V3/V4 referral format.
420  *
421  * For more details, see comments for smb_dfs_encode_refv2() or see
422  * MS-DFSC specification.
423  */
424 static uint32_t
425 smb_dfs_encode_refv3x(smb_request_t *sr, mbuf_chain_t *mbc,
426     dfs_info_t *referrals, uint16_t ver)
427 {
428 	_NOTE(ARGUNUSED(sr))
429 	uint16_t entsize, rep_bufsize, hdrsize;
430 	uint16_t server_type;
431 	uint16_t flags = 0;
432 	uint16_t path_offs, altpath_offs, netpath_offs;
433 	uint16_t targetsz, total_targetsz = 0;
434 	uint16_t dfs_pathsz;
435 	uint16_t r;
436 
437 	hdrsize = (ver == DFS_REFERRAL_V3) ? DFS_REFV3_ENTSZ : DFS_REFV4_ENTSZ;
438 	rep_bufsize = MBC_MAXBYTES(mbc);
439 	dfs_pathsz = smb_wcequiv_strlen(referrals->i_uncpath) + 2;
440 	entsize = hdrsize + dfs_pathsz + dfs_pathsz +
441 	    smb_dfs_referrals_unclen(referrals, 0);
442 
443 	if (entsize > rep_bufsize) {
444 		/* need room for at least one referral */
445 		return (NT_STATUS_BUFFER_OVERFLOW);
446 	}
447 
448 	server_type = (referrals->i_type == DFS_OBJECT_ROOT) ?
449 	    DFS_SRVTYPE_ROOT : DFS_SRVTYPE_NONROOT;
450 
451 	rep_bufsize -= entsize;
452 
453 	for (r = 0; r < referrals->i_ntargets; r++) {
454 		path_offs = (referrals->i_ntargets - r) * hdrsize;
455 		altpath_offs = path_offs + dfs_pathsz;
456 		netpath_offs = altpath_offs + dfs_pathsz + total_targetsz;
457 		targetsz = smb_dfs_referrals_unclen(referrals, r);
458 
459 		if (r != 0) {
460 			entsize = hdrsize + targetsz;
461 			if (entsize > rep_bufsize)
462 				/* silently drop targets that do not fit */
463 				break;
464 			rep_bufsize -= entsize;
465 			flags = 0;
466 		} else if (ver == DFS_REFERRAL_V4) {
467 			flags = DFS_ENTFLG_T;
468 		}
469 
470 		(void) smb_mbc_encodef(mbc, "wwwwlwww16.",
471 		    ver, hdrsize, server_type, flags,
472 		    referrals->i_timeout, path_offs, altpath_offs,
473 		    netpath_offs);
474 
475 		total_targetsz += targetsz;
476 	}
477 
478 	smb_dfs_encode_targets(mbc, referrals);
479 
480 	return (NT_STATUS_SUCCESS);
481 }
482 
483 /*
484  * Encodes DFS path, and target strings which come after fixed header
485  * entries.
486  *
487  * Windows 2000 and earlier set the DFSAlternatePathOffset to point to
488  * an 8.3 string representation of the string pointed to by
489  * DFSPathOffset if it is not a legal 8.3 string. Otherwise, if
490  * DFSPathOffset points to a legal 8.3 string, DFSAlternatePathOffset
491  * points to a separate copy of the same string. Windows Server 2003,
492  * Windows Server 2008 and Windows Server 2008 R2 set the
493  * DFSPathOffset and DFSAlternatePathOffset fields to point to separate
494  * copies of the identical string.
495  *
496  * Following Windows 2003 and later here.
497  */
498 static void
499 smb_dfs_encode_targets(mbuf_chain_t *mbc, dfs_info_t *referrals)
500 {
501 	char *target;
502 	int r;
503 
504 	(void) smb_mbc_encodef(mbc, "UU", referrals->i_uncpath,
505 	    referrals->i_uncpath);
506 
507 	target = kmem_alloc(MAXPATHLEN, KM_SLEEP);
508 	for (r = 0; r < referrals->i_ntargets; r++) {
509 		(void) snprintf(target, MAXPATHLEN, "\\%s\\%s",
510 		    referrals->i_targets[r].t_server,
511 		    referrals->i_targets[r].t_share);
512 		(void) smb_mbc_encodef(mbc, "U", target);
513 	}
514 	kmem_free(target, MAXPATHLEN);
515 }
516 
517 /*
518  * Get referral information for the specified path from user space
519  * using a door call.
520  */
521 static uint32_t
522 smb_dfs_referrals_get(smb_request_t *sr, char *dfs_path, dfs_reftype_t reftype,
523     dfs_referral_response_t *refrsp)
524 {
525 	dfs_referral_query_t	req;
526 	int			rc;
527 
528 	req.rq_type = reftype;
529 	req.rq_path = dfs_path;
530 
531 	bzero(refrsp, sizeof (dfs_referral_response_t));
532 	refrsp->rp_status = NT_STATUS_NOT_FOUND;
533 
534 	rc = smb_kdoor_upcall(sr->sr_server, SMB_DR_DFS_GET_REFERRALS,
535 	    &req, dfs_referral_query_xdr, refrsp, dfs_referral_response_xdr);
536 
537 	if (rc != 0)
538 		return (NT_STATUS_FS_DRIVER_REQUIRED);
539 
540 	/*
541 	 * Map the Win error to one of the NT status codes
542 	 * documented in MS-DFSC. The most common, when we
543 	 * have no DFS root configured, is NOT_FOUND.
544 	 */
545 	switch (refrsp->rp_status) {
546 	case ERROR_SUCCESS:
547 		break;
548 	case ERROR_INVALID_PARAMETER:
549 		return (NT_STATUS_INVALID_PARAMETER);
550 	case ERROR_NOT_ENOUGH_MEMORY:
551 		return (NT_STATUS_INSUFFICIENT_RESOURCES);
552 	case ERROR_NOT_FOUND:
553 		return (NT_STATUS_NOT_FOUND);
554 	default:
555 		return (NT_STATUS_UNEXPECTED_NETWORK_ERROR);
556 	}
557 
558 	(void) strsubst(refrsp->rp_referrals.i_uncpath, '/', '\\');
559 	return (NT_STATUS_SUCCESS);
560 }
561 
562 static void
563 smb_dfs_referrals_free(dfs_referral_response_t *refrsp)
564 {
565 	xdr_free(dfs_referral_response_xdr, (char *)refrsp);
566 }
567 
568 /*
569  * Returns the Unicode string length for the target UNC of
570  * the specified entry by 'refno'
571  *
572  * Note that the UNC path should be encoded with ONE leading
573  * slash not two as is common to user-visible UNC paths.
574  */
575 static uint16_t
576 smb_dfs_referrals_unclen(dfs_info_t *referrals, uint16_t refno)
577 {
578 	uint16_t len;
579 
580 	if (refno >= referrals->i_ntargets)
581 		return (0);
582 
583 	/* Encoded target UNC \server\share */
584 	len = smb_wcequiv_strlen(referrals->i_targets[refno].t_server) +
585 	    smb_wcequiv_strlen(referrals->i_targets[refno].t_share) +
586 	    smb_wcequiv_strlen("\\\\") + 2; /* two '\' + NULL */
587 
588 	return (len);
589 }
590