smb_mangle_name.c revision 8b2cc8ac894f2d58f38cf2fb7c3ac778f4c57c09
/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <smbsrv/smb_i18n.h>
#include <smbsrv/smb_vops.h>
#include <smbsrv/smb_incl.h>
#include <smbsrv/smb_fsops.h>
#define SMB_NAME83_BASELEN 8
#define SMB_NAME83_LEN 12
/*
* Characters we don't allow in DOS file names.
* If a filename contains any of these chars, it should get mangled.
*
* '.' is also an invalid DOS char but since it's a special
* case it doesn't appear in the list.
*/
static char *invalid_dos_chars =
"\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017"
"\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
" \"/\\:|<>*?";
/*
* According to MSKB article #142982, Windows deletes invalid chars and
* spaces from file name in mangling process; and invalid chars include:
* ."/\[]:;=,
*
* But some of these chars and some other chars (e.g. +) are replaced
* with underscore (_). They are introduced here as special chars.
*/
static char *special_chars = "[];=,+";
/*
* smb_match_name
*
* This function will mangle the "name" field and save the resulted
* shortname to the "shortname" field and 8.3 name to "name83" field.
* The three fields, "name", "shortname" and "name83" will then be
* sent for pattern match with "pattern" field.
*
* The 0 is returned when the name is a reserved dos name, no match
* for the pattern or any type of failure. The 1 is returned when
* there is a match.
*/
int
{
int rc = 0;
int force;
char name83[SMB_SHORTNAMELEN];
char shortname[SMB_SHORTNAMELEN];
/* Leading or trailing dots are disallowed */
if (smb_is_reserved_dos_name(name))
return (0);
/* If no match, check for shortname (if any) */
if (*shortname != 0)
/*
* Sigh... DOS Shells use short name
* interchangeably with long case sensitive
* names. So check that too...
*/
if ((rc == 0) && !ignore_case)
/*
* Still not found and potentially a premangled name...
* Check to see if the butt-head programmer is
* assuming that we mangle names in the same manner
* as NT...
*/
if (rc == 0)
}
return (rc);
}
/*
* smb_match_unknown
*
* I couldn't figure out what the assumptions of this peice of
* code about the format of pattern and name are and so how
* it's trying to match them. I just cleaned it up a little bit!
*
* If anybody could figure out what this is doing, please put
* comment here and change the function's name!
*/
static int
{
int rc;
rc = 0;
if (utf8_isstrupr(pattern) <= 0)
return (rc);
if (nc == ' ')
continue;
break;
}
if ((pc == '~') &&
while (mts_isdigit(pc))
if (pc == '.') {
if (nc == '.')
break;
}
break;
}
}
if (pc == 0)
rc = 1;
}
return (rc);
}
/*
* Return true if name contains characters that are invalid in a file
* name or it is a reserved DOS device name. Otherwise, returns false.
*
* Control characters (values 0 - 31) and the following characters are
* invalid:
* < > : " / \ | ? *
*/
smb_is_invalid_filename(const char *name)
{
const char *p;
if (*p != ' ')
return (B_TRUE);
}
return (smb_is_reserved_dos_name(name));
}
/*
* smb_is_reserved_dos_name
*
* This function checks if the name is a reserved DOS device name.
* The device name should not be followed immediately by an extension,
* for example, NUL.txt.
*/
static boolean_t
smb_is_reserved_dos_name(const char *name)
{
"COM5", "COM6", "COM7", "COM8", "COM9", "CON" };
"LPT6", "LPT7", "LPT8", "LPT9" };
char **reserved;
char ch;
int n_reserved;
int len;
int i;
switch (ch) {
case 'A':
case 'N':
case 'P':
break;
case 'C':
break;
case 'L':
break;
default:
return (B_FALSE);
}
for (i = 0; i < n_reserved; ++i) {
return (B_TRUE);
}
}
return (B_FALSE);
}
/*
* smb_needs_mangle
*
* Determines whether the given name needs to get mangled.
*
* Here are the (known) rules:
*
* 1st char is dot (.)
* name length > 12 chars
* # dots > 1
* # dots == 0 and length > 8
* # dots == 1 and name isn't 8.3
* contains illegal chars
*/
int
{
char *namep;
char *last_dot;
/*
* Returning (1) for these cases forces consistency with how
* these names are treated (smb_mangle_name() will produce an 8.3 name
* for these)
*/
return (1);
/* skip the leading dots (if any) */
;
last_dot = 0;
len++;
if (*namep == '.') {
/* keep the position of last dot */
ndots++;
}
}
/* Windows mangles names like .a, .abc, or .abcd */
if (*name == '.')
return (1);
if (len > 12)
return (1);
switch (ndots) {
case 0:
/* no dot */
if (len > 8)
return (1);
break;
case 1:
/* just one dot */
/*LINTED E_PTR_DIFF_OVERFLOW*/
return (1);
break;
default:
/* more than one dot */
return (1);
}
if (!mts_isascii(*namep) ||
return (1);
}
return (0);
}
/*
* smb_needs_shortname
*
* Determine whether a shortname should be generated for a file name that is
* already in 8.3 format.
*
* Paramters:
* name - original file name
*
* Return:
* 1 - Shortname is required to be generated.
* 0 - No shortname needs to be generated.
*
* Note
* =======
* Windows NT server: shortname is created only if either
* the filename or extension portion of
* a file is made up of mixed case.
* Windows 2000 server: shortname is not created regardless
* of the case.
* Windows 2003 server: [Same as Windows NT server.]
*
* StorEdge will conform to the rule used by Windows NT/2003 server.
*
* For instance:
* File | Create shortname?
* ================================
* nf.txt | N
* NF.TXT | N
* NF.txt | N
* nf | N
* NF | N
* nF.txt | Y
* nf.TxT | Y
* Nf | Y
* nF | Y
*
*/
static int
smb_needs_shortname(char *name)
{
char buf[9];
int len;
int create = 0;
const char *dot_pos = 0;
/*LINTED E_PTRDIFF_OVERFLOW*/
/* First, examine the name portion of the file */
if (len) {
/* if the name contains both lower and upper cases */
/* create shortname */
create = 1;
} else if (dot_pos) {
/* Next, examine the extension portion of the file */
/*
* if the extension contains both lower and upper
* cases
*/
/* create shortname */
create = 1;
}
}
return (create);
}
/*
* smb_mangle_char
*
* If given char is an invalid DOS character or it's not an
* ascii char, it should be deleted from mangled and 8.3 name.
*
* If given char is one of special chars, it should be replaced
* with '_'.
*
* Otherwise just make it upper case.
*/
static unsigned char
smb_mangle_char(unsigned char ch)
{
return (0);
return ('_');
return (mts_toupper(ch));
}
/*
* smb_generate_mangle
*
* Generates a mangle string which contains
* at least 2 (considering fileid cannot be 0)
* and at most 7 chars.
*
* Returns the number of chars in the generated mangle.
*/
static int
{
/*
* 36**6 = 2176782336: more than enough to express inodes in 6
* chars
*/
static char *base36 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
unsigned char *manglep = mangle_buf;
*manglep = 0;
/*LINTED E_PTRDIFF_OVERFLOW*/
return (manglep - mangle_buf);
}
/*
* smb_maybe_mangled_name
*
* Mangled names should be valid DOS file names: less than 12 characters
* long, contain at least one tilde character and conform to an 8.3 name
* format.
*
* Returns true if the name looks like a mangled name.
*/
int
smb_maybe_mangled_name(char *name)
{
const char *p;
int ndots = 0;
int i;
return (B_FALSE);
if (*p == '.') {
if ((++ndots) > 1)
return (B_FALSE);
}
if ((*p == '~') && (i < SMB_NAME83_BASELEN))
if (*p == '.' && !has_tilde)
return (B_FALSE);
}
return ((*p == 0) && has_tilde);
}
/*
* smb_mangle_name
*
* Microsoft knowledge base article #142982 describes how Windows
* generates 8.3 filenames from long file names. Some other details
* can be found in article #114816.
*
* The function first checks to see whether the given name needs mangling.
* If not, and the force parameter is not set, then no mangling is done,
* but both the shortname (if needed) and the 8.3 name are produced and
* returned.
*
* If the "force" parameter is set (as will be the case for case-insensitive
* collisions), then the name will be mangled.
*
* Whenever mangling is needed, both the shortname and the 8.3 names are
* produced and returned.
*
* For example, the xxx.xy in 8.3 format will be "xxx .xy ".
*/
int smb_mangle_name(
char *name, /* original file name */
char *shortname, /* mangled name (if applicable) */
char *name83, /* (mangled) name in 8.3 format */
int force) /* force mangling even if mangling is not */
/* needed according to standard algorithm */
{
int avail;
unsigned char ch;
unsigned char mangle_buf[8];
unsigned char *namep;
unsigned char *manglep;
unsigned char *out_short;
unsigned char *out_83;
/*
* NOTE:
* This function used to consider filename case
* in order to mangle. I removed those checks.
*/
/* Allow dot and dot dot up front */
/* no shortname */
return (1);
}
/* no shortname */
return (1);
}
/* no mangle */
/* check if shortname is required or not */
if (smb_needs_shortname(name)) {
while (*namep)
*out_short = '\0';
}
if (*name == '.') {
/* copy extension */
name++;
while (*name)
}
return (1);
}
/*
* generated mangle part has always less than 8 chars, so
* use the chars before the first dot in filename
* and try to generate a full 8 char name.
*/
/* skip the leading dots (if any) */
;
if (ch == 0)
continue;
avail--;
}
/* Copy in mangled part */
while (*manglep)
/* Pad any leftover in 8.3 name with spaces */
while (avail--)
*out_83++ = ' ';
/* Work on extension now */
avail = 3;
*out_83++ = '.';
if (dot_pos) {
if (*namep != 0) {
*out_short++ = '.';
if (ch == 0)
continue;
avail--;
}
}
}
while (avail--)
*out_83++ = ' ';
return (1);
}
/*
* smb_unmangle_name
*
* Given a mangled name, try to find the real file name as it appears
* in the directory entry.
*
* smb_unmangle_name should only be called on names for which
* smb_maybe_mangled_name() is true
*
* File systems which support VFSFT_EDIRENT_FLAGS will return the
* directory entries as a buffer of edirent_t structure. Others will
* return a buffer of dirent64_t structures. A union is used for the
* the pointer into the buffer (bufptr, edp and dp).
*
* Returns:
* 0 - SUCCESS. Unmangled name is returned in namebuf.
* EINVAL - a parameter was invalid.
* ENOTDIR - dnode is not a directory node.
* ENOENT - an unmangled name could not be found.
*/
int
{
char shortname[SMB_SHORTNAMELEN];
char name83[SMB_SHORTNAMELEN];
union {
char *bufptr;
dirent64_t *dp;
} u;
return (EINVAL);
return (ENOTDIR);
*namebuf = '\0';
offset = 0;
if (bufsize == 0) {
break;
}
reclen = 0;
if (is_edp) {
} else {
}
return (0);
}
}
if (eof) {
break;
}
}
return (err);
}