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