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