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