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