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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
23 */
24
25 #include <smbsrv/smb_kproto.h>
26 #include <smbsrv/smb_fsops.h>
27 #include <sys/pathname.h>
28 #include <sys/sdt.h>
29
30 static char *smb_pathname_catia_v5tov4(smb_request_t *, char *, char *, int);
31 static char *smb_pathname_catia_v4tov5(smb_request_t *, char *, char *, int);
32 static int smb_pathname_lookup(pathname_t *, pathname_t *, int,
33 vnode_t **, vnode_t *, vnode_t *, smb_attr_t *attr, cred_t *);
34 static char *smb_pathname_strdup(smb_request_t *, const char *);
35 static char *smb_pathname_strcat(smb_request_t *, char *, const char *);
36 static void smb_pathname_preprocess(smb_request_t *, smb_pathname_t *);
37 static void smb_pathname_preprocess_quota(smb_request_t *, smb_pathname_t *);
38 static int smb_pathname_dfs_preprocess(smb_request_t *, char *, size_t);
39 static void smb_pathname_preprocess_adminshare(smb_request_t *,
40 smb_pathname_t *);
41
42
43 uint32_t
smb_is_executable(char * path)44 smb_is_executable(char *path)
45 {
46 char extension[5];
47 int len = strlen(path);
48
49 if ((len >= 4) && (path[len - 4] == '.')) {
50 (void) strcpy(extension, &path[len - 3]);
51 (void) smb_strupr(extension);
52
53 if (strcmp(extension, "EXE") == 0)
54 return (NODE_FLAGS_EXECUTABLE);
55
56 if (strcmp(extension, "COM") == 0)
57 return (NODE_FLAGS_EXECUTABLE);
58
59 if (strcmp(extension, "DLL") == 0)
60 return (NODE_FLAGS_EXECUTABLE);
61
62 if (strcmp(extension, "SYM") == 0)
63 return (NODE_FLAGS_EXECUTABLE);
64 }
65
66 return (0);
67 }
68
69 /*
70 * smb_pathname_reduce
71 *
72 * smb_pathname_reduce() takes a path and returns the smb_node for the
73 * second-to-last component of the path. It also returns the name of the last
74 * component. Pointers for both of these fields must be supplied by the caller.
75 *
76 * Upon success, 0 is returned.
77 *
78 * Upon error, *dir_node will be set to 0.
79 *
80 * *sr (in)
81 * ---
82 * smb_request structure pointer
83 *
84 * *cred (in)
85 * -----
86 * credential
87 *
88 * *path (in)
89 * -----
90 * pathname to be looked up
91 *
92 * *share_root_node (in)
93 * ----------------
94 * File operations which are share-relative should pass sr->tid_tree->t_snode.
95 * If the call is not for a share-relative operation, this parameter must be 0
96 * (e.g. the call from smbsr_setup_share()). (Such callers will have path
97 * operations done using root_smb_node.) This parameter is used to determine
98 * whether mount points can be crossed.
99 *
100 * share_root_node should have at least one reference on it. This reference
101 * will stay intact throughout this routine.
102 *
103 * *cur_node (in)
104 * ---------
105 * The smb_node for the current directory (for relative paths).
106 * cur_node should have at least one reference on it.
107 * This reference will stay intact throughout this routine.
108 *
109 * **dir_node (out)
110 * ----------
111 * Directory for the penultimate component of the original path.
112 * (Note that this is not the same as the parent directory of the ultimate
113 * target in the case of a link.)
114 *
115 * The directory smb_node is returned held. The caller will need to release
116 * the hold or otherwise make sure it will get released (e.g. in a destroy
117 * routine if made part of a global structure).
118 *
119 * last_component (out)
120 * --------------
121 * The last component of the path. (This may be different from the name of any
122 * link target to which the last component may resolve.)
123 *
124 *
125 * ____________________________
126 *
127 * The CIFS server lookup path needs to have logic equivalent to that of
128 * smb_fsop_lookup(), smb_vop_lookup() and other smb_vop_*() routines in the
129 * following areas:
130 *
131 * - non-traversal of child mounts (handled by smb_pathname_reduce)
132 * - unmangling (handled in smb_pathname)
133 * - "chroot" behavior of share root (handled by lookuppnvp)
134 *
135 * In addition, it needs to replace backslashes with forward slashes. It also
136 * ensures that link processing is done correctly, and that directory
137 * information requested by the caller is correctly returned (i.e. for paths
138 * with a link in the last component, the directory information of the
139 * link and not the target needs to be returned).
140 */
141
142 int
smb_pathname_reduce(smb_request_t * sr,cred_t * cred,const char * path,smb_node_t * share_root_node,smb_node_t * cur_node,smb_node_t ** dir_node,char * last_component)143 smb_pathname_reduce(
144 smb_request_t *sr,
145 cred_t *cred,
146 const char *path,
147 smb_node_t *share_root_node,
148 smb_node_t *cur_node,
149 smb_node_t **dir_node,
150 char *last_component)
151 {
152 smb_node_t *root_node;
153 pathname_t ppn;
154 char *usepath;
155 int lookup_flags = FOLLOW;
156 int trailing_slash = 0;
157 int err = 0;
158 int len;
159 smb_node_t *vss_cur_node;
160 smb_node_t *vss_root_node;
161 smb_node_t *local_cur_node;
162 smb_node_t *local_root_node;
163
164 ASSERT(dir_node);
165 ASSERT(last_component);
166
167 *dir_node = NULL;
168 *last_component = '\0';
169 vss_cur_node = NULL;
170 vss_root_node = NULL;
171
172 if (sr && sr->tid_tree) {
173 if (STYPE_ISIPC(sr->tid_tree->t_res_type))
174 return (EACCES);
175 }
176
177 if (SMB_TREE_IS_CASEINSENSITIVE(sr))
178 lookup_flags |= FIGNORECASE;
179
180 if (path == NULL)
181 return (EINVAL);
182
183 if (*path == '\0')
184 return (ENOENT);
185
186 usepath = kmem_alloc(MAXPATHLEN, KM_SLEEP);
187
188 if ((len = strlcpy(usepath, path, MAXPATHLEN)) >= MAXPATHLEN) {
189 kmem_free(usepath, MAXPATHLEN);
190 return (ENAMETOOLONG);
191 }
192
193 (void) strsubst(usepath, '\\', '/');
194
195 if (share_root_node)
196 root_node = share_root_node;
197 else
198 root_node = sr->sr_server->si_root_smb_node;
199
200 if (cur_node == NULL)
201 cur_node = root_node;
202
203 local_cur_node = cur_node;
204 local_root_node = root_node;
205
206 if (SMB_TREE_IS_DFSROOT(sr) && (sr->smb_flg2 & SMB_FLAGS2_DFS)) {
207 err = smb_pathname_dfs_preprocess(sr, usepath, MAXPATHLEN);
208 if (err != 0) {
209 kmem_free(usepath, MAXPATHLEN);
210 return (err);
211 }
212 len = strlen(usepath);
213 }
214
215 if (sr && (sr->smb_flg2 & SMB_FLAGS2_REPARSE_PATH)) {
216 err = smb_vss_lookup_nodes(sr, root_node, cur_node,
217 usepath, &vss_cur_node, &vss_root_node);
218
219 if (err != 0) {
220 kmem_free(usepath, MAXPATHLEN);
221 return (err);
222 }
223
224 len = strlen(usepath);
225 local_cur_node = vss_cur_node;
226 local_root_node = vss_root_node;
227 }
228
229 if (usepath[len - 1] == '/')
230 trailing_slash = 1;
231
232 (void) strcanon(usepath, "/");
233
234 (void) pn_alloc(&ppn);
235
236 if ((err = pn_set(&ppn, usepath)) != 0) {
237 (void) pn_free(&ppn);
238 kmem_free(usepath, MAXPATHLEN);
239 if (vss_cur_node != NULL)
240 (void) smb_node_release(vss_cur_node);
241 if (vss_root_node != NULL)
242 (void) smb_node_release(vss_root_node);
243 return (err);
244 }
245
246 /*
247 * If a path does not have a trailing slash, strip off the
248 * last component. (We only need to return an smb_node for
249 * the second to last component; a name is returned for the
250 * last component.)
251 */
252
253 if (trailing_slash) {
254 (void) strlcpy(last_component, ".", MAXNAMELEN);
255 } else {
256 (void) pn_setlast(&ppn);
257 (void) strlcpy(last_component, ppn.pn_path, MAXNAMELEN);
258 ppn.pn_path[0] = '\0';
259 }
260
261 if ((strcmp(ppn.pn_buf, "/") == 0) || (ppn.pn_buf[0] == '\0')) {
262 smb_node_ref(local_cur_node);
263 *dir_node = local_cur_node;
264 } else {
265 err = smb_pathname(sr, ppn.pn_buf, lookup_flags,
266 local_root_node, local_cur_node, NULL, dir_node, cred);
267 }
268
269 (void) pn_free(&ppn);
270 kmem_free(usepath, MAXPATHLEN);
271
272 /*
273 * Prevent access to anything outside of the share root, except
274 * when mapping a share because that may require traversal from
275 * / to a mounted file system. share_root_node is NULL when
276 * mapping a share.
277 *
278 * Note that we disregard whether the traversal of the path went
279 * outside of the file system and then came back (say via a link).
280 */
281
282 if ((err == 0) && share_root_node) {
283 if (share_root_node->vp->v_vfsp != (*dir_node)->vp->v_vfsp)
284 err = EACCES;
285 }
286
287 if (err) {
288 if (*dir_node) {
289 (void) smb_node_release(*dir_node);
290 *dir_node = NULL;
291 }
292 *last_component = 0;
293 }
294
295 if (vss_cur_node != NULL)
296 (void) smb_node_release(vss_cur_node);
297 if (vss_root_node != NULL)
298 (void) smb_node_release(vss_root_node);
299
300 return (err);
301 }
302
303 /*
304 * smb_pathname()
305 * wrapper to lookuppnvp(). Handles name unmangling.
306 *
307 * *dir_node is the true directory of the target *node.
308 *
309 * If any component but the last in the path is not found, ENOTDIR instead of
310 * ENOENT will be returned.
311 *
312 * Path components are processed one at a time so that smb_nodes can be
313 * created for each component. This allows the n_dnode field in the
314 * smb_node to be properly populated.
315 *
316 * Because of the above, links are also processed in this routine
317 * (i.e., we do not pass the FOLLOW flag to lookuppnvp()). This
318 * will allow smb_nodes to be created for each component of a link.
319 *
320 * Mangle checking is per component. If a name is mangled, when the
321 * unmangled name is passed to smb_pathname_lookup() do not pass
322 * FIGNORECASE, since the unmangled name is the real on-disk name.
323 * Otherwise pass FIGNORECASE if it's set in flags. This will cause the
324 * file system to return "first match" in the event of a case collision.
325 *
326 * If CATIA character translation is enabled it is applied to each
327 * component before passing the component to smb_pathname_lookup().
328 * After smb_pathname_lookup() the reverse translation is applied.
329 */
330
331 int
smb_pathname(smb_request_t * sr,char * path,int flags,smb_node_t * root_node,smb_node_t * cur_node,smb_node_t ** dir_node,smb_node_t ** ret_node,cred_t * cred)332 smb_pathname(smb_request_t *sr, char *path, int flags,
333 smb_node_t *root_node, smb_node_t *cur_node, smb_node_t **dir_node,
334 smb_node_t **ret_node, cred_t *cred)
335 {
336 char *component, *real_name, *namep;
337 pathname_t pn, rpn, upn, link_pn;
338 smb_node_t *dnode, *fnode;
339 smb_attr_t attr;
340 vnode_t *rootvp, *vp;
341 size_t pathleft;
342 int err = 0;
343 int nlink = 0;
344 int local_flags;
345 uint32_t abe_flag = 0;
346 char namebuf[MAXNAMELEN];
347
348 if (path == NULL)
349 return (EINVAL);
350
351 ASSERT(root_node);
352 ASSERT(cur_node);
353 ASSERT(ret_node);
354
355 *ret_node = NULL;
356
357 if (dir_node)
358 *dir_node = NULL;
359
360 (void) pn_alloc(&upn);
361
362 if ((err = pn_set(&upn, path)) != 0) {
363 (void) pn_free(&upn);
364 return (err);
365 }
366
367 if (SMB_TREE_SUPPORTS_ABE(sr))
368 abe_flag = SMB_ABE;
369
370 (void) pn_alloc(&pn);
371 (void) pn_alloc(&rpn);
372
373 component = kmem_alloc(MAXNAMELEN, KM_SLEEP);
374 real_name = kmem_alloc(MAXNAMELEN, KM_SLEEP);
375
376 fnode = NULL;
377 dnode = cur_node;
378 smb_node_ref(dnode);
379 rootvp = root_node->vp;
380
381 while ((pathleft = pn_pathleft(&upn)) != 0) {
382 if (fnode) {
383 smb_node_release(dnode);
384 dnode = fnode;
385 fnode = NULL;
386 }
387
388 if ((err = pn_getcomponent(&upn, component)) != 0)
389 break;
390
391 if ((namep = smb_pathname_catia_v5tov4(sr, component,
392 namebuf, sizeof (namebuf))) == NULL) {
393 err = EILSEQ;
394 break;
395 }
396
397 if ((err = pn_set(&pn, namep)) != 0)
398 break;
399
400 local_flags = flags & FIGNORECASE;
401 err = smb_pathname_lookup(&pn, &rpn, local_flags,
402 &vp, rootvp, dnode->vp, &attr, cred);
403
404 if (err) {
405 if (!SMB_TREE_SUPPORTS_SHORTNAMES(sr) ||
406 !smb_maybe_mangled(component))
407 break;
408
409 if ((err = smb_unmangle(dnode, component,
410 real_name, MAXNAMELEN, abe_flag)) != 0)
411 break;
412
413 if ((namep = smb_pathname_catia_v5tov4(sr, real_name,
414 namebuf, sizeof (namebuf))) == NULL) {
415 err = EILSEQ;
416 break;
417 }
418
419 if ((err = pn_set(&pn, namep)) != 0)
420 break;
421
422 local_flags = 0;
423 err = smb_pathname_lookup(&pn, &rpn, local_flags,
424 &vp, rootvp, dnode->vp, &attr, cred);
425 if (err)
426 break;
427 }
428
429 /*
430 * This check MUST be done before symlink check
431 * since a reparse point is of type VLNK but should
432 * not be handled like a regular symlink.
433 */
434 if (attr.sa_dosattr & FILE_ATTRIBUTE_REPARSE_POINT) {
435 err = EREMOTE;
436 VN_RELE(vp);
437 break;
438 }
439
440 if ((vp->v_type == VLNK) &&
441 ((flags & FOLLOW) || pn_pathleft(&upn))) {
442
443 if (++nlink > MAXSYMLINKS) {
444 err = ELOOP;
445 VN_RELE(vp);
446 break;
447 }
448
449 (void) pn_alloc(&link_pn);
450 err = pn_getsymlink(vp, &link_pn, cred);
451 VN_RELE(vp);
452
453 if (err == 0) {
454 if (pn_pathleft(&link_pn) == 0)
455 (void) pn_set(&link_pn, ".");
456 err = pn_insert(&upn, &link_pn,
457 strlen(component));
458 }
459 pn_free(&link_pn);
460
461 if (err)
462 break;
463
464 if (upn.pn_pathlen == 0) {
465 err = ENOENT;
466 break;
467 }
468
469 if (upn.pn_path[0] == '/') {
470 fnode = root_node;
471 smb_node_ref(fnode);
472 }
473
474 if (pn_fixslash(&upn))
475 flags |= FOLLOW;
476
477 } else {
478 if (flags & FIGNORECASE) {
479 if (strcmp(rpn.pn_path, "/") != 0)
480 pn_setlast(&rpn);
481 namep = rpn.pn_path;
482 } else {
483 namep = pn.pn_path;
484 }
485
486 namep = smb_pathname_catia_v4tov5(sr, namep,
487 namebuf, sizeof (namebuf));
488
489 fnode = smb_node_lookup(sr, NULL, cred, vp, namep,
490 dnode, NULL);
491 VN_RELE(vp);
492
493 if (fnode == NULL) {
494 err = ENOMEM;
495 break;
496 }
497 }
498
499 while (upn.pn_path[0] == '/') {
500 upn.pn_path++;
501 upn.pn_pathlen--;
502 }
503
504 }
505
506 if ((pathleft) && (err == ENOENT))
507 err = ENOTDIR;
508
509 if (err) {
510 if (fnode)
511 smb_node_release(fnode);
512 if (dnode)
513 smb_node_release(dnode);
514 } else {
515 *ret_node = fnode;
516
517 if (dir_node)
518 *dir_node = dnode;
519 else
520 smb_node_release(dnode);
521 }
522
523 kmem_free(component, MAXNAMELEN);
524 kmem_free(real_name, MAXNAMELEN);
525 (void) pn_free(&pn);
526 (void) pn_free(&rpn);
527 (void) pn_free(&upn);
528
529 return (err);
530 }
531
532 /*
533 * Holds on dvp and rootvp (if not rootdir) are required by lookuppnvp()
534 * and will be released within lookuppnvp().
535 */
536 static int
smb_pathname_lookup(pathname_t * pn,pathname_t * rpn,int flags,vnode_t ** vp,vnode_t * rootvp,vnode_t * dvp,smb_attr_t * attr,cred_t * cred)537 smb_pathname_lookup(pathname_t *pn, pathname_t *rpn, int flags,
538 vnode_t **vp, vnode_t *rootvp, vnode_t *dvp, smb_attr_t *attr, cred_t *cred)
539 {
540 int err;
541
542 *vp = NULL;
543 VN_HOLD(dvp);
544 if (rootvp != rootdir)
545 VN_HOLD(rootvp);
546
547 err = lookuppnvp(pn, rpn, flags, NULL, vp, rootvp, dvp, cred);
548 if ((err == 0) && (attr != NULL))
549 (void) smb_vop_getattr(*vp, NULL, attr, 0, kcred);
550
551 return (err);
552 }
553
554 /*
555 * CATIA Translation of a pathname component prior to passing it to lookuppnvp
556 *
557 * If the translated component name contains a '/' NULL is returned.
558 * The caller should treat this as error EILSEQ. It is not valid to
559 * have a directory name with a '/'.
560 */
561 static char *
smb_pathname_catia_v5tov4(smb_request_t * sr,char * name,char * namebuf,int buflen)562 smb_pathname_catia_v5tov4(smb_request_t *sr, char *name,
563 char *namebuf, int buflen)
564 {
565 char *namep;
566
567 if (SMB_TREE_SUPPORTS_CATIA(sr)) {
568 namep = smb_vop_catia_v5tov4(name, namebuf, buflen);
569 if (strchr(namep, '/') != NULL)
570 return (NULL);
571 return (namep);
572 }
573
574 return (name);
575 }
576
577 /*
578 * CATIA translation of a pathname component after returning from lookuppnvp
579 */
580 static char *
smb_pathname_catia_v4tov5(smb_request_t * sr,char * name,char * namebuf,int buflen)581 smb_pathname_catia_v4tov5(smb_request_t *sr, char *name,
582 char *namebuf, int buflen)
583 {
584 if (SMB_TREE_SUPPORTS_CATIA(sr)) {
585 smb_vop_catia_v4tov5(name, namebuf, buflen);
586 return (namebuf);
587 }
588
589 return (name);
590 }
591
592 /*
593 * sr - needed to check for case sense
594 * path - non mangled path needed to be looked up from the startvp
595 * startvp - the vnode to start the lookup from
596 * rootvp - the vnode of the root of the filesystem
597 * returns the vnode found when starting at startvp and using the path
598 *
599 * Finds a vnode starting at startvp and parsing the non mangled path
600 */
601
602 vnode_t *
smb_lookuppathvptovp(smb_request_t * sr,char * path,vnode_t * startvp,vnode_t * rootvp)603 smb_lookuppathvptovp(smb_request_t *sr, char *path, vnode_t *startvp,
604 vnode_t *rootvp)
605 {
606 pathname_t pn;
607 vnode_t *vp = NULL;
608 int lookup_flags = FOLLOW;
609
610 if (SMB_TREE_IS_CASEINSENSITIVE(sr))
611 lookup_flags |= FIGNORECASE;
612
613 (void) pn_alloc(&pn);
614
615 if (pn_set(&pn, path) == 0) {
616 VN_HOLD(startvp);
617 if (rootvp != rootdir)
618 VN_HOLD(rootvp);
619
620 /* lookuppnvp should release the holds */
621 if (lookuppnvp(&pn, NULL, lookup_flags, NULL, &vp,
622 rootvp, startvp, kcred) != 0) {
623 pn_free(&pn);
624 return (NULL);
625 }
626 }
627
628 pn_free(&pn);
629 return (vp);
630 }
631
632 /*
633 * smb_pathname_init
634 * Parse path: pname\\fname:sname:stype
635 *
636 * Elements of the smb_pathname_t structure are allocated using request
637 * specific storage and will be free'd when the sr is destroyed.
638 *
639 * Populate pn structure elements with the individual elements
640 * of pn->pn_path. pn->pn_sname will contain the whole stream name
641 * including the stream type and preceding colon: :sname:%DATA
642 * pn_stype will point to the stream type within pn_sname.
643 *
644 * If the pname element is missing pn_pname will be set to NULL.
645 * If any other element is missing the pointer in pn will be NULL.
646 */
647 void
smb_pathname_init(smb_request_t * sr,smb_pathname_t * pn,char * path)648 smb_pathname_init(smb_request_t *sr, smb_pathname_t *pn, char *path)
649 {
650 char *pname, *fname, *sname;
651 int len;
652
653 bzero(pn, sizeof (smb_pathname_t));
654 pn->pn_path = smb_pathname_strdup(sr, path);
655
656 smb_pathname_preprocess(sr, pn);
657
658 /* parse pn->pn_path into its constituent parts */
659 pname = pn->pn_path;
660 fname = strrchr(pn->pn_path, '\\');
661
662 if (fname) {
663 if (fname == pname) {
664 pn->pn_pname = NULL;
665 } else {
666 *fname = '\0';
667 pn->pn_pname =
668 smb_pathname_strdup(sr, pname);
669 *fname = '\\';
670 }
671 ++fname;
672 } else {
673 fname = pname;
674 pn->pn_pname = NULL;
675 }
676
677 if (fname[0] == '\0') {
678 pn->pn_fname = NULL;
679 return;
680 }
681
682 if (!smb_is_stream_name(fname)) {
683 pn->pn_fname = smb_pathname_strdup(sr, fname);
684 return;
685 }
686
687 /*
688 * find sname and stype in fname.
689 * sname can't be NULL smb_is_stream_name checks this
690 */
691 sname = strchr(fname, ':');
692 if (sname == fname)
693 fname = NULL;
694 else {
695 *sname = '\0';
696 pn->pn_fname =
697 smb_pathname_strdup(sr, fname);
698 *sname = ':';
699 }
700
701 pn->pn_sname = smb_pathname_strdup(sr, sname);
702 pn->pn_stype = strchr(pn->pn_sname + 1, ':');
703 if (pn->pn_stype) {
704 (void) smb_strupr(pn->pn_stype);
705 } else {
706 len = strlen(pn->pn_sname);
707 pn->pn_sname = smb_pathname_strcat(sr, pn->pn_sname, ":$DATA");
708 pn->pn_stype = pn->pn_sname + len;
709 }
710 ++pn->pn_stype;
711 }
712
713 /*
714 * smb_pathname_preprocess
715 *
716 * Perform common pre-processing of pn->pn_path:
717 * - if the pn_path is blank, set it to '\\'
718 * - perform unicode wildcard converstion.
719 * - convert any '/' to '\\'
720 * - eliminate duplicate slashes
721 * - remove trailing slashes
722 * - quota directory specific pre-processing
723 */
724 static void
smb_pathname_preprocess(smb_request_t * sr,smb_pathname_t * pn)725 smb_pathname_preprocess(smb_request_t *sr, smb_pathname_t *pn)
726 {
727 char *p;
728
729 /* treat empty path as "\\" */
730 if (strlen(pn->pn_path) == 0) {
731 pn->pn_path = smb_pathname_strdup(sr, "\\");
732 return;
733 }
734
735 /* perform unicode wildcard conversion */
736 smb_convert_wildcards(pn->pn_path);
737
738 /* treat '/' as '\\' */
739 (void) strsubst(pn->pn_path, '/', '\\');
740
741 (void) strcanon(pn->pn_path, "\\");
742
743 /* remove trailing '\\' */
744 p = pn->pn_path + strlen(pn->pn_path) - 1;
745 if ((p != pn->pn_path) && (*p == '\\'))
746 *p = '\0';
747
748 smb_pathname_preprocess_quota(sr, pn);
749 smb_pathname_preprocess_adminshare(sr, pn);
750 }
751
752 /*
753 * smb_pathname_preprocess_quota
754 *
755 * There is a special file required by windows so that the quota
756 * tab will be displayed by windows clients. This is created in
757 * a special directory, $EXTEND, at the root of the shared file
758 * system. To hide this directory prepend a '.' (dot).
759 */
760 static void
smb_pathname_preprocess_quota(smb_request_t * sr,smb_pathname_t * pn)761 smb_pathname_preprocess_quota(smb_request_t *sr, smb_pathname_t *pn)
762 {
763 char *name = "$EXTEND";
764 char *new_name = ".$EXTEND";
765 char *p, *slash;
766 int len;
767
768 if (!smb_node_is_vfsroot(sr->tid_tree->t_snode))
769 return;
770
771 p = pn->pn_path;
772
773 /* ignore any initial "\\" */
774 p += strspn(p, "\\");
775 if (smb_strcasecmp(p, name, strlen(name)) != 0)
776 return;
777
778 p += strlen(name);
779 if ((*p != ':') && (*p != '\\') && (*p != '\0'))
780 return;
781
782 slash = (pn->pn_path[0] == '\\') ? "\\" : "";
783 len = strlen(pn->pn_path) + 2;
784 pn->pn_path = smb_srm_alloc(sr, len);
785 (void) snprintf(pn->pn_path, len, "%s%s%s", slash, new_name, p);
786 (void) smb_strupr(pn->pn_path);
787 }
788
789 /*
790 * smb_pathname_preprocess_adminshare
791 *
792 * Convert any path with share name "C$" or "c$" (Admin share) in to lower case.
793 */
794 static void
smb_pathname_preprocess_adminshare(smb_request_t * sr,smb_pathname_t * pn)795 smb_pathname_preprocess_adminshare(smb_request_t *sr, smb_pathname_t *pn)
796 {
797 if (strcasecmp(sr->tid_tree->t_sharename, "c$") == 0)
798 (void) smb_strlwr(pn->pn_path);
799 }
800
801 /*
802 * smb_pathname_strdup
803 *
804 * Duplicate NULL terminated string s.
805 *
806 * The new string is allocated using request specific storage and will
807 * be free'd when the sr is destroyed.
808 */
809 static char *
smb_pathname_strdup(smb_request_t * sr,const char * s)810 smb_pathname_strdup(smb_request_t *sr, const char *s)
811 {
812 char *s2;
813 size_t n;
814
815 n = strlen(s) + 1;
816 s2 = smb_srm_zalloc(sr, n);
817 (void) strlcpy(s2, s, n);
818 return (s2);
819 }
820
821 /*
822 * smb_pathname_strcat
823 *
824 * Reallocate NULL terminated string s1 to accommodate
825 * concatenating NULL terminated string s2.
826 * Append s2 and return resulting NULL terminated string.
827 *
828 * The string buffer is reallocated using request specific
829 * storage and will be free'd when the sr is destroyed.
830 */
831 static char *
smb_pathname_strcat(smb_request_t * sr,char * s1,const char * s2)832 smb_pathname_strcat(smb_request_t *sr, char *s1, const char *s2)
833 {
834 size_t n;
835
836 n = strlen(s1) + strlen(s2) + 1;
837 s1 = smb_srm_rezalloc(sr, s1, n);
838 (void) strlcat(s1, s2, n);
839 return (s1);
840 }
841
842 /*
843 * smb_pathname_validate
844 *
845 * Perform basic validation of pn:
846 * - If first component of pn->path is ".." -> PATH_SYNTAX_BAD
847 * - If there are wildcards in pn->pn_pname -> OBJECT_NAME_INVALID
848 * - If fname is "." -> INVALID_OBJECT_NAME
849 *
850 * On unix .. at the root of a file system links to the root. Thus
851 * an attempt to lookup "/../../.." will be the same as looking up "/"
852 * CIFs clients expect the above to result in
853 * NT_STATUS_OBJECT_PATH_SYNTAX_BAD. It is currently not possible
854 * (and questionable if it's desirable) to deal with all cases
855 * but paths beginning with \\.. are handled.
856 *
857 * Returns: B_TRUE if pn is valid,
858 * otherwise returns B_FALSE and sets error status in sr.
859 */
860 boolean_t
smb_pathname_validate(smb_request_t * sr,smb_pathname_t * pn)861 smb_pathname_validate(smb_request_t *sr, smb_pathname_t *pn)
862 {
863 char *path = pn->pn_path;
864
865 /* ignore any initial "\\" */
866 path += strspn(path, "\\");
867
868 /* If first component of path is ".." -> PATH_SYNTAX_BAD */
869 if ((strcmp(path, "..") == 0) || (strncmp(path, "..\\", 3) == 0)) {
870 smbsr_error(sr, NT_STATUS_OBJECT_PATH_SYNTAX_BAD,
871 ERRDOS, ERROR_BAD_PATHNAME);
872 return (B_FALSE);
873 }
874
875 /* If there are wildcards in pn->pn_pname -> OBJECT_NAME_INVALID */
876 if (pn->pn_pname && smb_contains_wildcards(pn->pn_pname)) {
877 smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
878 ERRDOS, ERROR_INVALID_NAME);
879 return (B_FALSE);
880 }
881
882 /* If fname is "." -> INVALID_OBJECT_NAME */
883 if (pn->pn_fname && (strcmp(pn->pn_fname, ".") == 0)) {
884 smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
885 ERRDOS, ERROR_PATH_NOT_FOUND);
886 return (B_FALSE);
887 }
888
889 return (B_TRUE);
890 }
891
892 /*
893 * smb_validate_dirname
894 *
895 * smb_pathname_validate() should have already been performed on pn.
896 *
897 * Very basic directory name validation: checks for colons in a path.
898 * Need to skip the drive prefix since it contains a colon.
899 *
900 * Returns: B_TRUE if the name is valid,
901 * otherwise returns B_FALSE and sets error status in sr.
902 */
903 boolean_t
smb_validate_dirname(smb_request_t * sr,smb_pathname_t * pn)904 smb_validate_dirname(smb_request_t *sr, smb_pathname_t *pn)
905 {
906 char *name;
907 char *path = pn->pn_path;
908
909 if ((name = path) != 0) {
910 name += strspn(name, "\\");
911
912 if (strchr(name, ':') != 0) {
913 smbsr_error(sr, NT_STATUS_NOT_A_DIRECTORY,
914 ERRDOS, ERROR_INVALID_NAME);
915 return (B_FALSE);
916 }
917 }
918
919 return (B_TRUE);
920 }
921
922 /*
923 * smb_validate_object_name
924 *
925 * smb_pathname_validate() should have already been pertformed on pn.
926 *
927 * Very basic file name validation.
928 * For filenames, we check for names of the form "AAAn:". Names that
929 * contain three characters, a single digit and a colon (:) are reserved
930 * as DOS device names, i.e. "COM1:".
931 * Stream name validation is handed off to smb_validate_stream_name
932 *
933 * Returns: B_TRUE if pn->pn_fname is valid,
934 * otherwise returns B_FALSE and sets error status in sr.
935 */
936 boolean_t
smb_validate_object_name(smb_request_t * sr,smb_pathname_t * pn)937 smb_validate_object_name(smb_request_t *sr, smb_pathname_t *pn)
938 {
939 if (pn->pn_fname &&
940 strlen(pn->pn_fname) == 5 &&
941 smb_isdigit(pn->pn_fname[3]) &&
942 pn->pn_fname[4] == ':') {
943 smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
944 ERRDOS, ERROR_INVALID_NAME);
945 return (B_FALSE);
946 }
947
948 if (pn->pn_sname)
949 return (smb_validate_stream_name(sr, pn));
950
951 return (B_TRUE);
952 }
953
954 /*
955 * smb_stream_parse_name
956 *
957 * smb_stream_parse_name should only be called for a path that
958 * contains a valid named stream. Path validation should have
959 * been performed before this function is called.
960 *
961 * Find the last component of path and split it into filename
962 * and stream name.
963 *
964 * On return the named stream type will be present. The stream
965 * type defaults to ":$DATA", if it has not been defined
966 * For exmaple, 'stream' contains :<sname>:$DATA
967 */
968 void
smb_stream_parse_name(char * path,char * filename,char * stream)969 smb_stream_parse_name(char *path, char *filename, char *stream)
970 {
971 char *fname, *sname, *stype;
972
973 ASSERT(path);
974 ASSERT(filename);
975 ASSERT(stream);
976
977 fname = strrchr(path, '\\');
978 fname = (fname == NULL) ? path : fname + 1;
979 (void) strlcpy(filename, fname, MAXNAMELEN);
980
981 sname = strchr(filename, ':');
982 (void) strlcpy(stream, sname, MAXNAMELEN);
983 *sname = '\0';
984
985 stype = strchr(stream + 1, ':');
986 if (stype == NULL)
987 (void) strlcat(stream, ":$DATA", MAXNAMELEN);
988 else
989 (void) smb_strupr(stype);
990 }
991
992 /*
993 * smb_is_stream_name
994 *
995 * Determines if 'path' specifies a named stream.
996 *
997 * path is a NULL terminated string which could be a stream path.
998 * [pathname/]fname[:stream_name[:stream_type]]
999 *
1000 * - If there is no colon in the path or it's the last char
1001 * then it's not a stream name
1002 *
1003 * - '::' is a non-stream and is commonly used by Windows to designate
1004 * the unamed stream in the form "::$DATA"
1005 */
1006 boolean_t
smb_is_stream_name(char * path)1007 smb_is_stream_name(char *path)
1008 {
1009 char *colonp;
1010
1011 if (path == NULL)
1012 return (B_FALSE);
1013
1014 colonp = strchr(path, ':');
1015 if ((colonp == NULL) || (*(colonp+1) == '\0'))
1016 return (B_FALSE);
1017
1018 if (strstr(path, "::"))
1019 return (B_FALSE);
1020
1021 return (B_TRUE);
1022 }
1023
1024 /*
1025 * smb_validate_stream_name
1026 *
1027 * B_FALSE will be returned, and the error status ser in the sr, if:
1028 * - the path is not a stream name
1029 * - a path is specified but the fname is ommitted.
1030 * - the stream_type is specified but not valid.
1031 *
1032 * Note: the stream type is case-insensitive.
1033 */
1034 boolean_t
smb_validate_stream_name(smb_request_t * sr,smb_pathname_t * pn)1035 smb_validate_stream_name(smb_request_t *sr, smb_pathname_t *pn)
1036 {
1037 static char *strmtype[] = {
1038 "$DATA",
1039 "$INDEX_ALLOCATION"
1040 };
1041 int i;
1042
1043 ASSERT(pn);
1044 ASSERT(pn->pn_sname);
1045
1046 if ((!(pn->pn_sname)) ||
1047 ((pn->pn_pname) && !(pn->pn_fname))) {
1048 smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
1049 ERRDOS, ERROR_INVALID_NAME);
1050 return (B_FALSE);
1051 }
1052
1053
1054 if (pn->pn_stype != NULL) {
1055 for (i = 0; i < sizeof (strmtype) / sizeof (strmtype[0]); ++i) {
1056 if (strcasecmp(pn->pn_stype, strmtype[i]) == 0)
1057 return (B_TRUE);
1058 }
1059
1060 smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
1061 ERRDOS, ERROR_INVALID_NAME);
1062 return (B_FALSE);
1063 }
1064
1065 return (B_TRUE);
1066 }
1067
1068 /*
1069 * valid DFS I/O path:
1070 *
1071 * \server-or-domain\share
1072 * \server-or-domain\share\path
1073 *
1074 * All the returned errors by this function needs to be
1075 * checked against Windows.
1076 */
1077 static int
smb_pathname_dfs_preprocess(smb_request_t * sr,char * path,size_t pathsz)1078 smb_pathname_dfs_preprocess(smb_request_t *sr, char *path, size_t pathsz)
1079 {
1080 smb_unc_t unc;
1081 char *linkpath;
1082 int rc;
1083
1084 if (sr->tid_tree == NULL)
1085 return (0);
1086
1087 if ((rc = smb_unc_init(path, &unc)) != 0)
1088 return (rc);
1089
1090 if (smb_strcasecmp(unc.unc_share, sr->tid_tree->t_sharename, 0)) {
1091 smb_unc_free(&unc);
1092 return (EINVAL);
1093 }
1094
1095 linkpath = unc.unc_path;
1096 (void) snprintf(path, pathsz, "/%s", (linkpath) ? linkpath : "");
1097
1098 smb_unc_free(&unc);
1099 return (0);
1100 }
1101