xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_mangle_name.c (revision 6d52f363e3b2c0c5da672c5b8c8adec99d345f38)
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