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