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