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