smb_pathname.c revision 11963:061945695ce1
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_fsops.h>
28#include <sys/pathname.h>
29#include <sys/sdt.h>
30
31static char *smb_pathname_catia_v5tov4(smb_request_t *, char *, char *, int);
32static char *smb_pathname_catia_v4tov5(smb_request_t *, char *, char *, int);
33static int smb_pathname_lookup(pathname_t *, pathname_t *, int,
34    vnode_t **, vnode_t *, vnode_t *, smb_attr_t *attr, cred_t *);
35static char *smb_pathname_strdup(smb_request_t *, const char *);
36static char *smb_pathname_strcat(smb_request_t *, char *, const char *);
37static void smb_pathname_preprocess(smb_request_t *, smb_pathname_t *);
38static void smb_pathname_preprocess_quota(smb_request_t *, smb_pathname_t *);
39static int smb_pathname_dfs_preprocess(smb_request_t *, char *, size_t);
40static void smb_pathname_preprocess_adminshare(smb_request_t *,
41    smb_pathname_t *);
42
43
44uint32_t
45smb_is_executable(char *path)
46{
47	char	extension[5];
48	int	len = strlen(path);
49
50	if ((len >= 4) && (path[len - 4] == '.')) {
51		(void) strcpy(extension, &path[len - 3]);
52		(void) smb_strupr(extension);
53
54		if (strcmp(extension, "EXE") == 0)
55			return (NODE_FLAGS_EXECUTABLE);
56
57		if (strcmp(extension, "COM") == 0)
58			return (NODE_FLAGS_EXECUTABLE);
59
60		if (strcmp(extension, "DLL") == 0)
61			return (NODE_FLAGS_EXECUTABLE);
62
63		if (strcmp(extension, "SYM") == 0)
64			return (NODE_FLAGS_EXECUTABLE);
65	}
66
67	return (0);
68}
69
70/*
71 * smb_pathname_reduce
72 *
73 * smb_pathname_reduce() takes a path and returns the smb_node for the
74 * second-to-last component of the path.  It also returns the name of the last
75 * component.  Pointers for both of these fields must be supplied by the caller.
76 *
77 * Upon success, 0 is returned.
78 *
79 * Upon error, *dir_node will be set to 0.
80 *
81 * *sr (in)
82 * ---
83 * smb_request structure pointer
84 *
85 * *cred (in)
86 * -----
87 * credential
88 *
89 * *path (in)
90 * -----
91 * pathname to be looked up
92 *
93 * *share_root_node (in)
94 * ----------------
95 * File operations which are share-relative should pass sr->tid_tree->t_snode.
96 * If the call is not for a share-relative operation, this parameter must be 0
97 * (e.g. the call from smbsr_setup_share()).  (Such callers will have path
98 * operations done using root_smb_node.)  This parameter is used to determine
99 * whether mount points can be crossed.
100 *
101 * share_root_node should have at least one reference on it.  This reference
102 * will stay intact throughout this routine.
103 *
104 * *cur_node (in)
105 * ---------
106 * The smb_node for the current directory (for relative paths).
107 * cur_node should have at least one reference on it.
108 * This reference will stay intact throughout this routine.
109 *
110 * **dir_node (out)
111 * ----------
112 * Directory for the penultimate component of the original path.
113 * (Note that this is not the same as the parent directory of the ultimate
114 * target in the case of a link.)
115 *
116 * The directory smb_node is returned held.  The caller will need to release
117 * the hold or otherwise make sure it will get released (e.g. in a destroy
118 * routine if made part of a global structure).
119 *
120 * last_component (out)
121 * --------------
122 * The last component of the path.  (This may be different from the name of any
123 * link target to which the last component may resolve.)
124 *
125 *
126 * ____________________________
127 *
128 * The CIFS server lookup path needs to have logic equivalent to that of
129 * smb_fsop_lookup(), smb_vop_lookup() and other smb_vop_*() routines in the
130 * following areas:
131 *
132 *	- non-traversal of child mounts		(handled by smb_pathname_reduce)
133 *	- unmangling 				(handled in smb_pathname)
134 *	- "chroot" behavior of share root 	(handled by lookuppnvp)
135 *
136 * In addition, it needs to replace backslashes with forward slashes.  It also
137 * ensures that link processing is done correctly, and that directory
138 * information requested by the caller is correctly returned (i.e. for paths
139 * with a link in the last component, the directory information of the
140 * link and not the target needs to be returned).
141 */
142
143int
144smb_pathname_reduce(
145    smb_request_t	*sr,
146    cred_t		*cred,
147    const char		*path,
148    smb_node_t		*share_root_node,
149    smb_node_t		*cur_node,
150    smb_node_t		**dir_node,
151    char		*last_component)
152{
153	smb_node_t	*root_node;
154	pathname_t	ppn;
155	char		*usepath;
156	int		lookup_flags = FOLLOW;
157	int 		trailing_slash = 0;
158	int		err = 0;
159	int		len;
160	smb_node_t	*vss_cur_node;
161	smb_node_t	*vss_root_node;
162	smb_node_t	*local_cur_node;
163	smb_node_t	*local_root_node;
164
165	ASSERT(dir_node);
166	ASSERT(last_component);
167
168	*dir_node = NULL;
169	*last_component = '\0';
170	vss_cur_node = NULL;
171	vss_root_node = NULL;
172
173	if (sr && sr->tid_tree) {
174		if (STYPE_ISIPC(sr->tid_tree->t_res_type))
175			return (EACCES);
176	}
177
178	if (SMB_TREE_IS_CASEINSENSITIVE(sr))
179		lookup_flags |= FIGNORECASE;
180
181	if (path == NULL)
182		return (EINVAL);
183
184	if (*path == '\0')
185		return (ENOENT);
186
187	usepath = kmem_alloc(MAXPATHLEN, KM_SLEEP);
188
189	if ((len = strlcpy(usepath, path, MAXPATHLEN)) >= MAXPATHLEN) {
190		kmem_free(usepath, MAXPATHLEN);
191		return (ENAMETOOLONG);
192	}
193
194	(void) strsubst(usepath, '\\', '/');
195
196	if (share_root_node)
197		root_node = share_root_node;
198	else
199		root_node = sr->sr_server->si_root_smb_node;
200
201	if (cur_node == NULL)
202		cur_node = root_node;
203
204	local_cur_node = cur_node;
205	local_root_node = root_node;
206
207	if (sr && (sr->smb_flg2 & SMB_FLAGS2_DFS)) {
208		err = smb_pathname_dfs_preprocess(sr, usepath, MAXPATHLEN);
209		if (err != 0) {
210			kmem_free(usepath, MAXPATHLEN);
211			return (err);
212		}
213		len = strlen(usepath);
214	}
215
216	if (sr && (sr->smb_flg2 & SMB_FLAGS2_REPARSE_PATH)) {
217		err = smb_vss_lookup_nodes(sr, root_node, cur_node,
218		    usepath, &vss_cur_node, &vss_root_node);
219
220		if (err != 0) {
221			kmem_free(usepath, MAXPATHLEN);
222			return (err);
223		}
224
225		len = strlen(usepath);
226		local_cur_node = vss_cur_node;
227		local_root_node = vss_root_node;
228	}
229
230	if (usepath[len - 1] == '/')
231		trailing_slash = 1;
232
233	(void) strcanon(usepath, "/");
234
235	(void) pn_alloc(&ppn);
236
237	if ((err = pn_set(&ppn, usepath)) != 0) {
238		(void) pn_free(&ppn);
239		kmem_free(usepath, MAXPATHLEN);
240		if (vss_cur_node != NULL)
241			(void) smb_node_release(vss_cur_node);
242		if (vss_root_node != NULL)
243			(void) smb_node_release(vss_root_node);
244		return (err);
245	}
246
247	/*
248	 * If a path does not have a trailing slash, strip off the
249	 * last component.  (We only need to return an smb_node for
250	 * the second to last component; a name is returned for the
251	 * last component.)
252	 */
253
254	if (trailing_slash) {
255		(void) strlcpy(last_component, ".", MAXNAMELEN);
256	} else {
257		(void) pn_setlast(&ppn);
258		(void) strlcpy(last_component, ppn.pn_path, MAXNAMELEN);
259		ppn.pn_path[0] = '\0';
260	}
261
262	if ((strcmp(ppn.pn_buf, "/") == 0) || (ppn.pn_buf[0] == '\0')) {
263		smb_node_ref(local_cur_node);
264		*dir_node = local_cur_node;
265	} else {
266		err = smb_pathname(sr, ppn.pn_buf, lookup_flags,
267		    local_root_node, local_cur_node, NULL, dir_node, cred);
268	}
269
270	(void) pn_free(&ppn);
271	kmem_free(usepath, MAXPATHLEN);
272
273	/*
274	 * Prevent access to anything outside of the share root, except
275	 * when mapping a share because that may require traversal from
276	 * / to a mounted file system.  share_root_node is NULL when
277	 * mapping a share.
278	 *
279	 * Note that we disregard whether the traversal of the path went
280	 * outside of the file system and then came back (say via a link).
281	 */
282
283	if ((err == 0) && share_root_node) {
284		if (share_root_node->vp->v_vfsp != (*dir_node)->vp->v_vfsp)
285			err = EACCES;
286	}
287
288	if (err) {
289		if (*dir_node) {
290			(void) smb_node_release(*dir_node);
291			*dir_node = NULL;
292		}
293		*last_component = 0;
294	}
295
296	if (vss_cur_node != NULL)
297		(void) smb_node_release(vss_cur_node);
298	if (vss_root_node != NULL)
299		(void) smb_node_release(vss_root_node);
300
301	return (err);
302}
303
304/*
305 * smb_pathname()
306 * wrapper to lookuppnvp().  Handles name unmangling.
307 *
308 * *dir_node is the true directory of the target *node.
309 *
310 * If any component but the last in the path is not found, ENOTDIR instead of
311 * ENOENT will be returned.
312 *
313 * Path components are processed one at a time so that smb_nodes can be
314 * created for each component.  This allows the n_dnode field in the
315 * smb_node to be properly populated.
316 *
317 * Because of the above, links are also processed in this routine
318 * (i.e., we do not pass the FOLLOW flag to lookuppnvp()).  This
319 * will allow smb_nodes to be created for each component of a link.
320 *
321 * Mangle checking is per component. If a name is mangled, when the
322 * unmangled name is passed to smb_pathname_lookup() do not pass
323 * FIGNORECASE, since the unmangled name is the real on-disk name.
324 * Otherwise pass FIGNORECASE if it's set in flags. This will cause the
325 * file system to return "first match" in the event of a case collision.
326 *
327 * If CATIA character translation is enabled it is applied to each
328 * component before passing the component to smb_pathname_lookup().
329 * After smb_pathname_lookup() the reverse translation is applied.
330 */
331
332int
333smb_pathname(smb_request_t *sr, char *path, int flags,
334    smb_node_t *root_node, smb_node_t *cur_node, smb_node_t **dir_node,
335    smb_node_t **ret_node, cred_t *cred)
336{
337	char		*component, *real_name, *namep;
338	pathname_t	pn, rpn, upn, link_pn;
339	smb_node_t	*dnode, *fnode;
340	smb_attr_t	attr;
341	vnode_t		*rootvp, *vp;
342	size_t		pathleft;
343	int		err = 0;
344	int		nlink = 0;
345	int		local_flags;
346	uint32_t	abe_flag = 0;
347	char		namebuf[MAXNAMELEN];
348
349	if (path == NULL)
350		return (EINVAL);
351
352	ASSERT(root_node);
353	ASSERT(cur_node);
354	ASSERT(ret_node);
355
356	*ret_node = NULL;
357
358	if (dir_node)
359		*dir_node = NULL;
360
361	(void) pn_alloc(&upn);
362
363	if ((err = pn_set(&upn, path)) != 0) {
364		(void) pn_free(&upn);
365		return (err);
366	}
367
368	if (SMB_TREE_SUPPORTS_ABE(sr))
369		abe_flag = SMB_ABE;
370
371	(void) pn_alloc(&pn);
372	(void) pn_alloc(&rpn);
373
374	component = kmem_alloc(MAXNAMELEN, KM_SLEEP);
375	real_name = kmem_alloc(MAXNAMELEN, KM_SLEEP);
376
377	fnode = NULL;
378	dnode = cur_node;
379	smb_node_ref(dnode);
380	rootvp = root_node->vp;
381
382	while ((pathleft = pn_pathleft(&upn)) != 0) {
383		if (fnode) {
384			smb_node_release(dnode);
385			dnode = fnode;
386			fnode = NULL;
387		}
388
389		if ((err = pn_getcomponent(&upn, component)) != 0)
390			break;
391
392		if ((namep = smb_pathname_catia_v5tov4(sr, component,
393		    namebuf, sizeof (namebuf))) == NULL) {
394			err = EILSEQ;
395			break;
396		}
397
398		if ((err = pn_set(&pn, namep)) != 0)
399			break;
400
401		local_flags = flags & FIGNORECASE;
402		err = smb_pathname_lookup(&pn, &rpn, local_flags,
403		    &vp, rootvp, dnode->vp, &attr, cred);
404
405		if (err) {
406			if (smb_maybe_mangled_name(component) == 0)
407				break;
408
409			if ((err = smb_unmangle_name(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 */
536static int
537smb_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 */
561static char *
562smb_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 */
580static char *
581smb_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
602vnode_t *
603smb_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 */
647void
648smb_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 */
724static void
725smb_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 */
760static void
761smb_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 */
794static void
795smb_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 */
809static char *
810smb_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 */
831static char *
832smb_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 */
860boolean_t
861smb_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 */
903boolean_t
904smb_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 */
936boolean_t
937smb_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 */
968void
969smb_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 */
1006boolean_t
1007smb_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 */
1034boolean_t
1035smb_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 */
1077static int
1078smb_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