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 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <sys/types.h> 27 #include <sys/param.h> 28 #include <sys/sunddi.h> 29 #include <sys/errno.h> 30 #include <smbsrv/string.h> 31 #include <smbsrv/ctype.h> 32 #include <smbsrv/smb_i18n.h> 33 #include <smbsrv/smb_vops.h> 34 #include <smbsrv/smb_incl.h> 35 #include <smbsrv/smb_fsops.h> 36 37 #define SMB_NAME83_BASELEN 8 38 #define SMB_NAME83_LEN 12 39 40 /* 41 * Characters we don't allow in DOS file names. 42 * If a filename contains any of these chars, it should get mangled. 43 * 44 * '.' is also an invalid DOS char but since it's a special 45 * case it doesn't appear in the list. 46 */ 47 static char *invalid_dos_chars = 48 "\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017" 49 "\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" 50 " \"/\\:|<>*?"; 51 52 /* 53 * According to MSKB article #142982, Windows deletes invalid chars and 54 * spaces from file name in mangling process; and invalid chars include: 55 * ."/\[]:;=, 56 * 57 * But some of these chars and some other chars (e.g. +) are replaced 58 * with underscore (_). They are introduced here as special chars. 59 */ 60 static char *special_chars = "[];=,+"; 61 62 #define isinvalid(c) (strchr(invalid_dos_chars, c) || (c & 0x80)) 63 64 static int smb_match_unknown(char *name, char *pattern); 65 static boolean_t smb_is_reserved_dos_name(const char *name); 66 67 /* 68 * smb_match_name 69 * 70 * This function will mangle the "name" field and save the resulted 71 * shortname to the "shortname" field and 8.3 name to "name83" field. 72 * The three fields, "name", "shortname" and "name83" will then be 73 * sent for pattern match with "pattern" field. 74 * 75 * The 0 is returned when the name is a reserved dos name, no match 76 * for the pattern or any type of failure. The 1 is returned when 77 * there is a match. 78 */ 79 int 80 smb_match_name(ino64_t fileid, char *name, char *pattern, boolean_t ignore_case) 81 { 82 int rc = 0; 83 int force; 84 char name83[SMB_SHORTNAMELEN]; 85 char shortname[SMB_SHORTNAMELEN]; 86 87 /* Leading or trailing dots are disallowed */ 88 if (smb_is_reserved_dos_name(name)) 89 return (0); 90 91 for (force = 0; (force < 2 && rc == 0); force++) { 92 (void) smb_mangle_name(fileid, name, shortname, name83, force); 93 94 rc = smb_match_ci(pattern, name); 95 96 /* If no match, check for shortname (if any) */ 97 98 if (rc == 0 && strchr(pattern, '~')) 99 if (*shortname != 0) 100 rc = smb_match_ci(pattern, shortname); 101 102 /* 103 * Sigh... DOS Shells use short name 104 * interchangeably with long case sensitive 105 * names. So check that too... 106 */ 107 if ((rc == 0) && !ignore_case) 108 rc = smb_match83(pattern, name83); 109 110 /* 111 * Still not found and potentially a premangled name... 112 * Check to see if the butt-head programmer is 113 * assuming that we mangle names in the same manner 114 * as NT... 115 */ 116 if (rc == 0) 117 rc = smb_match_unknown(name, pattern); 118 } 119 120 return (rc); 121 } 122 123 /* 124 * smb_match_unknown 125 * 126 * I couldn't figure out what the assumptions of this peice of 127 * code about the format of pattern and name are and so how 128 * it's trying to match them. I just cleaned it up a little bit! 129 * 130 * If anybody could figure out what this is doing, please put 131 * comment here and change the function's name! 132 */ 133 static int 134 smb_match_unknown(char *name, char *pattern) 135 { 136 int rc; 137 char nc, pc; 138 char *np, *pp; 139 140 rc = 0; 141 if (utf8_isstrupr(pattern) <= 0) 142 return (rc); 143 144 np = name; 145 pp = pattern; 146 147 pc = *pattern; 148 while ((nc = *np++) != 0) { 149 if (nc == ' ') 150 continue; 151 152 nc = mts_toupper(nc); 153 if ((pc = *pp++) != nc) 154 break; 155 } 156 157 if ((pc == '~') && 158 (pp != (pattern + 1)) && 159 ((pc = *pp++) != 0)) { 160 while (mts_isdigit(pc)) 161 pc = *pp++; 162 163 if (pc == '.') { 164 while ((nc = *np++) != 0) { 165 if (nc == '.') 166 break; 167 } 168 169 while ((nc = *np++) != 0) { 170 nc = mts_toupper(nc); 171 if ((pc = *pp++) != nc) 172 break; 173 } 174 } 175 176 if (pc == 0) 177 rc = 1; 178 } 179 180 return (rc); 181 } 182 183 /* 184 * Return true if name contains characters that are invalid in a file 185 * name or it is a reserved DOS device name. Otherwise, returns false. 186 * 187 * Control characters (values 0 - 31) and the following characters are 188 * invalid: 189 * < > : " / \ | ? * 190 */ 191 boolean_t 192 smb_is_invalid_filename(const char *name) 193 { 194 const char *p; 195 196 if ((p = strpbrk(name, invalid_dos_chars)) != NULL) { 197 if (*p != ' ') 198 return (B_TRUE); 199 } 200 201 return (smb_is_reserved_dos_name(name)); 202 } 203 204 /* 205 * smb_is_reserved_dos_name 206 * 207 * This function checks if the name is a reserved DOS device name. 208 * The device name should not be followed immediately by an extension, 209 * for example, NUL.txt. 210 */ 211 static boolean_t 212 smb_is_reserved_dos_name(const char *name) 213 { 214 static char *cnames[] = { "CLOCK$", "COM1", "COM2", "COM3", "COM4", 215 "COM5", "COM6", "COM7", "COM8", "COM9", "CON" }; 216 static char *lnames[] = { "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", 217 "LPT6", "LPT7", "LPT8", "LPT9" }; 218 static char *others[] = { "AUX", "NUL", "PRN" }; 219 char **reserved; 220 char ch; 221 int n_reserved; 222 int len; 223 int i; 224 225 ch = mts_toupper(*name); 226 227 switch (ch) { 228 case 'A': 229 case 'N': 230 case 'P': 231 reserved = others; 232 n_reserved = sizeof (others) / sizeof (others[0]); 233 break; 234 case 'C': 235 reserved = cnames; 236 n_reserved = sizeof (cnames) / sizeof (cnames[0]); 237 break; 238 case 'L': 239 reserved = lnames; 240 n_reserved = sizeof (lnames) / sizeof (lnames[0]); 241 break; 242 default: 243 return (B_FALSE); 244 } 245 246 for (i = 0; i < n_reserved; ++i) { 247 len = strlen(reserved[i]); 248 249 if (utf8_strncasecmp(reserved[i], name, len) == 0) { 250 ch = *(name + len); 251 if ((ch == '\0') || (ch == '.')) 252 return (B_TRUE); 253 } 254 } 255 256 return (B_FALSE); 257 } 258 259 /* 260 * smb_needs_mangle 261 * 262 * Determines whether the given name needs to get mangled. 263 * 264 * Here are the (known) rules: 265 * 266 * 1st char is dot (.) 267 * name length > 12 chars 268 * # dots > 1 269 * # dots == 0 and length > 8 270 * # dots == 1 and name isn't 8.3 271 * contains illegal chars 272 */ 273 int 274 smb_needs_mangle(char *name, char **dot_pos) 275 { 276 int len, ndots; 277 char *namep; 278 char *last_dot; 279 280 /* 281 * Returning (1) for these cases forces consistency with how 282 * these names are treated (smb_mangle_name() will produce an 8.3 name 283 * for these) 284 */ 285 if ((strcmp(name, ".") == 0) || (strcmp(name, "..") == 0)) 286 return (1); 287 288 /* skip the leading dots (if any) */ 289 for (namep = name; *namep == '.'; namep++) 290 ; 291 292 len = ndots = 0; 293 last_dot = 0; 294 for (; *namep; namep++) { 295 len++; 296 if (*namep == '.') { 297 /* keep the position of last dot */ 298 last_dot = namep; 299 ndots++; 300 } 301 } 302 *dot_pos = last_dot; 303 304 /* Windows mangles names like .a, .abc, or .abcd */ 305 if (*name == '.') 306 return (1); 307 308 if (len > 12) 309 return (1); 310 311 switch (ndots) { 312 case 0: 313 /* no dot */ 314 if (len > 8) 315 return (1); 316 break; 317 318 case 1: 319 /* just one dot */ 320 /*LINTED E_PTR_DIFF_OVERFLOW*/ 321 if (((last_dot - name) > 8) || /* name length > 8 */ 322 (strlen(last_dot + 1) > 3)) /* extention > 3 */ 323 return (1); 324 break; 325 326 default: 327 /* more than one dot */ 328 return (1); 329 } 330 331 for (namep = name; *namep; namep++) { 332 if (!mts_isascii(*namep) || 333 strchr(special_chars, *namep) || 334 strchr(invalid_dos_chars, *namep)) 335 return (1); 336 } 337 338 return (0); 339 } 340 341 /* 342 * smb_needs_shortname 343 * 344 * Determine whether a shortname should be generated for a file name that is 345 * already in 8.3 format. 346 * 347 * Paramters: 348 * name - original file name 349 * 350 * Return: 351 * 1 - Shortname is required to be generated. 352 * 0 - No shortname needs to be generated. 353 * 354 * Note 355 * ======= 356 * Windows NT server: shortname is created only if either 357 * the filename or extension portion of 358 * a file is made up of mixed case. 359 * Windows 2000 server: shortname is not created regardless 360 * of the case. 361 * Windows 2003 server: [Same as Windows NT server.] 362 * 363 * StorEdge will conform to the rule used by Windows NT/2003 server. 364 * 365 * For instance: 366 * File | Create shortname? 367 * ================================ 368 * nf.txt | N 369 * NF.TXT | N 370 * NF.txt | N 371 * nf | N 372 * NF | N 373 * nF.txt | Y 374 * nf.TxT | Y 375 * Nf | Y 376 * nF | Y 377 * 378 */ 379 static int 380 smb_needs_shortname(char *name) 381 { 382 char buf[9]; 383 int len; 384 int create = 0; 385 const char *dot_pos = 0; 386 387 dot_pos = strrchr(name, '.'); 388 /*LINTED E_PTRDIFF_OVERFLOW*/ 389 len = (!dot_pos) ? strlen(name) : (dot_pos - name); 390 /* First, examine the name portion of the file */ 391 if (len) { 392 (void) snprintf(buf, len + 1, "%s", name); 393 /* if the name contains both lower and upper cases */ 394 if (utf8_isstrupr(buf) == 0 && utf8_isstrlwr(buf) == 0) { 395 /* create shortname */ 396 create = 1; 397 } else if (dot_pos) { 398 /* Next, examine the extension portion of the file */ 399 (void) snprintf(buf, sizeof (buf), "%s", dot_pos + 1); 400 /* 401 * if the extension contains both lower and upper 402 * cases 403 */ 404 if (utf8_isstrupr(buf) == 0 && utf8_isstrlwr(buf) == 0) 405 /* create shortname */ 406 create = 1; 407 } 408 } 409 410 return (create); 411 } 412 413 /* 414 * smb_mangle_char 415 * 416 * If given char is an invalid DOS character or it's not an 417 * ascii char, it should be deleted from mangled and 8.3 name. 418 * 419 * If given char is one of special chars, it should be replaced 420 * with '_'. 421 * 422 * Otherwise just make it upper case. 423 */ 424 static unsigned char 425 smb_mangle_char(unsigned char ch) 426 { 427 if (isinvalid(ch)) 428 return (0); 429 430 if (strchr(special_chars, ch)) 431 return ('_'); 432 433 return (mts_toupper(ch)); 434 } 435 436 /* 437 * smb_generate_mangle 438 * 439 * Generates a mangle string which contains 440 * at least 2 (considering fileid cannot be 0) 441 * and at most 7 chars. 442 * 443 * Returns the number of chars in the generated mangle. 444 */ 445 static int 446 smb_generate_mangle(ino64_t fileid, unsigned char *mangle_buf) 447 { 448 /* 449 * 36**6 = 2176782336: more than enough to express inodes in 6 450 * chars 451 */ 452 static char *base36 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 453 unsigned char *manglep = mangle_buf; 454 455 for (*manglep++ = '~'; fileid > 0; fileid /= 36) 456 *manglep++ = base36[fileid % 36]; 457 *manglep = 0; 458 459 /*LINTED E_PTRDIFF_OVERFLOW*/ 460 return (manglep - mangle_buf); 461 } 462 463 /* 464 * smb_maybe_mangled_name 465 * 466 * Mangled names should be valid DOS file names: less than 12 characters 467 * long, contain at least one tilde character and conform to an 8.3 name 468 * format. 469 * 470 * Returns true if the name looks like a mangled name. 471 */ 472 int 473 smb_maybe_mangled_name(char *name) 474 { 475 const char *p; 476 boolean_t has_tilde = B_FALSE; 477 int ndots = 0; 478 int i; 479 480 for (p = name, i = 0; (*p != '\0') && (i < SMB_NAME83_LEN); i++, p++) { 481 if ((strchr(special_chars, *p) != NULL) || 482 (strchr(invalid_dos_chars, *p) != NULL)) 483 return (B_FALSE); 484 485 if (*p == '.') { 486 if ((++ndots) > 1) 487 return (B_FALSE); 488 } 489 490 if ((*p == '~') && (i < SMB_NAME83_BASELEN)) 491 has_tilde = B_TRUE; 492 493 if (*p == '.' && !has_tilde) 494 return (B_FALSE); 495 } 496 497 return ((*p == 0) && has_tilde); 498 } 499 500 /* 501 * smb_mangle_name 502 * 503 * Microsoft knowledge base article #142982 describes how Windows 504 * generates 8.3 filenames from long file names. Some other details 505 * can be found in article #114816. 506 * 507 * The function first checks to see whether the given name needs mangling. 508 * If not, and the force parameter is not set, then no mangling is done, 509 * but both the shortname (if needed) and the 8.3 name are produced and 510 * returned. 511 * 512 * If the "force" parameter is set (as will be the case for case-insensitive 513 * collisions), then the name will be mangled. 514 * 515 * Whenever mangling is needed, both the shortname and the 8.3 names are 516 * produced and returned. 517 * 518 * For example, the xxx.xy in 8.3 format will be "xxx .xy ". 519 */ 520 521 int smb_mangle_name( 522 ino64_t fileid, /* inode number to generate unique mangle */ 523 char *name, /* original file name */ 524 char *shortname, /* mangled name (if applicable) */ 525 char *name83, /* (mangled) name in 8.3 format */ 526 int force) /* force mangling even if mangling is not */ 527 /* needed according to standard algorithm */ 528 { 529 int avail; 530 unsigned char ch; 531 unsigned char mangle_buf[8]; 532 unsigned char *namep; 533 unsigned char *manglep; 534 unsigned char *out_short; 535 unsigned char *out_83; 536 char *dot_pos = NULL; 537 538 /* 539 * NOTE: 540 * This function used to consider filename case 541 * in order to mangle. I removed those checks. 542 */ 543 544 *shortname = *name83 = 0; 545 546 /* Allow dot and dot dot up front */ 547 if (strcmp(name, ".") == 0) { 548 /* no shortname */ 549 (void) strcpy(name83, ". . "); 550 return (1); 551 } 552 553 if (strcmp(name, "..") == 0) { 554 /* no shortname */ 555 (void) strcpy(name83, ".. . "); 556 return (1); 557 } 558 559 out_short = (unsigned char *)shortname; 560 out_83 = (unsigned char *)name83; 561 562 if ((smb_needs_mangle(name, &dot_pos) == 0) && (force == 0)) { 563 /* no mangle */ 564 565 /* check if shortname is required or not */ 566 if (smb_needs_shortname(name)) { 567 namep = (unsigned char *)name; 568 while (*namep) 569 *out_short++ = mts_toupper(*namep++); 570 *out_short = '\0'; 571 } 572 573 out_83 = (unsigned char *)name83; 574 (void) strcpy((char *)out_83, " . "); 575 while (*name && *name != '.') 576 *out_83++ = mts_toupper(*name++); 577 578 if (*name == '.') { 579 /* copy extension */ 580 name++; 581 out_83 = (unsigned char *)name83 + 9; 582 while (*name) 583 *out_83++ = mts_toupper(*name++); 584 } 585 return (1); 586 } 587 588 avail = 8 - smb_generate_mangle(fileid, mangle_buf); 589 590 /* 591 * generated mangle part has always less than 8 chars, so 592 * use the chars before the first dot in filename 593 * and try to generate a full 8 char name. 594 */ 595 596 /* skip the leading dots (if any) */ 597 for (namep = (unsigned char *)name; *namep == '.'; namep++) 598 ; 599 600 for (; avail && *namep && (*namep != '.'); namep++) { 601 ch = smb_mangle_char(*namep); 602 if (ch == 0) 603 continue; 604 *out_short++ = *out_83++ = ch; 605 avail--; 606 } 607 608 /* Copy in mangled part */ 609 manglep = mangle_buf; 610 611 while (*manglep) 612 *out_short++ = *out_83++ = *(manglep++); 613 614 /* Pad any leftover in 8.3 name with spaces */ 615 while (avail--) 616 *out_83++ = ' '; 617 618 /* Work on extension now */ 619 avail = 3; 620 *out_83++ = '.'; 621 if (dot_pos) { 622 namep = (unsigned char *)dot_pos + 1; 623 if (*namep != 0) { 624 *out_short++ = '.'; 625 for (; avail && *namep; namep++) { 626 ch = smb_mangle_char(*namep); 627 if (ch == 0) 628 continue; 629 630 *out_short++ = *out_83++ = ch; 631 avail--; 632 } 633 } 634 } 635 636 while (avail--) 637 *out_83++ = ' '; 638 639 *out_short = *out_83 = '\0'; 640 641 return (1); 642 } 643 644 /* 645 * smb_unmangle_name 646 * 647 * Given a mangled name, try to find the real file name as it appears 648 * in the directory entry. If the name does not contain a ~, it is most 649 * likely not a mangled name but the caller can still try to get the 650 * actual on-disk name by setting the "od" parameter. 651 * 652 * Returns 0 if a name has been returned in real_name. There are three 653 * possible scenarios: 654 * 1. Name did not contain a ~ and "od" was not set, in which 655 * case, real_name contains name. 656 * 2. Name did not contain a ~ and "od" was set, in which 657 * case, real_name contains the actual directory entry name. 658 * 3. Name did contain a ~, in which case, name was mangled and 659 * real_name contains the actual directory entry name. 660 * 661 * EINVAL: a parameter was invalid. 662 * ENOENT: an unmangled name could not be found. 663 */ 664 665 int 666 smb_unmangle_name(struct smb_request *sr, cred_t *cred, smb_node_t *dir_node, 667 char *name, char *real_name, int realname_size, char *shortname, 668 char *name83, int ondisk) 669 { 670 int err; 671 struct smb_node *snode = NULL; 672 smb_attr_t ret_attr; 673 char namebuf[SMB_SHORTNAMELEN]; 674 char *path; 675 uint16_t odid; 676 smb_odir_t *od; 677 smb_odirent_t *odirent; 678 boolean_t eos; 679 680 if (dir_node == NULL || name == NULL || real_name == NULL || 681 realname_size == 0) 682 return (EINVAL); 683 684 *real_name = '\0'; 685 snode = NULL; 686 687 if (smb_maybe_mangled_name(name) == 0) { 688 if (ondisk == 0) { 689 (void) strlcpy(real_name, name, realname_size); 690 return (0); 691 } 692 693 err = smb_fsop_lookup(sr, cred, 0, sr->tid_tree->t_snode, 694 dir_node, name, &snode, &ret_attr, NULL, NULL); 695 696 if (err != 0) 697 return (err); 698 699 (void) strlcpy(real_name, snode->od_name, realname_size); 700 smb_node_release(snode); 701 return (0); 702 } 703 704 if (shortname == 0) 705 shortname = namebuf; 706 if (name83 == 0) 707 name83 = namebuf; 708 709 /* determine the pathname and open an smb_odir_t */ 710 path = kmem_alloc(MAXNAMELEN, KM_SLEEP); 711 if ((err = vnodetopath(sr->tid_tree->t_snode->vp, dir_node->vp, path, 712 MAXNAMELEN, kcred)) != 0) 713 return (err); 714 715 if ((strlcat(path, "/*", MAXNAMELEN) >= MAXNAMELEN) || 716 ((odid = smb_odir_open(sr, path, SMB_SEARCH_ATTRIBUTES)) == 0) || 717 ((od = smb_tree_lookup_odir(sr->tid_tree, odid)) == NULL)) { 718 err = ENOENT; 719 } 720 kmem_free(path, MAXNAMELEN); 721 if (err != 0) 722 return (err); 723 724 odirent = kmem_alloc(sizeof (smb_odirent_t), KM_SLEEP); 725 for (;;) { 726 err = smb_odir_read(sr, od, odirent, &eos); 727 if ((err != 0) || (eos)) 728 break; 729 730 (void) smb_mangle_name(odirent->od_ino, odirent->od_name, 731 shortname, name83, 1); 732 733 if (utf8_strcasecmp(name, shortname) == 0) { 734 (void) strlcpy(real_name, odirent->od_name, 735 realname_size); 736 kmem_free(odirent, sizeof (smb_odirent_t)); 737 smb_odir_release(od); 738 smb_odir_close(od); 739 return (0); 740 } 741 } 742 743 kmem_free(odirent, sizeof (smb_odirent_t)); 744 smb_odir_release(od); 745 smb_odir_close(od); 746 return (ENOENT); 747 } 748