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