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
smb_is_invalid_filename(const char * name)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
smb_is_reserved_dos_name(const char * name)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
smb_needs_mangled(const char * name)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
smb_mangle_char(char c)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
smb_generate_mangle(uint64_t fid,char * buf,size_t buflen)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
smb_maybe_mangled(char * name)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
smb_mangle(const char * name,ino64_t fid,char * buf,size_t buflen)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
smb_unmangle(smb_node_t * dnode,char * name,char * namebuf,int buflen,uint32_t flags)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