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