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