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
43struct match_priv {
44	int depth;
45	boolean_t ci;
46};
47
48static int smb_match_private(const char *, const char *, struct match_priv *);
49
50static const char smb_wildcards[] = "*?<>\"";
51
52/*
53 * Return B_TRUE if pattern contains wildcards
54 */
55boolean_t
56smb_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 */
66boolean_t
67smb_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 */
138static int
139smb_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