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