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