1*9469f4f1Schristos /* $NetBSD: match.c,v 1.17 2024/09/24 21:32:18 christos Exp $ */ 2*9469f4f1Schristos /* $OpenBSD: match.c,v 1.45 2024/09/06 02:30:44 djm Exp $ */ 32d3b0f52Schristos 4ca32bd8dSchristos /* 5ca32bd8dSchristos * Author: Tatu Ylonen <ylo@cs.hut.fi> 6ca32bd8dSchristos * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 7ca32bd8dSchristos * All rights reserved 8ca32bd8dSchristos * Simple pattern matching, with '*' and '?' as wildcards. 9ca32bd8dSchristos * 10ca32bd8dSchristos * As far as I am concerned, the code I have written for this software 11ca32bd8dSchristos * can be used freely for any purpose. Any derived versions of this 12ca32bd8dSchristos * software must be clearly marked as such, and if the derived work is 13ca32bd8dSchristos * incompatible with the protocol description in the RFC file, it must be 14ca32bd8dSchristos * called by a name other than "ssh" or "Secure Shell". 15ca32bd8dSchristos */ 16ca32bd8dSchristos /* 17ca32bd8dSchristos * Copyright (c) 2000 Markus Friedl. All rights reserved. 18ca32bd8dSchristos * 19ca32bd8dSchristos * Redistribution and use in source and binary forms, with or without 20ca32bd8dSchristos * modification, are permitted provided that the following conditions 21ca32bd8dSchristos * are met: 22ca32bd8dSchristos * 1. Redistributions of source code must retain the above copyright 23ca32bd8dSchristos * notice, this list of conditions and the following disclaimer. 24ca32bd8dSchristos * 2. Redistributions in binary form must reproduce the above copyright 25ca32bd8dSchristos * notice, this list of conditions and the following disclaimer in the 26ca32bd8dSchristos * documentation and/or other materials provided with the distribution. 27ca32bd8dSchristos * 28ca32bd8dSchristos * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 29ca32bd8dSchristos * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 30ca32bd8dSchristos * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 31ca32bd8dSchristos * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 32ca32bd8dSchristos * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 33ca32bd8dSchristos * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 34ca32bd8dSchristos * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 35ca32bd8dSchristos * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 36ca32bd8dSchristos * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 37ca32bd8dSchristos * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 38ca32bd8dSchristos */ 39ca32bd8dSchristos 40313c6c94Schristos #include "includes.h" 41*9469f4f1Schristos __RCSID("$NetBSD: match.c,v 1.17 2024/09/24 21:32:18 christos Exp $"); 42ca32bd8dSchristos #include <sys/types.h> 43ca32bd8dSchristos 44ca32bd8dSchristos #include <ctype.h> 4500a838c4Schristos #include <stdlib.h> 46ca32bd8dSchristos #include <string.h> 47ed75d7a8Schristos #include <stdarg.h> 4841768fc1Schristos #include <stdio.h> 49ca32bd8dSchristos 50ca32bd8dSchristos #include "xmalloc.h" 51ca32bd8dSchristos #include "match.h" 5241768fc1Schristos #include "misc.h" 53ca32bd8dSchristos 54ca32bd8dSchristos /* 55ca32bd8dSchristos * Returns true if the given string matches the pattern (which may contain ? 56ca32bd8dSchristos * and * as wildcards), and zero if it does not match. 57ca32bd8dSchristos */ 58ca32bd8dSchristos int 59ca32bd8dSchristos match_pattern(const char *s, const char *pattern) 60ca32bd8dSchristos { 61ca32bd8dSchristos for (;;) { 62ca32bd8dSchristos /* If at end of pattern, accept if also at end of string. */ 63ca32bd8dSchristos if (!*pattern) 64ca32bd8dSchristos return !*s; 65ca32bd8dSchristos 66ca32bd8dSchristos if (*pattern == '*') { 6717418e98Schristos /* Skip this and any consecutive asterisks. */ 6817418e98Schristos while (*pattern == '*') 69ca32bd8dSchristos pattern++; 70ca32bd8dSchristos 71ca32bd8dSchristos /* If at end of pattern, accept immediately. */ 72ca32bd8dSchristos if (!*pattern) 73ca32bd8dSchristos return 1; 74ca32bd8dSchristos 75ca32bd8dSchristos /* If next character in pattern is known, optimize. */ 76ca32bd8dSchristos if (*pattern != '?' && *pattern != '*') { 77ca32bd8dSchristos /* 78ca32bd8dSchristos * Look instances of the next character in 79ca32bd8dSchristos * pattern, and try to match starting from 80ca32bd8dSchristos * those. 81ca32bd8dSchristos */ 82ca32bd8dSchristos for (; *s; s++) 83ca32bd8dSchristos if (*s == *pattern && 84ca32bd8dSchristos match_pattern(s + 1, pattern + 1)) 85ca32bd8dSchristos return 1; 86ca32bd8dSchristos /* Failed. */ 87ca32bd8dSchristos return 0; 88ca32bd8dSchristos } 89ca32bd8dSchristos /* 90ca32bd8dSchristos * Move ahead one character at a time and try to 91ca32bd8dSchristos * match at each position. 92ca32bd8dSchristos */ 93ca32bd8dSchristos for (; *s; s++) 94ca32bd8dSchristos if (match_pattern(s, pattern)) 95ca32bd8dSchristos return 1; 96ca32bd8dSchristos /* Failed. */ 97ca32bd8dSchristos return 0; 98ca32bd8dSchristos } 99ca32bd8dSchristos /* 100ca32bd8dSchristos * There must be at least one more character in the string. 101ca32bd8dSchristos * If we are at the end, fail. 102ca32bd8dSchristos */ 103ca32bd8dSchristos if (!*s) 104ca32bd8dSchristos return 0; 105ca32bd8dSchristos 106ca32bd8dSchristos /* Check if the next character of the string is acceptable. */ 107ca32bd8dSchristos if (*pattern != '?' && *pattern != *s) 108ca32bd8dSchristos return 0; 109ca32bd8dSchristos 110ca32bd8dSchristos /* Move to the next character, both in string and in pattern. */ 111ca32bd8dSchristos s++; 112ca32bd8dSchristos pattern++; 113ca32bd8dSchristos } 114ca32bd8dSchristos /* NOTREACHED */ 115ca32bd8dSchristos } 116ca32bd8dSchristos 117ca32bd8dSchristos /* 118ca32bd8dSchristos * Tries to match the string against the 119ca32bd8dSchristos * comma-separated sequence of subpatterns (each possibly preceded by ! to 120ca32bd8dSchristos * indicate negation). Returns -1 if negation matches, 1 if there is 121ca32bd8dSchristos * a positive match, 0 if there is no match at all. 122ca32bd8dSchristos */ 123ca32bd8dSchristos int 1244054ffb0Schristos match_pattern_list(const char *string, const char *pattern, int dolower) 125ca32bd8dSchristos { 126ca32bd8dSchristos char sub[1024]; 127ca32bd8dSchristos int negated; 128ca32bd8dSchristos int got_positive; 1294054ffb0Schristos u_int i, subi, len = strlen(pattern); 130ca32bd8dSchristos 131ca32bd8dSchristos got_positive = 0; 132ca32bd8dSchristos for (i = 0; i < len;) { 133ca32bd8dSchristos /* Check if the subpattern is negated. */ 134ca32bd8dSchristos if (pattern[i] == '!') { 135ca32bd8dSchristos negated = 1; 136ca32bd8dSchristos i++; 137ca32bd8dSchristos } else 138ca32bd8dSchristos negated = 0; 139ca32bd8dSchristos 140ca32bd8dSchristos /* 141ca32bd8dSchristos * Extract the subpattern up to a comma or end. Convert the 142ca32bd8dSchristos * subpattern to lowercase. 143ca32bd8dSchristos */ 144ca32bd8dSchristos for (subi = 0; 145ca32bd8dSchristos i < len && subi < sizeof(sub) - 1 && pattern[i] != ','; 146ca32bd8dSchristos subi++, i++) 1478a4530f9Schristos sub[subi] = dolower && isupper((u_char)pattern[i]) ? 1488a4530f9Schristos tolower((u_char)pattern[i]) : pattern[i]; 149ca32bd8dSchristos /* If subpattern too long, return failure (no match). */ 150ca32bd8dSchristos if (subi >= sizeof(sub) - 1) 151ca32bd8dSchristos return 0; 152ca32bd8dSchristos 15341768fc1Schristos /* If the subpattern was terminated by a comma, then skip it. */ 154ca32bd8dSchristos if (i < len && pattern[i] == ',') 155ca32bd8dSchristos i++; 156ca32bd8dSchristos 157ca32bd8dSchristos /* Null-terminate the subpattern. */ 158ca32bd8dSchristos sub[subi] = '\0'; 159ca32bd8dSchristos 160ca32bd8dSchristos /* Try to match the subpattern against the string. */ 161ca32bd8dSchristos if (match_pattern(string, sub)) { 162ca32bd8dSchristos if (negated) 163ca32bd8dSchristos return -1; /* Negative */ 164ca32bd8dSchristos else 165ca32bd8dSchristos got_positive = 1; /* Positive */ 166ca32bd8dSchristos } 167ca32bd8dSchristos } 168ca32bd8dSchristos 169ca32bd8dSchristos /* 170ca32bd8dSchristos * Return success if got a positive match. If there was a negative 171ca32bd8dSchristos * match, we have already returned -1 and never get here. 172ca32bd8dSchristos */ 173ca32bd8dSchristos return got_positive; 174ca32bd8dSchristos } 175ca32bd8dSchristos 176aa36fcacSchristos /* Match a list representing users or groups. */ 177aa36fcacSchristos int 178aa36fcacSchristos match_usergroup_pattern_list(const char *string, const char *pattern) 179aa36fcacSchristos { 180aa36fcacSchristos /* Case sensitive match */ 181aa36fcacSchristos return match_pattern_list(string, pattern, 0); 182aa36fcacSchristos } 183aa36fcacSchristos 184ca32bd8dSchristos /* 185ca32bd8dSchristos * Tries to match the host name (which must be in all lowercase) against the 186ca32bd8dSchristos * comma-separated sequence of subpatterns (each possibly preceded by ! to 187ca32bd8dSchristos * indicate negation). Returns -1 if negation matches, 1 if there is 188ca32bd8dSchristos * a positive match, 0 if there is no match at all. 189ca32bd8dSchristos */ 190ca32bd8dSchristos int 1914054ffb0Schristos match_hostname(const char *host, const char *pattern) 192ca32bd8dSchristos { 19341768fc1Schristos char *hostcopy = xstrdup(host); 19441768fc1Schristos int r; 19541768fc1Schristos 19641768fc1Schristos lowercase(hostcopy); 19741768fc1Schristos r = match_pattern_list(hostcopy, pattern, 1); 19841768fc1Schristos free(hostcopy); 19941768fc1Schristos return r; 200ca32bd8dSchristos } 201ca32bd8dSchristos 202ca32bd8dSchristos /* 203ca32bd8dSchristos * returns 0 if we get a negative match for the hostname or the ip 204ca32bd8dSchristos * or if we get no match at all. returns -1 on error, or 1 on 205ca32bd8dSchristos * successful match. 206ca32bd8dSchristos */ 207ca32bd8dSchristos int 208ca32bd8dSchristos match_host_and_ip(const char *host, const char *ipaddr, 209ca32bd8dSchristos const char *patterns) 210ca32bd8dSchristos { 211ca32bd8dSchristos int mhost, mip; 212ca32bd8dSchristos 213ca32bd8dSchristos if ((mip = addr_match_list(ipaddr, patterns)) == -2) 214ee85abc4Schristos return -1; /* error in ipaddr match */ 215ee85abc4Schristos else if (host == NULL || ipaddr == NULL || mip == -1) 216ee85abc4Schristos return 0; /* negative ip address match, or testing pattern */ 217ca32bd8dSchristos 218ca32bd8dSchristos /* negative hostname match */ 2194054ffb0Schristos if ((mhost = match_hostname(host, patterns)) == -1) 220ca32bd8dSchristos return 0; 221ca32bd8dSchristos /* no match at all */ 222ca32bd8dSchristos if (mhost == 0 && mip == 0) 223ca32bd8dSchristos return 0; 224ca32bd8dSchristos return 1; 225ca32bd8dSchristos } 226ca32bd8dSchristos 227ca32bd8dSchristos /* 228ee85abc4Schristos * Match user, user@host_or_ip, user@host_or_ip_list against pattern. 229ee85abc4Schristos * If user, host and ipaddr are all NULL then validate pattern/ 230ee85abc4Schristos * Returns -1 on invalid pattern, 0 on no match, 1 on match. 231ca32bd8dSchristos */ 232ca32bd8dSchristos int 233ca32bd8dSchristos match_user(const char *user, const char *host, const char *ipaddr, 234ca32bd8dSchristos const char *pattern) 235ca32bd8dSchristos { 236ca32bd8dSchristos char *p, *pat; 237ca32bd8dSchristos int ret; 238ca32bd8dSchristos 239ee85abc4Schristos /* test mode */ 240ee85abc4Schristos if (user == NULL && host == NULL && ipaddr == NULL) { 241*9469f4f1Schristos if ((p = strrchr(pattern, '@')) != NULL && 242ee85abc4Schristos match_host_and_ip(NULL, NULL, p + 1) < 0) 243ee85abc4Schristos return -1; 244ee85abc4Schristos return 0; 245ee85abc4Schristos } 246ee85abc4Schristos 247a629fefcSchristos if (user == NULL) 248a629fefcSchristos return 0; /* shouldn't happen */ 249a629fefcSchristos 250*9469f4f1Schristos if (strrchr(pattern, '@') == NULL) 251ca32bd8dSchristos return match_pattern(user, pattern); 252ca32bd8dSchristos 253ca32bd8dSchristos pat = xstrdup(pattern); 254*9469f4f1Schristos p = strrchr(pat, '@'); 255ca32bd8dSchristos *p++ = '\0'; 256ca32bd8dSchristos 257ca32bd8dSchristos if ((ret = match_pattern(user, pat)) == 1) 258ca32bd8dSchristos ret = match_host_and_ip(host, ipaddr, p); 25900a838c4Schristos free(pat); 260ca32bd8dSchristos 261ca32bd8dSchristos return ret; 262ca32bd8dSchristos } 263ca32bd8dSchristos 264ca32bd8dSchristos /* 265ca32bd8dSchristos * Returns first item from client-list that is also supported by server-list, 26600a838c4Schristos * caller must free the returned string. 267ca32bd8dSchristos */ 268ca32bd8dSchristos #define MAX_PROP 40 269ca32bd8dSchristos #define SEP "," 270ca32bd8dSchristos char * 271ca32bd8dSchristos match_list(const char *client, const char *server, u_int *next) 272ca32bd8dSchristos { 273ca32bd8dSchristos char *sproposals[MAX_PROP]; 274ca32bd8dSchristos char *c, *s, *p, *ret, *cp, *sp; 275ca32bd8dSchristos int i, j, nproposals; 276ca32bd8dSchristos 277ca32bd8dSchristos c = cp = xstrdup(client); 278ca32bd8dSchristos s = sp = xstrdup(server); 279ca32bd8dSchristos 280ca32bd8dSchristos for ((p = strsep(&sp, SEP)), i=0; p && *p != '\0'; 281ca32bd8dSchristos (p = strsep(&sp, SEP)), i++) { 282ca32bd8dSchristos if (i < MAX_PROP) 283ca32bd8dSchristos sproposals[i] = p; 284ca32bd8dSchristos else 285ca32bd8dSchristos break; 286ca32bd8dSchristos } 287ca32bd8dSchristos nproposals = i; 288ca32bd8dSchristos 289ca32bd8dSchristos for ((p = strsep(&cp, SEP)), i=0; p && *p != '\0'; 290ca32bd8dSchristos (p = strsep(&cp, SEP)), i++) { 291ca32bd8dSchristos for (j = 0; j < nproposals; j++) { 292ca32bd8dSchristos if (strcmp(p, sproposals[j]) == 0) { 293ca32bd8dSchristos ret = xstrdup(p); 294ca32bd8dSchristos if (next != NULL) 295ca32bd8dSchristos *next = (cp == NULL) ? 296ca32bd8dSchristos strlen(c) : (u_int)(cp - c); 29700a838c4Schristos free(c); 29800a838c4Schristos free(s); 299ca32bd8dSchristos return ret; 300ca32bd8dSchristos } 301ca32bd8dSchristos } 302ca32bd8dSchristos } 303ca32bd8dSchristos if (next != NULL) 304ca32bd8dSchristos *next = strlen(c); 30500a838c4Schristos free(c); 30600a838c4Schristos free(s); 307ca32bd8dSchristos return NULL; 308ca32bd8dSchristos } 30941768fc1Schristos 31041768fc1Schristos /* 31155a4608bSchristos * Filter proposal using pattern-list filter. 3122d3b0f52Schristos * "denylist" determines sense of filter: 31355a4608bSchristos * non-zero indicates that items matching filter should be excluded. 31455a4608bSchristos * zero indicates that only items matching filter should be included. 31555a4608bSchristos * returns NULL on allocation error, otherwise caller must free result. 31641768fc1Schristos */ 31755a4608bSchristos static char * 3182d3b0f52Schristos filter_list(const char *proposal, const char *filter, int denylist) 31941768fc1Schristos { 32041768fc1Schristos size_t len = strlen(proposal) + 1; 32141768fc1Schristos char *fix_prop = malloc(len); 32241768fc1Schristos char *orig_prop = strdup(proposal); 32341768fc1Schristos char *cp, *tmp; 32455a4608bSchristos int r; 32541768fc1Schristos 32641768fc1Schristos if (fix_prop == NULL || orig_prop == NULL) { 32741768fc1Schristos free(orig_prop); 32841768fc1Schristos free(fix_prop); 32941768fc1Schristos return NULL; 33041768fc1Schristos } 33141768fc1Schristos 33241768fc1Schristos tmp = orig_prop; 33341768fc1Schristos *fix_prop = '\0'; 33441768fc1Schristos while ((cp = strsep(&tmp, ",")) != NULL) { 33555a4608bSchristos r = match_pattern_list(cp, filter, 0); 3362d3b0f52Schristos if ((denylist && r != 1) || (!denylist && r == 1)) { 33741768fc1Schristos if (*fix_prop != '\0') 33841768fc1Schristos strlcat(fix_prop, ",", len); 33941768fc1Schristos strlcat(fix_prop, cp, len); 34041768fc1Schristos } 34141768fc1Schristos } 34241768fc1Schristos free(orig_prop); 34341768fc1Schristos return fix_prop; 34441768fc1Schristos } 34541768fc1Schristos 34655a4608bSchristos /* 34755a4608bSchristos * Filters a comma-separated list of strings, excluding any entry matching 34855a4608bSchristos * the 'filter' pattern list. Caller must free returned string. 34955a4608bSchristos */ 35055a4608bSchristos char * 3512d3b0f52Schristos match_filter_denylist(const char *proposal, const char *filter) 35255a4608bSchristos { 35355a4608bSchristos return filter_list(proposal, filter, 1); 35455a4608bSchristos } 35555a4608bSchristos 35655a4608bSchristos /* 35755a4608bSchristos * Filters a comma-separated list of strings, including only entries matching 35855a4608bSchristos * the 'filter' pattern list. Caller must free returned string. 35955a4608bSchristos */ 36055a4608bSchristos char * 3612d3b0f52Schristos match_filter_allowlist(const char *proposal, const char *filter) 36255a4608bSchristos { 36355a4608bSchristos return filter_list(proposal, filter, 0); 36455a4608bSchristos } 365