/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2014 Nexenta Systems, Inc. All rights reserved.
*/
#if !defined(_KERNEL) && !defined(_FAKE_KERNEL)
#include <stdlib.h>
#include <string.h>
#else
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/sunddi.h>
#endif
#include <smbsrv/string.h>
#include <smbsrv/smb.h>
/*
* Maximum recursion depth for the wildcard match functions.
* These functions may recurse when processing a '*'.
*/
#define SMB_MATCH_DEPTH_MAX 32
struct match_priv {
int depth;
boolean_t ci;
};
static int smb_match_private(const char *, const char *, struct match_priv *);
static const char smb_wildcards[] = "*?<>\"";
/*
* Return B_TRUE if pattern contains wildcards
*/
boolean_t
smb_contains_wildcards(const char *pattern)
{
return (strpbrk(pattern, smb_wildcards) != NULL);
}
/*
* NT-compatible file name match function. [MS-FSA 3.1.4.4]
* Returns TRUE if there is a match.
*/
boolean_t
smb_match(const char *p, const char *s, boolean_t ci)
{
struct match_priv priv;
int rc;
/*
* Optimize common patterns that match everything:
* ("*", "<\"*") That second one is the converted
* form of "*.*" after smb_convert_wildcards() does
* its work on it for an old LM client. Note that a
* plain "*.*" never gets this far.
*/
if (p[0] == '*' && p[1] == '\0')
return (B_TRUE);
if (p[0] == '<' && p[1] == '\"' && p[2] == '*' && p[3] == '\0')
return (B_TRUE);
/*
* Match string ".." as if "." This is Windows behavior
* (not mentioned in MS-FSA) that was determined using
* the Samba masktest program.
*/
if (s[0] == '.' && s[1] == '.' && s[2] == '\0')
s++;
/*
* Optimize simple patterns (no wildcards)
*/
if (NULL == strpbrk(p, smb_wildcards)) {
if (ci)
rc = smb_strcasecmp(p, s, 0);
else
rc = strcmp(p, s);
return (rc == 0);
}
/*
* Do real wildcard match.
*/
priv.depth = 0;
priv.ci = ci;
rc = smb_match_private(p, s, &priv);
return (rc == 1);
}
/*
* Internal file name match function. [MS-FSA 3.1.4.4]
* This does the full expression evaluation.
*
* '*' matches zero of more of any characters.
* '?' matches exactly one of any character.
* '<' matches any string up through the last dot or EOS.
* '>' matches any one char not a dot, dot at EOS, or EOS.
* '"' matches a dot, or EOS.
*
* Returns:
* 1 match
* 0 no-match
* -1 no-match, error (illseq, too many wildcards in pattern, ...)
*
* Note that both the pattern and the string are in multi-byte form.
*
* The implementation of this is quite tricky. First note that it
* can call itself recursively, though it limits the recursion depth.
* Each switch case in the while loop can basically do one of three
* things: (a) return "Yes, match", (b) return "not a match", or
* continue processing the match pattern. The cases for wildcards
* that may match a variable number of characters ('*' and '<') do
* recursive calls, looking for a match of the remaining pattern,
* starting at the current and later positions in the string.
*/
static int
smb_match_private(const char *pat, const char *str, struct match_priv *priv)
{
const char *limit;
char pc; /* current pattern char */
int rc;
smb_wchar_t wcpat, wcstr; /* current wchar in pat, str */
int nbpat, nbstr; /* multi-byte length of it */
if (priv->depth >= SMB_MATCH_DEPTH_MAX)
return (-1);
/*
* Advance over one multi-byte char, used in cases like
* '?' or '>' where "match one character" needs to be
* interpreted as "match one multi-byte sequence".
*
* This macro needs to consume the semicolon following
* each place it appears, so this is carefully written
* as an if/else with a missing semicolon at the end.
*/
#define ADVANCE(str) \
if ((nbstr = smb_mbtowc(NULL, str, MTS_MB_CHAR_MAX)) < 1) \
return (-1); \
else \
str += nbstr /* no ; */
/*
* We move pat forward in each switch case so that the
* default case can move it by a whole multi-byte seq.
*/
while ((pc = *pat) != '\0') {
switch (pc) {
case '?': /* exactly one of any character */
pat++;
if (*str != '\0') {
ADVANCE(str);
continue;
}
/* EOS: no-match */
return (0);
case '*': /* zero or more of any characters */
pat++;
/* Optimize '*' at end of pattern. */
if (*pat == '\0')
return (1); /* match */
while (*str != '\0') {
priv->depth++;
rc = smb_match_private(pat, str, priv);
priv->depth--;
if (rc != 0)
return (rc); /* match */
ADVANCE(str);
}
continue;
case '<': /* any string up through the last dot or EOS */
pat++;
if ((limit = strrchr(str, '.')) != NULL)
limit++;
while (*str != '\0' && str != limit) {
priv->depth++;
rc = smb_match_private(pat, str, priv);
priv->depth--;
if (rc != 0)
return (rc); /* match */
ADVANCE(str);
}
continue;
case '>': /* anything not a dot, dot at EOS, or EOS */
pat++;
if (*str == '.') {
if (str[1] == '\0') {
/* dot at EOS */
str++; /* ADVANCE over '.' */
continue;
}
/* dot NOT at EOS: no-match */
return (0);
}
if (*str != '\0') {
/* something not a dot */
ADVANCE(str);
continue;
}
continue;
case '\"': /* dot, or EOS */
pat++;
if (*str == '.') {
str++; /* ADVANCE over '.' */
continue;
}
if (*str == '\0') {
continue;
}
/* something else: no-match */
return (0);
default: /* not a wildcard */
nbpat = smb_mbtowc(&wcpat, pat, MTS_MB_CHAR_MAX);
nbstr = smb_mbtowc(&wcstr, str, MTS_MB_CHAR_MAX);
/* make sure we advance */
if (nbpat < 1 || nbstr < 1)
return (-1);
if (wcpat == wcstr) {
pat += nbpat;
str += nbstr;
continue;
}
if (priv->ci) {
wcpat = smb_tolower(wcpat);
wcstr = smb_tolower(wcstr);
if (wcpat == wcstr) {
pat += nbpat;
str += nbstr;
continue;
}
}
return (0); /* no-match */
}
}
return (*str == '\0');
}