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