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 smb_wchar_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