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
smb_dfs_get_referrals(smb_request_t * sr,smb_fsctl_t * fsctl)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
smb_dfs_get_reftype(const char * path)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
smb_dfs_encode_hdr(mbuf_chain_t * mbc,dfs_info_t * referrals)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
smb_dfs_encode_refv1(smb_request_t * sr,mbuf_chain_t * mbc,dfs_info_t * referrals)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
smb_dfs_encode_refv2(smb_request_t * sr,mbuf_chain_t * mbc,dfs_info_t * referrals)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
smb_dfs_encode_refv3x(smb_request_t * sr,mbuf_chain_t * mbc,dfs_info_t * referrals,uint16_t ver)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
smb_dfs_encode_targets(mbuf_chain_t * mbc,dfs_info_t * referrals)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
smb_dfs_referrals_get(smb_request_t * sr,char * dfs_path,dfs_reftype_t reftype,dfs_referral_response_t * refrsp)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
smb_dfs_referrals_free(dfs_referral_response_t * refrsp)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
smb_dfs_referrals_unclen(dfs_info_t * referrals,uint16_t refno)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