1/*	$NetBSD: addrmatch.c,v 1.15 2021/04/19 14:40:15 christos Exp $	*/
2/*	$OpenBSD: addrmatch.c,v 1.17 2021/04/03 06:18:40 djm Exp $ */
3
4/*
5 * Copyright (c) 2004-2008 Damien Miller <djm@mindrot.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include "includes.h"
21__RCSID("$NetBSD: addrmatch.c,v 1.15 2021/04/19 14:40:15 christos Exp $");
22#include <sys/types.h>
23#include <sys/socket.h>
24#include <netinet/in.h>
25#include <arpa/inet.h>
26
27#include <netdb.h>
28#include <string.h>
29#include <stdlib.h>
30#include <stdio.h>
31#include <stdarg.h>
32
33#include "addr.h"
34#include "match.h"
35#include "log.h"
36
37/*
38 * Match "addr" against list pattern list "_list", which may contain a
39 * mix of CIDR addresses and old-school wildcards.
40 *
41 * If addr is NULL, then no matching is performed, but _list is parsed
42 * and checked for well-formedness.
43 *
44 * Returns 1 on match found (never returned when addr == NULL).
45 * Returns 0 on if no match found, or no errors found when addr == NULL.
46 * Returns -1 on negated match found (never returned when addr == NULL).
47 * Returns -2 on invalid list entry.
48 */
49int
50addr_match_list(const char *addr, const char *_list)
51{
52	char *list, *cp, *o;
53	struct xaddr try_addr, match_addr;
54	u_int masklen, neg;
55	int ret = 0, r;
56
57	if (addr != NULL && addr_pton(addr, &try_addr) != 0) {
58		debug2_f("couldn't parse address %.100s", addr);
59		return 0;
60	}
61	if ((o = list = strdup(_list)) == NULL)
62		return -1;
63	while ((cp = strsep(&list, ",")) != NULL) {
64		neg = *cp == '!';
65		if (neg)
66			cp++;
67		if (*cp == '\0') {
68			ret = -2;
69			break;
70		}
71		/* Prefer CIDR address matching */
72		r = addr_pton_cidr(cp, &match_addr, &masklen);
73		if (r == -2) {
74			debug2_f("inconsistent mask length for "
75			    "match network \"%.100s\"", cp);
76			ret = -2;
77			break;
78		} else if (r == 0) {
79			if (addr != NULL && addr_netmatch(&try_addr,
80			    &match_addr, masklen) == 0) {
81 foundit:
82				if (neg) {
83					ret = -1;
84					break;
85				}
86				ret = 1;
87			}
88			continue;
89		} else {
90			/* If CIDR parse failed, try wildcard string match */
91			if (addr != NULL && match_pattern(addr, cp) == 1)
92				goto foundit;
93		}
94	}
95	free(o);
96
97	return ret;
98}
99
100/*
101 * Match "addr" against list CIDR list "_list". Lexical wildcards and
102 * negation are not supported. If "addr" == NULL, will verify structure
103 * of "_list".
104 *
105 * Returns 1 on match found (never returned when addr == NULL).
106 * Returns 0 on if no match found, or no errors found when addr == NULL.
107 * Returns -1 on error
108 */
109int
110addr_match_cidr_list(const char *addr, const char *_list)
111{
112	char *list, *cp, *o;
113	struct xaddr try_addr, match_addr;
114	u_int masklen;
115	int ret = 0, r;
116
117	if (addr != NULL && addr_pton(addr, &try_addr) != 0) {
118		debug2_f("couldn't parse address %.100s", addr);
119		return 0;
120	}
121	if ((o = list = strdup(_list)) == NULL)
122		return -1;
123	while ((cp = strsep(&list, ",")) != NULL) {
124		if (*cp == '\0') {
125			error_f("empty entry in list \"%.100s\"", o);
126			ret = -1;
127			break;
128		}
129
130		/*
131		 * NB. This function is called in pre-auth with untrusted data,
132		 * so be extra paranoid about junk reaching getaddrino (via
133		 * addr_pton_cidr).
134		 */
135
136		/* Stop junk from reaching getaddrinfo. +3 is for masklen */
137		if (strlen(cp) > INET6_ADDRSTRLEN + 3) {
138			error_f("list entry \"%.100s\" too long", cp);
139			ret = -1;
140			break;
141		}
142#define VALID_CIDR_CHARS "0123456789abcdefABCDEF.:/"
143		if (strspn(cp, VALID_CIDR_CHARS) != strlen(cp)) {
144			error_f("list entry \"%.100s\" contains invalid "
145			    "characters", cp);
146			ret = -1;
147		}
148
149		/* Prefer CIDR address matching */
150		r = addr_pton_cidr(cp, &match_addr, &masklen);
151		if (r == -1) {
152			error("Invalid network entry \"%.100s\"", cp);
153			ret = -1;
154			break;
155		} else if (r == -2) {
156			error("Inconsistent mask length for "
157			    "network \"%.100s\"", cp);
158			ret = -1;
159			break;
160		} else if (r == 0 && addr != NULL) {
161			if (addr_netmatch(&try_addr, &match_addr,
162			    masklen) == 0)
163				ret = 1;
164			continue;
165		}
166	}
167	free(o);
168
169	return ret;
170}
171