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