xref: /illumos-gate/usr/src/common/smbsrv/smb_match.c (revision 45405cce)
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 /*
23  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
25  */
26 
27 #ifndef _KERNEL
28 #include <stdlib.h>
29 #include <string.h>
30 #else
31 #include <sys/types.h>
32 #include <sys/sunddi.h>
33 #endif
34 #include <smbsrv/string.h>
35 #include <smbsrv/smb.h>
36 
37 /*
38  * Maximum recursion depth for the wildcard match functions.
39  * These functions may recurse when processing a '*'.
40  */
41 #define	SMB_MATCH_DEPTH_MAX	32
42 
43 struct match_priv {
44 	int depth;
45 	boolean_t ci;
46 };
47 
48 static int smb_match_private(const char *, const char *, struct match_priv *);
49 
50 static const char smb_wildcards[] = "*?<>\"";
51 
52 /*
53  * Return B_TRUE if pattern contains wildcards
54  */
55 boolean_t
56 smb_contains_wildcards(const char *pattern)
57 {
58 
59 	return (strpbrk(pattern, smb_wildcards) != NULL);
60 }
61 
62 /*
63  * NT-compatible file name match function.  [MS-FSA 3.1.4.4]
64  * Returns TRUE if there is a match.
65  */
66 boolean_t
67 smb_match(const char *p, const char *s, boolean_t ci)
68 {
69 	struct match_priv priv;
70 	int rc;
71 
72 	/*
73 	 * Optimize common patterns that match everything:
74 	 * ("*", "<\"*")  That second one is the converted
75 	 * form of "*.*" after smb_convert_wildcards() does
76 	 * its work on it for an old LM client. Note that a
77 	 * plain "*.*" never gets this far.
78 	 */
79 	if (p[0] == '*' && p[1] == '\0')
80 		return (B_TRUE);
81 	if (p[0] == '<' && p[1] == '\"' && p[2] == '*' && p[3] == '\0')
82 		return (B_TRUE);
83 
84 	/*
85 	 * Match string ".." as if "."  This is Windows behavior
86 	 * (not mentioned in MS-FSA) that was determined using
87 	 * the Samba masktest program.
88 	 */
89 	if (s[0] == '.' && s[1] == '.' && s[2] == '\0')
90 		s++;
91 
92 	/*
93 	 * Optimize simple patterns (no wildcards)
94 	 */
95 	if (NULL == strpbrk(p, smb_wildcards)) {
96 		if (ci)
97 			rc = smb_strcasecmp(p, s, 0);
98 		else
99 			rc = strcmp(p, s);
100 		return (rc == 0);
101 	}
102 
103 	/*
104 	 * Do real wildcard match.
105 	 */
106 	priv.depth = 0;
107 	priv.ci = ci;
108 	rc = smb_match_private(p, s, &priv);
109 	return (rc == 1);
110 }
111 
112 /*
113  * Internal file name match function.  [MS-FSA 3.1.4.4]
114  * This does the full expression evaluation.
115  *
116  * '*' matches zero of more of any characters.
117  * '?' matches exactly one of any character.
118  * '<' matches any string up through the last dot or EOS.
119  * '>' matches any one char not a dot, dot at EOS, or EOS.
120  * '"' matches a dot, or EOS.
121  *
122  * Returns:
123  *  1	match
124  *  0	no-match
125  * -1	no-match, error (illseq, too many wildcards in pattern, ...)
126  *
127  * Note that both the pattern and the string are in multi-byte form.
128  *
129  * The implementation of this is quite tricky.  First note that it
130  * can call itself recursively, though it limits the recursion depth.
131  * Each switch case in the while loop can basically do one of three
132  * things: (a) return "Yes, match", (b) return "not a match", or
133  * continue processing the match pattern.  The cases for wildcards
134  * that may match a variable number of characters ('*' and '<') do
135  * recursive calls, looking for a match of the remaining pattern,
136  * starting at the current and later positions in the string.
137  */
138 static int
139 smb_match_private(const char *pat, const char *str, struct match_priv *priv)
140 {
141 	const char	*limit;
142 	char		pc;		/* current pattern char */
143 	int		rc;
144 	smb_wchar_t	wcpat, wcstr;	/* current wchar in pat, str */
145 	int		nbpat, nbstr;	/* multi-byte length of it */
146 
147 	if (priv->depth >= SMB_MATCH_DEPTH_MAX)
148 		return (-1);
149 
150 	/*
151 	 * Advance over one multi-byte char, used in cases like
152 	 * '?' or '>' where "match one character" needs to be
153 	 * interpreted as "match one multi-byte sequence".
154 	 *
155 	 * This	macro needs to consume the semicolon following
156 	 * each place it appears, so this is carefully written
157 	 * as an if/else with a missing semicolon at the end.
158 	 */
159 #define	ADVANCE(str) \
160 	if ((nbstr = smb_mbtowc(NULL, str, MTS_MB_CHAR_MAX)) < 1) \
161 		return (-1); \
162 	else \
163 		str += nbstr	/* no ; */
164 
165 	/*
166 	 * We move pat forward in each switch case so that the
167 	 * default case can move it by a whole multi-byte seq.
168 	 */
169 	while ((pc = *pat) != '\0') {
170 		switch (pc) {
171 
172 		case '?':	/* exactly one of any character */
173 			pat++;
174 			if (*str != '\0') {
175 				ADVANCE(str);
176 				continue;
177 			}
178 			/* EOS: no-match */
179 			return (0);
180 
181 		case '*':	/* zero or more of any characters */
182 			pat++;
183 			/* Optimize '*' at end of pattern. */
184 			if (*pat == '\0')
185 				return (1); /* match */
186 			while (*str != '\0') {
187 				priv->depth++;
188 				rc = smb_match_private(pat, str, priv);
189 				priv->depth--;
190 				if (rc != 0)
191 					return (rc); /* match */
192 				ADVANCE(str);
193 			}
194 			continue;
195 
196 		case '<':	/* any string up through the last dot or EOS */
197 			pat++;
198 			if ((limit = strrchr(str, '.')) != NULL)
199 				limit++;
200 			while (*str != '\0' && str != limit) {
201 				priv->depth++;
202 				rc = smb_match_private(pat, str, priv);
203 				priv->depth--;
204 				if (rc != 0)
205 					return (rc); /* match */
206 				ADVANCE(str);
207 			}
208 			continue;
209 
210 		case '>':	/* anything not a dot, dot at EOS, or EOS */
211 			pat++;
212 			if (*str == '.') {
213 				if (str[1] == '\0') {
214 					/* dot at EOS */
215 					str++;	/* ADVANCE over '.' */
216 					continue;
217 				}
218 				/* dot NOT at EOS: no-match */
219 				return (0);
220 			}
221 			if (*str != '\0') {
222 				/* something not a dot */
223 				ADVANCE(str);
224 				continue;
225 			}
226 			continue;
227 
228 		case '\"':	/* dot, or EOS */
229 			pat++;
230 			if (*str == '.') {
231 				str++;	/* ADVANCE over '.' */
232 				continue;
233 			}
234 			if (*str == '\0') {
235 				continue;
236 			}
237 			/* something else: no-match */
238 			return (0);
239 
240 		default:	/* not a wildcard */
241 			nbpat = smb_mbtowc(&wcpat, pat, MTS_MB_CHAR_MAX);
242 			nbstr = smb_mbtowc(&wcstr, str, MTS_MB_CHAR_MAX);
243 			/* make sure we advance */
244 			if (nbpat < 1 || nbstr < 1)
245 				return (-1);
246 			if (wcpat == wcstr) {
247 				pat += nbpat;
248 				str += nbstr;
249 				continue;
250 			}
251 			if (priv->ci) {
252 				wcpat = smb_tolower(wcpat);
253 				wcstr = smb_tolower(wcstr);
254 				if (wcpat == wcstr) {
255 					pat += nbpat;
256 					str += nbstr;
257 					continue;
258 				}
259 			}
260 			return (0); /* no-match */
261 		}
262 	}
263 	return (*str == '\0');
264 }
265