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*/
smb_com_trans2_report_dfs_inconsistency(smb_request_t * sr)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
smb_com_trans2_get_dfs_referral(smb_request_t * sr,smb_xa_t * xa)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
smb_dfs_get_reftype(const char * path)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
smb_dfs_encode_hdr(smb_xa_t * xa,dfs_info_t * referrals)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
smb_dfs_encode_refv1(smb_request_t * sr,smb_xa_t * xa,dfs_info_t * referrals)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
smb_dfs_encode_refv2(smb_request_t * sr,smb_xa_t * xa,dfs_info_t * referrals)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
smb_dfs_encode_refv3_v4(smb_request_t * sr,smb_xa_t * xa,dfs_info_t * referrals,uint16_t ver)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
smb_dfs_encode_targets(smb_xa_t * xa,dfs_info_t * referrals)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
smb_dfs_referrals_get(smb_request_t * sr,char * dfs_path,dfs_reftype_t reftype,dfs_referral_response_t * refrsp)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
smb_dfs_referrals_free(dfs_referral_response_t * refrsp)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
smb_dfs_referrals_unclen(dfs_info_t * referrals,uint16_t refno)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