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