xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb_mangle_name.c (revision 6e375c8351497b82ffa4f33cbf61d712999b4605)
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 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"@(#)smb_mangle_name.c	1.3	08/08/07 SMI"
27 
28 #include <sys/types.h>
29 #include <sys/sunddi.h>
30 #include <sys/errno.h>
31 #include <smbsrv/string.h>
32 #include <smbsrv/ctype.h>
33 #include <smbsrv/smb_i18n.h>
34 #include <smbsrv/smb_vops.h>
35 #include <smbsrv/smb_incl.h>
36 #include <smbsrv/smb_fsops.h>
37 
38 static int smb_match_unknown(char *name, char *pattern);
39 static int smb_is_reserved_dos_name(char *name);
40 static int smb_match_reserved(char *name, char *rsrv);
41 
42 /*
43  * smb_match_name
44  *
45  * This function will mangle the "name" field and save the resulted
46  * shortname to the "shortname" field and 8.3 name to "name83" field.
47  * The three fields, "name", "shortname" and "name83" will then be
48  * sent for pattern match with "pattern" field.
49  *
50  * The 0 is returned when the name is a reserved dos name, no match
51  * for the pattern or any type of failure. The 1 is returned when
52  * there is a match.
53  */
54 int
55 smb_match_name(ino64_t fileid, char *name, char *shortname,
56     char *name83, char *pattern, boolean_t ignore_case)
57 {
58 	int rc = 0;
59 	int force;
60 
61 	/* Leading or trailing dots are disallowed */
62 	if (smb_is_reserved_dos_name(name))
63 		return (0);
64 
65 	for (force = 0; (force < 2 && rc == 0); force++) {
66 		(void) smb_mangle_name(fileid, name, shortname, name83, force);
67 
68 		rc = smb_match_ci(pattern, name);
69 
70 		/* If no match, check for shortname (if any) */
71 
72 		if (rc == 0 && strchr(pattern, '~'))
73 			if (*shortname != 0)
74 				rc = smb_match_ci(pattern, shortname);
75 
76 		/*
77 		 * Sigh... DOS Shells use short name
78 		 * interchangeably with long case sensitive
79 		 * names. So check that too...
80 		 */
81 		if ((rc == 0) && !ignore_case)
82 			rc = smb_match83(pattern, name83);
83 
84 		/*
85 		 * Still not found and potentially a premangled name...
86 		 * Check to see if the butt-head programmer is
87 		 * assuming that we mangle names in the same manner
88 		 * as NT...
89 		 */
90 		if (rc == 0)
91 			rc = smb_match_unknown(name, pattern);
92 	}
93 
94 	return (rc);
95 }
96 
97 /*
98  * smb_match_unknown
99  *
100  * I couldn't figure out what the assumptions of this peice of
101  * code about the format of pattern and name are and so how
102  * it's trying to match them.  I just cleaned it up a little bit!
103  *
104  * If anybody could figure out what this is doing, please put
105  * comment here and change the function's name!
106  */
107 static int
108 smb_match_unknown(char *name, char *pattern)
109 {
110 	int rc;
111 	char nc, pc;
112 	char *np, *pp;
113 
114 	rc = 0;
115 	if (utf8_isstrupr(pattern) <= 0)
116 		return (rc);
117 
118 	np = name;
119 	pp = pattern;
120 
121 	pc = *pattern;
122 	while ((nc = *np++) != 0) {
123 		if (nc == ' ')
124 			continue;
125 
126 		nc = mts_toupper(nc);
127 		if ((pc = *pp++) != nc)
128 			break;
129 	}
130 
131 	if ((pc == '~') &&
132 	    (pp != (pattern + 1)) &&
133 	    ((pc = *pp++) != 0)) {
134 		while (mts_isdigit(pc))
135 			pc = *pp++;
136 
137 		if (pc == '.') {
138 			while ((nc = *np++) != 0) {
139 				if (nc == '.')
140 					break;
141 			}
142 
143 			while ((nc = *np++) != 0) {
144 				nc = mts_toupper(nc);
145 				if ((pc = *pp++) != nc)
146 					break;
147 			}
148 		}
149 
150 		if (pc == 0)
151 			rc = 1;
152 	}
153 
154 	return (rc);
155 }
156 
157 /*
158  * smb_match_reserved
159  *
160  * Checks if the given name matches given
161  * DOS reserved name prefix.
162  *
163  * Returns 1 if match, 0 otherwise
164  */
165 static int
166 smb_match_reserved(char *name, char *rsrv)
167 {
168 	char ch;
169 
170 	int len = strlen(rsrv);
171 	return (!utf8_strncasecmp(rsrv, name, len) &&
172 	    ((ch = *(name + len)) == 0 || ch == '.'));
173 }
174 
175 /*
176  * smb_is_reserved_dos_name
177  *
178  * This function checks if the name is a reserved dos name.
179  *
180  * The function returns 1 when the name is a reserved dos name;
181  * otherwise, it returns 0.
182  */
183 static int
184 smb_is_reserved_dos_name(char *name)
185 {
186 	char	ch;
187 
188 	/*
189 	 * Eliminate all names reserved by DOS and Windows.
190 	 */
191 	ch = mts_toupper(*name);
192 
193 	switch (ch) {
194 	case 'A':
195 		if (smb_match_reserved(name, "AUX"))
196 			return (1);
197 		break;
198 
199 	case 'C':
200 		if (smb_match_reserved(name, "CLOCK$") ||
201 		    smb_match_reserved(name, "COM1") ||
202 		    smb_match_reserved(name, "COM2") ||
203 		    smb_match_reserved(name, "COM3") ||
204 		    smb_match_reserved(name, "COM4") ||
205 		    smb_match_reserved(name, "CON")) {
206 			return (1);
207 		}
208 
209 		break;
210 
211 	case 'L':
212 		if ((utf8_strncasecmp("LPT1", name, 4) == 0) ||
213 		    (utf8_strncasecmp("LPT2", name, 4) == 0) ||
214 		    (utf8_strncasecmp("LPT3", name, 4) == 0))
215 			return (1);
216 		break;
217 
218 	case 'N':
219 		if (smb_match_reserved(name, "NUL"))
220 			return (1);
221 		break;
222 
223 	case 'P':
224 		if (smb_match_reserved(name, "PRN"))
225 			return (1);
226 	}
227 
228 	/*
229 	 * If the server is configured to support Catia Version 5
230 	 * deployments, any filename that contains backslash will
231 	 * have already been translated to the UTF-8 encoding of
232 	 * Latin Small Letter Y with Diaeresis. Thus, the check
233 	 * for backslash in the filename is not necessary.
234 	 */
235 #ifdef CATIA_SUPPORT
236 	/* XXX Catia support */
237 	if ((get_caps() & NFCAPS_CATIA) == 0) {
238 		while (*name != 0) {
239 			if (*name == '\\')
240 				return (1);
241 			name++;
242 		}
243 	}
244 #endif /* CATIA_SUPPORT */
245 
246 	return (0);
247 }
248 
249 /*
250  * Characters we don't allow in DOS file names.
251  * If a filename contains any of these chars, it should
252  * get mangled.
253  *
254  * '.' is also an invalid DOS char but since it's a special
255  * case it doesn't appear in the list.
256  */
257 static char *invalid_dos_chars =
258 	"\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017"
259 	"\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037"
260 	" \"/\\:|<>*?";
261 
262 /*
263  * According to MSKB article #142982, Windows deletes invalid chars and
264  * spaces from file name in mangling process; and invalid chars include:
265  * ."/\[]:;=,
266  *
267  * But some of these chars and some other chars (e.g. +) are replaced
268  * with underscore (_). They are introduced here as special chars.
269  */
270 static char *special_chars = "[];=,+";
271 
272 #define	isinvalid(c)	(strchr(invalid_dos_chars, c) || (c & 0x80))
273 
274 /*
275  * smb_needs_mangle
276  *
277  * Determines whether the given name needs to get mangled.
278  *
279  * Here are the (known) rules:
280  *
281  *	1st char is dot (.)
282  *	name length > 12 chars
283  *	# dots > 1
284  *	# dots == 0 and length > 8
285  *	# dots == 1 and name isn't 8.3
286  *	contains illegal chars
287  */
288 int
289 smb_needs_mangle(char *name, char **dot_pos)
290 {
291 	int len, ndots;
292 	char *namep;
293 	char *last_dot;
294 
295 	/*
296 	 * Returning (1) for these cases forces consistency with how
297 	 * these names are treated (smb_mangle_name() will produce an 8.3 name
298 	 * for these)
299 	 */
300 	if ((strcmp(name, ".") == 0) || (strcmp(name, "..") == 0))
301 		return (1);
302 
303 	/* skip the leading dots (if any) */
304 	for (namep = name; *namep == '.'; namep++)
305 		;
306 
307 	len = ndots = 0;
308 	last_dot = 0;
309 	for (; *namep; namep++) {
310 		len++;
311 		if (*namep == '.') {
312 			/* keep the position of last dot */
313 			last_dot = namep;
314 			ndots++;
315 		}
316 	}
317 	*dot_pos = last_dot;
318 
319 	/* Windows mangles names like .a, .abc, or .abcd */
320 	if (*name == '.')
321 		return (1);
322 
323 	if (len > 12)
324 		return (1);
325 
326 	switch (ndots) {
327 	case 0:
328 		/* no dot */
329 		if (len > 8)
330 			return (1);
331 		break;
332 
333 	case 1:
334 		/* just one dot */
335 		/*LINTED E_PTR_DIFF_OVERFLOW*/
336 		if (((last_dot - name) > 8) ||		/* name length > 8 */
337 		    (strlen(last_dot + 1) > 3))		/* extention > 3 */
338 			return (1);
339 		break;
340 
341 	default:
342 		/* more than one dot */
343 		return (1);
344 	}
345 
346 	for (namep = name; *namep; namep++) {
347 		if (!mts_isascii(*namep) ||
348 		    strchr(special_chars, *namep) ||
349 		    strchr(invalid_dos_chars, *namep))
350 			return (1);
351 	}
352 
353 	return (0);
354 }
355 
356 /*
357  * smb_needs_shortname
358  *
359  * Determine whether a shortname should be generated for a file name that is
360  * already in 8.3 format.
361  *
362  * Paramters:
363  *   name - original file name
364  *
365  * Return:
366  *   1 - Shortname is required to be generated.
367  *   0 - No shortname needs to be generated.
368  *
369  * Note
370  * =======
371  * Windows NT server:       shortname is created only if either
372  *                          the filename or extension portion of
373  *                          a file is made up of mixed case.
374  * Windows 2000 server:     shortname is not created regardless
375  *                          of the case.
376  * Windows 2003 server:     [Same as Windows NT server.]
377  *
378  * StorEdge will conform to the rule used by Windows NT/2003 server.
379  *
380  * For instance:
381  *    File      | Create shortname?
382  * ================================
383  *  nf.txt      | N
384  *  NF.TXT      | N
385  *  NF.txt      | N
386  *  nf          | N
387  *  NF          | N
388  *  nF.txt      | Y
389  *  nf.TxT      | Y
390  *  Nf          | Y
391  *  nF          | Y
392  *
393  */
394 static int
395 smb_needs_shortname(char *name)
396 {
397 	char buf[9];
398 	int len;
399 	int create = 0;
400 	const char *dot_pos = 0;
401 
402 	dot_pos = strrchr(name, '.');
403 	/*LINTED E_PTRDIFF_OVERFLOW*/
404 	len = (!dot_pos) ? strlen(name) : (dot_pos - name);
405 	/* First, examine the name portion of the file */
406 	if (len) {
407 		(void) snprintf(buf, len + 1, "%s", name);
408 		/* if the name contains both lower and upper cases */
409 		if (utf8_isstrupr(buf) == 0 && utf8_isstrlwr(buf) == 0) {
410 			/* create shortname */
411 			create = 1;
412 		} else 	if (dot_pos) {
413 			/* Next, examine the extension portion of the file */
414 			(void) snprintf(buf, sizeof (buf), "%s", dot_pos + 1);
415 			/*
416 			 * if the extension contains both lower and upper
417 			 * cases
418 			 */
419 			if (utf8_isstrupr(buf) == 0 && utf8_isstrlwr(buf) == 0)
420 				/* create shortname */
421 				create = 1;
422 		}
423 	}
424 
425 	return (create);
426 }
427 
428 /*
429  * smb_mangle_char
430  *
431  * If given char is an invalid DOS character or it's not an
432  * ascii char, it should be deleted from mangled and 8.3 name.
433  *
434  * If given char is one of special chars, it should be replaced
435  * with '_'.
436  *
437  * Otherwise just make it upper case.
438  */
439 static unsigned char
440 smb_mangle_char(unsigned char ch)
441 {
442 	if (isinvalid(ch))
443 		return (0);
444 
445 	if (strchr(special_chars, ch))
446 		return ('_');
447 
448 	return (mts_toupper(ch));
449 }
450 
451 /*
452  * smb_generate_mangle
453  *
454  * Generates a mangle string which contains
455  * at least 2 (considering fileid cannot be 0)
456  * and at most 7 chars.
457  *
458  * Returns the number of chars in the generated mangle.
459  */
460 static int
461 smb_generate_mangle(ino64_t fileid, unsigned char *mangle_buf)
462 {
463 	/*
464 	 * 36**6 = 2176782336: more than enough to express inodes in 6
465 	 * chars
466 	 */
467 	static char *base36 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
468 	unsigned char *manglep = mangle_buf;
469 
470 	for (*manglep++ = '~'; fileid > 0; fileid /= 36)
471 		*manglep++ = base36[fileid % 36];
472 	*manglep = 0;
473 
474 	/*LINTED E_PTRDIFF_OVERFLOW*/
475 	return (manglep - mangle_buf);
476 }
477 
478 /*
479  * smb_maybe_mangled_name
480  *
481  * returns true if the passed name can possibly be a mangled name.
482  * mangled names should be valid dos file names hence less than 12 characters
483  * long and should contain at least one tilde character.
484  *
485  * note that this function can be further enhanced to check for invalid
486  * dos characters/character patterns (such as "file..1.c") but this version
487  * should be sufficient in most cases.
488  */
489 int
490 smb_maybe_mangled_name(char *name)
491 {
492 	int i, has_tilde = 0;
493 
494 	for (i = 0; *name && (i < 12); i++, name++) {
495 		if ((*name == '~') && (i < 8))
496 			has_tilde = 1;
497 
498 		if (*name == '.' && has_tilde == 0)
499 			return (0);
500 	}
501 
502 	return ((*name == 0) && has_tilde);
503 }
504 
505 /*
506  * smb_mangle_name
507  *
508  * Microsoft knowledge base article #142982 describes how Windows
509  * generates 8.3 filenames from long file names. Some other details
510  * can be found in article #114816.
511  *
512  * The function first checks to see whether the given name needs mangling.
513  * If not, and the force parameter is not set, then no mangling is done,
514  * but both the shortname (if needed) and the 8.3 name are produced and
515  * returned.
516  *
517  * If the "force" parameter is set (as will be the case for case-insensitive
518  * collisions), then the name will be mangled.
519  *
520  * Whenever mangling is needed, both the shortname and the 8.3 names are
521  * produced and returned.
522  *
523  * For example, the xxx.xy in 8.3 format will be "xxx     .xy ".
524  */
525 
526 int smb_mangle_name(
527 	ino64_t fileid,		/* inode number to generate unique mangle */
528 	char *name,		/* original file name */
529 	char *shortname,	/* mangled name (if applicable) */
530 	char *name83,		/* (mangled) name in 8.3 format */
531 	int force)		/* force mangling even if mangling is not */
532 				/* needed according to standard algorithm */
533 {
534 	int avail;
535 	unsigned char ch;
536 	unsigned char mangle_buf[8];
537 	unsigned char *namep;
538 	unsigned char *manglep;
539 	unsigned char *out_short;
540 	unsigned char *out_83;
541 	char *dot_pos = NULL;
542 
543 	/*
544 	 * NOTE:
545 	 * This function used to consider filename case
546 	 * in order to mangle. I removed those checks.
547 	 */
548 
549 	*shortname = *name83 = 0;
550 
551 	/* Allow dot and dot dot up front */
552 	if (strcmp(name, ".") == 0) {
553 		/* no shortname */
554 		(void) strcpy(name83, ".       .   ");
555 		return (1);
556 	}
557 
558 	if (strcmp(name, "..") == 0) {
559 		/* no shortname */
560 		(void) strcpy(name83, "..      .   ");
561 		return (1);
562 	}
563 
564 	out_short = (unsigned char *)shortname;
565 	out_83 = (unsigned char *)name83;
566 
567 	if ((smb_needs_mangle(name, &dot_pos) == 0) && (force == 0)) {
568 		/* no mangle */
569 
570 		/* check if shortname is required or not */
571 		if (smb_needs_shortname(name)) {
572 			namep = (unsigned char *)name;
573 			while (*namep)
574 				*out_short++ = mts_toupper(*namep++);
575 			*out_short = '\0';
576 		}
577 
578 		out_83 = (unsigned char *)name83;
579 		(void) strcpy((char *)out_83, "        .   ");
580 		while (*name && *name != '.')
581 			*out_83++ = mts_toupper(*name++);
582 
583 		if (*name == '.') {
584 			/* copy extension */
585 			name++;
586 			out_83 = (unsigned char *)name83 + 9;
587 			while (*name)
588 				*out_83++ = mts_toupper(*name++);
589 		}
590 		return (1);
591 	}
592 
593 	avail = 8 - smb_generate_mangle(fileid, mangle_buf);
594 
595 	/*
596 	 * generated mangle part has always less than 8 chars, so
597 	 * use the chars before the first dot in filename
598 	 * and try to generate a full 8 char name.
599 	 */
600 
601 	/* skip the leading dots (if any) */
602 	for (namep = (unsigned char *)name; *namep == '.'; namep++)
603 		;
604 
605 	for (; avail && *namep && (*namep != '.'); namep++) {
606 		ch = smb_mangle_char(*namep);
607 		if (ch == 0)
608 			continue;
609 		*out_short++ = *out_83++ = ch;
610 		avail--;
611 	}
612 
613 	/* Copy in mangled part */
614 	manglep = mangle_buf;
615 
616 	while (*manglep)
617 		*out_short++ = *out_83++ = *(manglep++);
618 
619 	/* Pad any leftover in 8.3 name with spaces */
620 	while (avail--)
621 		*out_83++ = ' ';
622 
623 	/* Work on extension now */
624 	avail = 3;
625 	*out_83++ = '.';
626 	if (dot_pos) {
627 		namep = (unsigned char *)dot_pos + 1;
628 		if (*namep != 0) {
629 			*out_short++ = '.';
630 			for (; avail && *namep; namep++) {
631 				ch = smb_mangle_char(*namep);
632 				if (ch == 0)
633 					continue;
634 
635 				*out_short++ = *out_83++ = ch;
636 				avail--;
637 			}
638 		}
639 	}
640 
641 	while (avail--)
642 		*out_83++ = ' ';
643 
644 	*out_short = *out_83 = '\0';
645 
646 	return (1);
647 }
648 
649 /*
650  * smb_unmangle_name
651  *
652  * Given a mangled name, try to find the real file name as it appears
653  * in the directory entry. If the name does not contain a ~, it is most
654  * likely not a mangled name but the caller can still try to get the
655  * actual on-disk name by setting the "od" parameter.
656  *
657  * Returns 0 if a name has been returned in real_name. There are three
658  * possible scenarios:
659  *  1. Name did not contain a ~ and "od" was not set, in which
660  *     case, real_name contains name.
661  *  2. Name did not contain a ~ and "od" was set, in which
662  *     case, real_name contains the actual directory entry name.
663  *  3. Name did contain a ~, in which case, name was mangled and
664  *     real_name contains the actual directory entry name.
665  *
666  * EINVAL: a parameter was invalid.
667  * ENOENT: an unmangled name could not be found.
668  */
669 
670 int
671 smb_unmangle_name(struct smb_request *sr, cred_t *cred, smb_node_t *dir_node,
672 	char *name, char *real_name, int realname_size, char *shortname,
673 	char *name83, int od)
674 {
675 	int err;
676 	int len;
677 	int force = 0;
678 	ino64_t inode;
679 	uint32_t cookie;
680 	struct smb_node *snode = NULL;
681 	smb_attr_t ret_attr;
682 	char *dot_pos = NULL;
683 	char *readdir_name;
684 	char *shortp;
685 	char namebuf[SMB_SHORTNAMELEN];
686 
687 	if (dir_node == NULL || name == NULL || real_name == NULL ||
688 	    realname_size == 0)
689 		return (EINVAL);
690 
691 	*real_name = '\0';
692 	snode = NULL;
693 
694 	if (smb_maybe_mangled_name(name) == 0) {
695 		if (od == 0) {
696 			(void) strlcpy(real_name, name, realname_size);
697 			return (0);
698 		}
699 
700 		err = smb_fsop_lookup(sr, cred, 0, sr->tid_tree->t_snode,
701 		    dir_node, name, &snode, &ret_attr, NULL, NULL);
702 
703 		if (err != 0)
704 			return (err);
705 
706 		(void) strlcpy(real_name, snode->od_name, realname_size);
707 		smb_node_release(snode);
708 		return (0);
709 	}
710 
711 	if (shortname == 0)
712 		shortname = namebuf;
713 	if (name83 == 0)
714 		name83 = namebuf;
715 
716 	cookie = 0;
717 
718 	readdir_name = kmem_alloc(MAXNAMELEN, KM_SLEEP);
719 
720 	snode = NULL;
721 	while (cookie != 0x7FFFFFFF) {
722 
723 		len = realname_size - 1;
724 
725 		err = smb_fsop_readdir(sr, cred, dir_node, &cookie,
726 		    readdir_name, &len, &inode, NULL, &snode, &ret_attr);
727 
728 		if (err || (cookie == 0x7FFFFFFF))
729 			break;
730 
731 		readdir_name[len] = 0;
732 
733 		/*
734 		 * smb_fsop_readdir() may return a mangled name if the
735 		 * name has a case collision.
736 		 *
737 		 * If readdir_name is not a mangled name, we mangle
738 		 * readdir_name to see if it will match the name the
739 		 * client passed in.
740 		 *
741 		 * If smb_needs_mangle() does not succeed, we try again
742 		 * using the force flag.  It is possible that the client
743 		 * is using a mangled name that resulted from a prior
744 		 * case collision which no longer exists in the directory.
745 		 * smb_needs_mangle(), with the force flag, will produce
746 		 * a mangled name regardless of whether the name passed in
747 		 * meets standard DOS criteria for name mangling.
748 		 */
749 
750 		if (smb_maybe_mangled_name(readdir_name)) {
751 			shortp = readdir_name;
752 		} else {
753 			if (smb_needs_mangle(readdir_name, &dot_pos) == 0)
754 				force = 1;
755 			(void) smb_mangle_name(inode, readdir_name, shortname,
756 			    name83, force);
757 			shortp = shortname;
758 		}
759 
760 		if (utf8_strcasecmp(name, shortp) == 0) {
761 			kmem_free(readdir_name, MAXNAMELEN);
762 			(void) strlcpy(real_name, snode->od_name,
763 			    realname_size);
764 
765 			smb_node_release(snode);
766 
767 			return (0);
768 		} else {
769 			smb_node_release(snode);
770 			snode = NULL;
771 		}
772 	}
773 
774 	kmem_free(readdir_name, MAXNAMELEN);
775 
776 	return (ENOENT);
777 }
778