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 }