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