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