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  * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
  24  */
  25 
  26 #include <sys/types.h>
  27 #include <sys/param.h>
  28 #include <sys/sunddi.h>
  29 #include <sys/errno.h>
  30 #include <sys/extdirent.h>
  31 #include <smbsrv/string.h>
  32 #include <smbsrv/smb_vops.h>
  33 #include <smbsrv/smb_kproto.h>
  34 #include <smbsrv/smb_fsops.h>
  35 
  36 /*
  37  * Characters we don't allow in DOS file names.
  38  * If a filename contains any of these chars, it should get mangled.
  39  *
  40  * '.' is also an invalid DOS char but since it's a special
  41  * case it doesn't appear in the list.
  42  */
  43 static const char invalid_dos_chars[] =
  44         "\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017"
  45         "\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
  46         " \"/\\:|<>*?";
  47 
  48 /*
  49  * According to MSKB article #142982, Windows deletes invalid chars and
  50  * spaces from file name in mangling process; and invalid chars include:
  51  * ."/\[]:;=,
  52  *
  53  * But some of these chars and some other chars (e.g. +) are replaced
  54  * with underscore (_). They are introduced here as special chars.
  55  */
  56 static const char special_chars[] = "[];=,+";
  57 
  58 #define isinvalid(c)    (strchr(invalid_dos_chars, c) || (c & 0x80))
  59 
  60 static int smb_generate_mangle(uint64_t, char *, size_t);
  61 static char smb_mangle_char(char);
  62 
  63 /*
  64  * Return true if name contains characters that are invalid in a file
  65  * name or it is a reserved DOS device name.  Otherwise, returns false.
  66  *
  67  * Control characters (values 0 - 31) and the following characters are
  68  * invalid:
  69  *      < > : " / \ | ? *
  70  */
  71 boolean_t
  72 smb_is_invalid_filename(const char *name)
  73 {
  74         const char *p;
  75 
  76         if ((p = strpbrk(name, invalid_dos_chars)) != NULL) {
  77                 if (*p != ' ')
  78                         return (B_TRUE);
  79         }
  80 
  81         return (smb_is_reserved_dos_name(name));
  82 }
  83 
  84 /*
  85  * smb_is_reserved_dos_name
  86  *
  87  * This function checks if the name is a reserved DOS device name.
  88  * The device name should not be followed immediately by an extension,
  89  * for example, NUL.txt.
  90  */
  91 boolean_t
  92 smb_is_reserved_dos_name(const char *name)
  93 {
  94         static char *cnames[] = { "CLOCK$", "COM1", "COM2", "COM3", "COM4",
  95                 "COM5", "COM6", "COM7", "COM8", "COM9", "CON" };
  96         static char *lnames[] = { "LPT1", "LPT2", "LPT3", "LPT4", "LPT5",
  97                 "LPT6", "LPT7", "LPT8", "LPT9" };
  98         static char *others[] = { "AUX", "NUL", "PRN" };
  99         char    **reserved;
 100         char    ch;
 101         int     n_reserved;
 102         int     len;
 103         int     i;
 104 
 105         ch = smb_toupper(*name);
 106 
 107         switch (ch) {
 108         case 'A':
 109         case 'N':
 110         case 'P':
 111                 reserved = others;
 112                 n_reserved = sizeof (others) / sizeof (others[0]);
 113                 break;
 114         case 'C':
 115                 reserved = cnames;
 116                 n_reserved = sizeof (cnames) / sizeof (cnames[0]);
 117                 break;
 118         case 'L':
 119                 reserved = lnames;
 120                 n_reserved = sizeof (lnames) / sizeof (lnames[0]);
 121                 break;
 122         default:
 123                 return (B_FALSE);
 124         }
 125 
 126         for (i  = 0; i < n_reserved; ++i) {
 127                 len = strlen(reserved[i]);
 128 
 129                 if (smb_strcasecmp(reserved[i], name, len) == 0) {
 130                         ch = *(name + len);
 131                         if ((ch == '\0') || (ch == '.'))
 132                                 return (B_TRUE);
 133                 }
 134         }
 135 
 136         return (B_FALSE);
 137 }
 138 
 139 /*
 140  * smb_needs_mangled
 141  *
 142  * A name needs to be mangled if any of the following are true:
 143  * - the first character is dot (.) and the name is not "." or ".."
 144  * - the name contains illegal or special charsacter
 145  * - the name name length > 12
 146  * - the number of dots == 0 and length > 8
 147  * - the number of dots > 1
 148  * - the number of dots == 1 and name is not 8.3
 149  */
 150 boolean_t
 151 smb_needs_mangled(const char *name)
 152 {
 153         int len, extlen, ndots;
 154         const char *p;
 155         const char *last_dot;
 156 
 157         if ((strcmp(name, ".") == 0) || (strcmp(name, "..") == 0))
 158                 return (B_FALSE);
 159 
 160         if (*name == '.')
 161                 return (B_TRUE);
 162 
 163         len = 0;
 164         ndots = 0;
 165         last_dot = NULL;
 166         for (p = name; *p != '\0'; ++p) {
 167                 if (smb_iscntrl(*p) ||
 168                     (strchr(special_chars, *p) != NULL) ||
 169                     (strchr(invalid_dos_chars, *p)) != NULL)
 170                         return (B_TRUE);
 171 
 172                 if (*p == '.') {
 173                         ++ndots;
 174                         last_dot = p;
 175                 }
 176                 ++len;
 177         }
 178 
 179         if ((len > SMB_NAME83_LEN) ||
 180             (ndots == 0 && len > SMB_NAME83_BASELEN) ||
 181             (ndots > 1)) {
 182                 return (B_TRUE);
 183         }
 184 
 185         if (last_dot != NULL) {
 186                 extlen = strlen(last_dot + 1);
 187                 if ((extlen == 0) || (extlen > SMB_NAME83_EXTLEN))
 188                         return (B_TRUE);
 189 
 190                 if ((len - extlen - 1) > SMB_NAME83_BASELEN)
 191                         return (B_TRUE);
 192         }
 193 
 194         return (B_FALSE);
 195 }
 196 
 197 /*
 198  * smb_mangle_char
 199  *
 200  * If c is an invalid DOS character or non-ascii, it should
 201  * not be used in the mangled name. We return -1 to indicate
 202  * an invalid character.
 203  *
 204  * If c is a special chars, it should be replaced with '_'.
 205  *
 206  * Otherwise c is returned as uppercase.
 207  */
 208 static char
 209 smb_mangle_char(char c)
 210 {
 211         if (isinvalid(c))
 212                 return (-1);
 213 
 214         if (strchr(special_chars, c))
 215                 return ('_');
 216 
 217         return (smb_toupper(c));
 218 }
 219 
 220 /*
 221  * smb_generate_mangle
 222  *
 223  * Generate a mangle string containing at least 2 characters and
 224  * at most (buflen - 1) characters.
 225  *
 226  * Returns the number of chars in the generated mangle.
 227  */
 228 static int
 229 smb_generate_mangle(uint64_t fid, char *buf, size_t buflen)
 230 {
 231         static char *base36 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 232         char *p = buf;
 233         int i;
 234 
 235         if (fid == 0)
 236                 fid = (uint64_t)-1;
 237 
 238         *p++ = '~';
 239         for (i = 2; (i < buflen) && (fid > 0); fid /= 36, ++i)
 240                 *p++ = base36[fid % 36];
 241         *p = '\0';
 242 
 243         return (i - 1);
 244 }
 245 
 246 /*
 247  * smb_maybe_mangled
 248  *
 249  * Mangled names should be valid DOS file names: less than 12 characters
 250  * long, contain at least one tilde character and conform to an 8.3 name
 251  * format.
 252  *
 253  * Returns true if the name looks like a mangled name.
 254  */
 255 boolean_t
 256 smb_maybe_mangled(char *name)
 257 {
 258         const char *p;
 259         boolean_t has_tilde = B_FALSE;
 260         int ndots = 0;
 261         int i;
 262 
 263         for (p = name, i = 0; (*p != '\0') && (i < SMB_NAME83_LEN); i++, p++) {
 264                 if ((strchr(special_chars, *p) != NULL) ||
 265                     (strchr(invalid_dos_chars, *p) != NULL))
 266                         return (B_FALSE);
 267 
 268                 if (*p == '.') {
 269                         if ((++ndots) > 1)
 270                                 return (B_FALSE);
 271                 }
 272 
 273                 if ((*p == '~') && (i < SMB_NAME83_BASELEN))
 274                         has_tilde = B_TRUE;
 275 
 276                 if (*p == '.' && !has_tilde)
 277                         return (B_FALSE);
 278         }
 279 
 280         return ((*p == '\0') && has_tilde);
 281 }
 282 
 283 /*
 284  * smb_mangle
 285  *
 286  * Microsoft knowledge base article #142982 describes how Windows
 287  * generates 8.3 filenames from long file names. Some other details
 288  * can be found in article #114816.
 289  *
 290  * This function will mangle the name whether mangling is required
 291  * or not. Callers should use smb_needs_mangled() to determine whether
 292  * mangling is required.
 293  *
 294  * name         original file name
 295  * fid          inode number to generate unique mangle
 296  * buf          output buffer (buflen bytes) to contain mangled name
 297  */
 298 void
 299 smb_mangle(const char *name, ino64_t fid, char *buf, size_t buflen)
 300 {
 301         int i, avail;
 302         const char *p;
 303         char c;
 304         char *pbuf;
 305         char mangle_buf[SMB_NAME83_BASELEN];
 306 
 307         ASSERT(name && buf && (buflen >= SMB_SHORTNAMELEN));
 308 
 309         avail = SMB_NAME83_BASELEN -
 310             smb_generate_mangle(fid, mangle_buf, SMB_NAME83_BASELEN);
 311         name += strspn(name, ".");
 312 
 313         /*
 314          * Copy up to avail characters from the base part of name
 315          * to buf then append the generated mangle string.
 316          */
 317         p = name;
 318         pbuf = buf;
 319         for (i = 0; (i < avail) && (*p != '\0') && (*p != '.'); ++i, ++p) {
 320                 if ((c = smb_mangle_char(*p)) == -1)
 321                         continue;
 322                 *pbuf++ = c;
 323         }
 324         *pbuf = '\0';
 325         (void) strlcat(pbuf, mangle_buf, SMB_NAME83_BASELEN);
 326         pbuf = strchr(pbuf, '\0');
 327 
 328         /*
 329          * Find the last dot in the name. If there is a dot and an
 330          * extension, append '.' and up to SMB_NAME83_EXTLEN extension
 331          * characters to the mangled name.
 332          */
 333         if (((p = strrchr(name, '.')) != NULL) && (*(++p) != '\0')) {
 334                 *pbuf++ = '.';
 335                 for (i = 0; (i < SMB_NAME83_EXTLEN) && (*p != '\0'); ++i, ++p) {
 336                         if ((c = smb_mangle_char(*p)) == -1)
 337                                 continue;
 338                         *pbuf++ = c;
 339                 }
 340         }
 341 
 342         *pbuf = '\0';
 343 }
 344 
 345 /*
 346  * smb_unmangle
 347  *
 348  * Given a mangled name, try to find the real file name as it appears
 349  * in the directory entry.
 350  *
 351  * smb_unmangle should only be called on names for which
 352  * smb_maybe_mangled() is true
 353  *
 354  * The flags arg is no longer used, but retained just to avoid
 355  * changing the many callers of this function.
 356  *
 357  * Returns:
 358  *   0       - SUCCESS. Unmangled name is returned in namebuf.
 359  *   EINVAL  - a parameter was invalid.
 360  *   ENOTDIR - dnode is not a directory node.
 361  *   ENOENT  - an unmangled name could not be found.
 362  */
 363 #define SMB_UNMANGLE_BUFSIZE    (4 * 1024)
 364 int
 365 smb_unmangle(smb_node_t *dnode, char *name, char *namebuf,
 366     int buflen, uint32_t flags)
 367 {
 368         _NOTE(ARGUNUSED(flags)) // avoid changing all callers
 369         int             err, eof, bufsize;
 370         uint64_t        offset;
 371         ino64_t         ino;
 372         char            *namep, *buf;
 373         char            shortname[SMB_SHORTNAMELEN];
 374         vnode_t         *vp;
 375         char            *bufptr;
 376         dirent64_t      *dp;
 377         cred_t          *cr = zone_kcred();
 378         int             rc = ENOENT;
 379 
 380         if (dnode == NULL || name == NULL || namebuf == NULL || buflen == 0)
 381                 return (EINVAL);
 382 
 383         ASSERT(smb_maybe_mangled(name) == B_TRUE);
 384 
 385         if (!smb_node_is_dir(dnode))
 386                 return (ENOTDIR);
 387 
 388         vp = dnode->vp;
 389         *namebuf = '\0';
 390 
 391         buf = kmem_alloc(SMB_UNMANGLE_BUFSIZE, KM_SLEEP);
 392         bufptr = buf;
 393         bufsize = 0;
 394         offset = 0;     // next entry offset
 395         eof = B_FALSE;
 396 
 397         for (;;) {
 398                 /*
 399                  * Read some entries, if buffer empty or
 400                  * we've scanned all of it.  Flags zero
 401                  * (no edirent, no ABE wanted here)
 402                  */
 403                 if (bufsize <= 0) {
 404                         bufsize = SMB_UNMANGLE_BUFSIZE;
 405                         rc = smb_vop_readdir(vp, offset, buf,
 406                             &bufsize, &eof, 0, cr);
 407                         if (rc != 0)
 408                                 break; /* error */
 409                         if (bufsize == 0) {
 410                                 eof = B_TRUE;
 411                                 rc = ENOENT;
 412                                 break;
 413                         }
 414                         bufptr = buf;
 415                 }
 416                 /* LINTED pointer alignment */
 417                 dp = (dirent64_t *)bufptr;
 418 
 419                 /*
 420                  * Partial records are not supposed to happen,
 421                  * but let's be defensive. If this happens,
 422                  * restart at the current offset.
 423                  */
 424                 bufptr += dp->d_reclen;
 425                 bufsize -= dp->d_reclen;
 426                 if (bufsize < 0)
 427                         continue;
 428 
 429                 offset = dp->d_off;
 430                 ino = dp->d_ino;
 431                 namep = dp->d_name;
 432 
 433                 /* skip non utf8 filename */
 434                 if (u8_validate(namep, strlen(namep), NULL,
 435                     U8_VALIDATE_ENTIRE, &err) < 0)
 436                         continue;
 437 
 438                 smb_mangle(namep, ino, shortname, SMB_SHORTNAMELEN);
 439                 if (smb_strcasecmp(name, shortname, 0) == 0) {
 440                         (void) strlcpy(namebuf, namep, buflen);
 441                         rc = 0;
 442                         break;
 443                 }
 444         }
 445 
 446         kmem_free(buf, SMB_UNMANGLE_BUFSIZE);
 447         return (rc);
 448 }