smb_pathname.c revision 5f1ef25c7a11451cbd3080dc3ce8e8db4ca996c4
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2012 Nexenta Systems, Inc. All rights reserved.
*/
#include <smbsrv/smb_kproto.h>
#include <smbsrv/smb_fsops.h>
#include <sys/pathname.h>
static char *smb_pathname_catia_v5tov4(smb_request_t *, char *, char *, int);
static char *smb_pathname_catia_v4tov5(smb_request_t *, char *, char *, int);
static char *smb_pathname_strdup(smb_request_t *, const char *);
static char *smb_pathname_strcat(smb_request_t *, char *, const char *);
static void smb_pathname_preprocess_adminshare(smb_request_t *,
smb_pathname_t *);
smb_is_executable(char *path)
{
char extension[5];
(void) smb_strupr(extension);
return (NODE_FLAGS_EXECUTABLE);
return (NODE_FLAGS_EXECUTABLE);
return (NODE_FLAGS_EXECUTABLE);
return (NODE_FLAGS_EXECUTABLE);
}
return (0);
}
/*
* smb_pathname_reduce
*
* smb_pathname_reduce() takes a path and returns the smb_node for the
* second-to-last component of the path. It also returns the name of the last
* component. Pointers for both of these fields must be supplied by the caller.
*
* Upon success, 0 is returned.
*
* Upon error, *dir_node will be set to 0.
*
* *sr (in)
* ---
* smb_request structure pointer
*
* *cred (in)
* -----
* credential
*
* *path (in)
* -----
* pathname to be looked up
*
* *share_root_node (in)
* ----------------
* File operations which are share-relative should pass sr->tid_tree->t_snode.
* If the call is not for a share-relative operation, this parameter must be 0
* (e.g. the call from smbsr_setup_share()). (Such callers will have path
* operations done using root_smb_node.) This parameter is used to determine
* whether mount points can be crossed.
*
* share_root_node should have at least one reference on it. This reference
* will stay intact throughout this routine.
*
* *cur_node (in)
* ---------
* The smb_node for the current directory (for relative paths).
* cur_node should have at least one reference on it.
* This reference will stay intact throughout this routine.
*
* **dir_node (out)
* ----------
* Directory for the penultimate component of the original path.
* (Note that this is not the same as the parent directory of the ultimate
* target in the case of a link.)
*
* The directory smb_node is returned held. The caller will need to release
* the hold or otherwise make sure it will get released (e.g. in a destroy
* routine if made part of a global structure).
*
* last_component (out)
* --------------
* The last component of the path. (This may be different from the name of any
* link target to which the last component may resolve.)
*
*
* ____________________________
*
* The CIFS server lookup path needs to have logic equivalent to that of
* smb_fsop_lookup(), smb_vop_lookup() and other smb_vop_*() routines in the
* following areas:
*
* - traversal of child mounts (handled by smb_pathname_reduce)
* - unmangling (handled in smb_pathname)
* - "chroot" behavior of share root (handled by lookuppnvp)
*
* In addition, it needs to replace backslashes with forward slashes. It also
* ensures that link processing is done correctly, and that directory
* information requested by the caller is correctly returned (i.e. for paths
* with a link in the last component, the directory information of the
* link and not the target needs to be returned).
*/
int
const char *path,
char *last_component)
{
char *usepath;
int lookup_flags = FOLLOW;
int trailing_slash = 0;
int err = 0;
int len;
*last_component = '\0';
vss_cur_node = NULL;
return (EACCES);
}
return (EINVAL);
if (*path == '\0')
return (ENOENT);
return (ENAMETOOLONG);
}
if (share_root_node)
else
if (err != 0) {
return (err);
}
}
if (err != 0) {
return (err);
}
}
trailing_slash = 1;
if (vss_cur_node != NULL)
(void) smb_node_release(vss_cur_node);
if (vss_root_node != NULL)
(void) smb_node_release(vss_root_node);
return (err);
}
/*
* If a path does not have a trailing slash, strip off the
* last component. (We only need to return an smb_node for
* the second to last component; a name is returned for the
* last component.)
*/
if (trailing_slash) {
} else {
(void) pn_setlast(&ppn);
}
} else {
}
/*
* Prevent traversal to another file system if mount point
* traversal is disabled.
*
* Note that we disregard whether the traversal of the path went
* outside of the file system and then came back (say via a link).
* This means that only symlinks that are expressed relatively to
* the share root work.
*
* share_root_node is NULL when mapping a share, so we disregard
* that case.
*/
if ((err == 0) && share_root_node) {
err = 0;
}
}
if (err) {
if (*dir_node) {
(void) smb_node_release(*dir_node);
}
*last_component = 0;
}
if (vss_cur_node != NULL)
(void) smb_node_release(vss_cur_node);
if (vss_root_node != NULL)
(void) smb_node_release(vss_root_node);
return (err);
}
/*
* smb_pathname()
* wrapper to lookuppnvp(). Handles name unmangling.
*
* *dir_node is the true directory of the target *node.
*
* If any component but the last in the path is not found, ENOTDIR instead of
* ENOENT will be returned.
*
* Path components are processed one at a time so that smb_nodes can be
* created for each component. This allows the n_dnode field in the
* smb_node to be properly populated.
*
* Because of the above, links are also processed in this routine
* (i.e., we do not pass the FOLLOW flag to lookuppnvp()). This
* will allow smb_nodes to be created for each component of a link.
*
* Mangle checking is per component. If a name is mangled, when the
* unmangled name is passed to smb_pathname_lookup() do not pass
* FIGNORECASE, since the unmangled name is the real on-disk name.
* Otherwise pass FIGNORECASE if it's set in flags. This will cause the
* file system to return "first match" in the event of a case collision.
*
* If CATIA character translation is enabled it is applied to each
* component before passing the component to smb_pathname_lookup().
* After smb_pathname_lookup() the reverse translation is applied.
*/
int
{
int err = 0;
int nlink = 0;
int local_flags;
char namebuf[MAXNAMELEN];
return (EINVAL);
if (dir_node)
return (err);
}
if (SMB_TREE_SUPPORTS_ABE(sr))
if (fnode) {
}
break;
break;
}
break;
if (err) {
if (!SMB_TREE_SUPPORTS_SHORTNAMES(sr) ||
break;
break;
break;
}
break;
local_flags = 0;
if (err)
break;
}
/*
* This check MUST be done before symlink check
* since a reparse point is of type VLNK but should
* not be handled like a regular symlink.
*/
break;
}
if (++nlink > MAXSYMLINKS) {
break;
}
if (err == 0) {
if (pn_pathleft(&link_pn) == 0)
}
if (err)
break;
if (upn.pn_pathlen == 0) {
break;
}
}
if (pn_fixslash(&upn))
} else {
if (flags & FIGNORECASE) {
pn_setlast(&rpn);
} else {
}
break;
}
}
upn.pn_pathlen--;
}
}
if (err) {
if (fnode)
if (dnode)
} else {
if (dir_node)
else
}
return (err);
}
/*
* Holds on dvp and rootvp (if not rootdir) are required by lookuppnvp()
* and will be released within lookuppnvp().
*/
static int
{
int err;
return (err);
}
/*
* CATIA Translation of a pathname component prior to passing it to lookuppnvp
*
* If the translated component name contains a '/' NULL is returned.
* The caller should treat this as error EILSEQ. It is not valid to
* have a directory name with a '/'.
*/
static char *
{
char *namep;
if (SMB_TREE_SUPPORTS_CATIA(sr)) {
return (NULL);
return (namep);
}
return (name);
}
/*
* CATIA translation of a pathname component after returning from lookuppnvp
*/
static char *
{
if (SMB_TREE_SUPPORTS_CATIA(sr)) {
return (namebuf);
}
return (name);
}
/*
* sr - needed to check for case sense
* path - non mangled path needed to be looked up from the startvp
* startvp - the vnode to start the lookup from
* rootvp - the vnode of the root of the filesystem
* returns the vnode found when starting at startvp and using the path
*
* Finds a vnode starting at startvp and parsing the non mangled path
*/
vnode_t *
{
int lookup_flags = FOLLOW;
/* lookuppnvp should release the holds */
return (NULL);
}
}
return (vp);
}
/*
* smb_pathname_init
* Parse path: pname\\fname:sname:stype
*
* Elements of the smb_pathname_t structure are allocated using request
* specific storage and will be free'd when the sr is destroyed.
*
* Populate pn structure elements with the individual elements
* of pn->pn_path. pn->pn_sname will contain the whole stream name
* including the stream type and preceding colon: :sname:%DATA
* pn_stype will point to the stream type within pn_sname.
*
* If the pname element is missing pn_pname will be set to NULL.
* If any other element is missing the pointer in pn will be NULL.
*/
void
{
int len;
/* parse pn->pn_path into its constituent parts */
if (fname) {
} else {
*fname = '\0';
*fname = '\\';
}
++fname;
} else {
}
if (fname[0] == '\0') {
return;
}
if (!smb_is_stream_name(fname)) {
return;
}
/*
* find sname and stype in fname.
* sname can't be NULL smb_is_stream_name checks this
*/
else {
*sname = '\0';
*sname = ':';
}
} else {
}
}
/*
* smb_pathname_preprocess
*
* Perform common pre-processing of pn->pn_path:
* - if the pn_path is blank, set it to '\\'
* - perform unicode wildcard converstion.
* - convert any '/' to '\\'
* - eliminate duplicate slashes
* - remove trailing slashes
* - quota directory specific pre-processing
*/
static void
{
char *p;
/* treat empty path as "\\" */
return;
}
/* treat '/' as '\\' */
/* remove trailing '\\' */
*p = '\0';
}
/*
* smb_pathname_preprocess_quota
*
* There is a special file required by windows so that the quota
* tab will be displayed by windows clients. This is created in
* a special directory, $EXTEND, at the root of the shared file
* system. To hide this directory prepend a '.' (dot).
*/
static void
{
char *name = "$EXTEND";
char *new_name = ".$EXTEND";
char *p, *slash;
int len;
return;
/* ignore any initial "\\" */
p += strspn(p, "\\");
return;
if ((*p != ':') && (*p != '\\') && (*p != '\0'))
return;
}
/*
* smb_pathname_preprocess_adminshare
*
* Convert any path with share name "C$" or "c$" (Admin share) in to lower case.
*/
static void
{
}
/*
* smb_pathname_strdup
*
* Duplicate NULL terminated string s.
*
* The new string is allocated using request specific storage and will
* be free'd when the sr is destroyed.
*/
static char *
{
char *s2;
size_t n;
n = strlen(s) + 1;
return (s2);
}
/*
* smb_pathname_strcat
*
* Reallocate NULL terminated string s1 to accommodate
* concatenating NULL terminated string s2.
* Append s2 and return resulting NULL terminated string.
*
* The string buffer is reallocated using request specific
* storage and will be free'd when the sr is destroyed.
*/
static char *
{
size_t n;
return (s1);
}
/*
* smb_pathname_validate
*
* Perform basic validation of pn:
* - If first component of pn->path is ".." -> PATH_SYNTAX_BAD
* - If there are wildcards in pn->pn_pname -> OBJECT_NAME_INVALID
* - If fname is "." -> INVALID_OBJECT_NAME
*
* On unix .. at the root of a file system links to the root. Thus
* an attempt to lookup "/../../.." will be the same as looking up "/"
* CIFs clients expect the above to result in
* NT_STATUS_OBJECT_PATH_SYNTAX_BAD. It is currently not possible
* (and questionable if it's desirable) to deal with all cases
* but paths beginning with \\.. are handled.
*
* Returns: B_TRUE if pn is valid,
* otherwise returns B_FALSE and sets error status in sr.
*/
{
/* ignore any initial "\\" */
/* If first component of path is ".." -> PATH_SYNTAX_BAD */
return (B_FALSE);
}
/* If there are wildcards in pn->pn_pname -> OBJECT_NAME_INVALID */
return (B_FALSE);
}
/* If fname is "." -> INVALID_OBJECT_NAME */
return (B_FALSE);
}
return (B_TRUE);
}
/*
* smb_validate_dirname
*
* smb_pathname_validate() should have already been performed on pn.
*
* Very basic directory name validation: checks for colons in a path.
* Need to skip the drive prefix since it contains a colon.
*
* Returns: B_TRUE if the name is valid,
* otherwise returns B_FALSE and sets error status in sr.
*/
{
char *name;
return (B_FALSE);
}
}
return (B_TRUE);
}
/*
* smb_validate_object_name
*
* smb_pathname_validate() should have already been pertformed on pn.
*
* Very basic file name validation.
* For filenames, we check for names of the form "AAAn:". Names that
* contain three characters, a single digit and a colon (:) are reserved
* as DOS device names, i.e. "COM1:".
* Stream name validation is handed off to smb_validate_stream_name
*
* Returns: B_TRUE if pn->pn_fname is valid,
* otherwise returns B_FALSE and sets error status in sr.
*/
{
return (B_FALSE);
}
return (B_TRUE);
}
/*
* smb_stream_parse_name
*
* smb_stream_parse_name should only be called for a path that
* contains a valid named stream. Path validation should have
* been performed before this function is called.
*
* Find the last component of path and split it into filename
* and stream name.
*
* On return the named stream type will be present. The stream
* type defaults to ":$DATA", if it has not been defined
* For exmaple, 'stream' contains :<sname>:$DATA
*/
void
{
*sname = '\0';
else
(void) smb_strupr(stype);
}
/*
* smb_is_stream_name
*
* Determines if 'path' specifies a named stream.
*
* path is a NULL terminated string which could be a stream path.
* [pathname/]fname[:stream_name[:stream_type]]
*
* - If there is no colon in the path or it's the last char
* then it's not a stream name
*
* - '::' is a non-stream and is commonly used by Windows to designate
* the unamed stream in the form "::$DATA"
*/
smb_is_stream_name(char *path)
{
char *colonp;
return (B_FALSE);
return (B_FALSE);
return (B_FALSE);
return (B_TRUE);
}
/*
* smb_validate_stream_name
*
* B_FALSE will be returned, and the error status ser in the sr, if:
* - the path is not a stream name
* - a path is specified but the fname is ommitted.
* - the stream_type is specified but not valid.
*
* Note: the stream type is case-insensitive.
*/
{
static char *strmtype[] = {
"$DATA",
"$INDEX_ALLOCATION"
};
int i;
return (B_FALSE);
}
return (B_TRUE);
}
return (B_FALSE);
}
return (B_TRUE);
}
/*
* valid DFS I/O path:
*
* \server-or-domain\share
* \server-or-domain\share\path
*
* All the returned errors by this function needs to be
* checked against Windows.
*/
static int
{
char *linkpath;
int rc;
return (0);
return (rc);
smb_unc_free(&unc);
return (EINVAL);
}
smb_unc_free(&unc);
return (0);
}