summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog14
-rw-r--r--Makefile.in4
-rw-r--r--addrmatch.c420
-rw-r--r--match.h5
-rw-r--r--servconf.c18
-rw-r--r--sshd_config.526
6 files changed, 473 insertions, 14 deletions
diff --git a/ChangeLog b/ChangeLog
index d60967404..65f97df7f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
120080610
2 - (dtucker) OpenBSD CVS Sync
3 - djm@cvs.openbsd.org 2008/06/10 03:57:27
4 [servconf.c match.h sshd_config.5]
5 support CIDR address matching in sshd_config "Match address" blocks, with
6 full support for negation and fall-back to classic wildcard matching.
7 For example:
8 Match address 192.0.2.0/24,3ffe:ffff::/32,!10.*
9 PasswordAuthentication yes
10 addrmatch.c code mostly lifted from flowd's addr.c
11 feedback and ok dtucker@
12
120080609 1320080609
2 - (dtucker) OpenBSD CVS Sync 14 - (dtucker) OpenBSD CVS Sync
3 - dtucker@cvs.openbsd.org 2008/06/08 17:04:41 15 - dtucker@cvs.openbsd.org 2008/06/08 17:04:41
@@ -4083,4 +4095,4 @@
4083 OpenServer 6 and add osr5bigcrypt support so when someone migrates 4095 OpenServer 6 and add osr5bigcrypt support so when someone migrates
4084 passwords between UnixWare and OpenServer they will still work. OK dtucker@ 4096 passwords between UnixWare and OpenServer they will still work. OK dtucker@
4085 4097
4086$Id: ChangeLog,v 1.4948 2008/06/09 13:52:22 dtucker Exp $ 4098$Id: ChangeLog,v 1.4949 2008/06/10 12:59:10 dtucker Exp $
diff --git a/Makefile.in b/Makefile.in
index b57b9b72d..5debe2533 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -1,4 +1,4 @@
1# $Id: Makefile.in,v 1.290 2008/05/19 06:00:08 djm Exp $ 1# $Id: Makefile.in,v 1.291 2008/06/10 12:59:10 dtucker Exp $
2 2
3# uncomment if you run a non bourne compatable shell. Ie. csh 3# uncomment if you run a non bourne compatable shell. Ie. csh
4#SHELL = @SH@ 4#SHELL = @SH@
@@ -83,7 +83,7 @@ SSHDOBJS=sshd.o auth-rhosts.o auth-passwd.o auth-rsa.o auth-rh-rsa.o \
83 auth-skey.o auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \ 83 auth-skey.o auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
84 auth2-none.o auth2-passwd.o auth2-pubkey.o \ 84 auth2-none.o auth2-passwd.o auth2-pubkey.o \
85 monitor_mm.o monitor.o monitor_wrap.o kexdhs.o kexgexs.o \ 85 monitor_mm.o monitor.o monitor_wrap.o kexdhs.o kexgexs.o \
86 auth-krb5.o \ 86 auth-krb5.o addrmatch.o \
87 auth2-gss.o gss-serv.o gss-serv-krb5.o \ 87 auth2-gss.o gss-serv.o gss-serv-krb5.o \
88 loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \ 88 loginrec.o auth-pam.o auth-shadow.o auth-sia.o md5crypt.o \
89 audit.o audit-bsm.o platform.o sftp-server.o sftp-common.o 89 audit.o audit-bsm.o platform.o sftp-server.o sftp-common.o
diff --git a/addrmatch.c b/addrmatch.c
new file mode 100644
index 000000000..a0559efa0
--- /dev/null
+++ b/addrmatch.c
@@ -0,0 +1,420 @@
1/* $OpenBSD: addrmatch.c,v 1.2 2008/06/10 05:22:45 djm Exp $ */
2
3/*
4 * Copyright (c) 2004-2008 Damien Miller <djm@mindrot.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include "includes.h"
20
21#include <sys/types.h>
22#include <sys/socket.h>
23#include <netinet/in.h>
24#include <arpa/inet.h>
25
26#include <netdb.h>
27#include <string.h>
28#include <stdlib.h>
29#include <stdio.h>
30#include <stdarg.h>
31
32#include "match.h"
33#include "log.h"
34
35struct xaddr {
36 sa_family_t af;
37 union {
38 struct in_addr v4;
39 struct in6_addr v6;
40 u_int8_t addr8[16];
41 u_int32_t addr32[4];
42 } xa; /* 128-bit address */
43 u_int32_t scope_id; /* iface scope id for v6 */
44#define v4 xa.v4
45#define v6 xa.v6
46#define addr8 xa.addr8
47#define addr32 xa.addr32
48};
49
50static int
51addr_unicast_masklen(int af)
52{
53 switch (af) {
54 case AF_INET:
55 return 32;
56 case AF_INET6:
57 return 128;
58 default:
59 return -1;
60 }
61}
62
63static inline int
64masklen_valid(int af, u_int masklen)
65{
66 switch (af) {
67 case AF_INET:
68 return masklen <= 32 ? 0 : -1;
69 case AF_INET6:
70 return masklen <= 128 ? 0 : -1;
71 default:
72 return -1;
73 }
74}
75
76/*
77 * Convert struct sockaddr to struct xaddr
78 * Returns 0 on success, -1 on failure.
79 */
80static int
81addr_sa_to_xaddr(struct sockaddr *sa, socklen_t slen, struct xaddr *xa)
82{
83 struct sockaddr_in *in4 = (struct sockaddr_in *)sa;
84 struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)sa;
85
86 memset(xa, '\0', sizeof(*xa));
87
88 switch (sa->sa_family) {
89 case AF_INET:
90 if (slen < sizeof(*in4))
91 return -1;
92 xa->af = AF_INET;
93 memcpy(&xa->v4, &in4->sin_addr, sizeof(xa->v4));
94 break;
95 case AF_INET6:
96 if (slen < sizeof(*in6))
97 return -1;
98 xa->af = AF_INET6;
99 memcpy(&xa->v6, &in6->sin6_addr, sizeof(xa->v6));
100 xa->scope_id = in6->sin6_scope_id;
101 break;
102 default:
103 return -1;
104 }
105
106 return 0;
107}
108
109/*
110 * Calculate a netmask of length 'l' for address family 'af' and
111 * store it in 'n'.
112 * Returns 0 on success, -1 on failure.
113 */
114static int
115addr_netmask(int af, u_int l, struct xaddr *n)
116{
117 int i;
118
119 if (masklen_valid(af, l) != 0 || n == NULL)
120 return -1;
121
122 memset(n, '\0', sizeof(*n));
123 switch (af) {
124 case AF_INET:
125 n->af = AF_INET;
126 n->v4.s_addr = htonl((0xffffffff << (32 - l)) & 0xffffffff);
127 return 0;
128 case AF_INET6:
129 n->af = AF_INET6;
130 for (i = 0; i < 4 && l >= 32; i++, l -= 32)
131 n->addr32[i] = 0xffffffffU;
132 if (i < 4 && l != 0)
133 n->addr32[i] = htonl((0xffffffff << (32 - l)) &
134 0xffffffff);
135 return 0;
136 default:
137 return -1;
138 }
139}
140
141/*
142 * Perform logical AND of addresses 'a' and 'b', storing result in 'dst'.
143 * Returns 0 on success, -1 on failure.
144 */
145static int
146addr_and(struct xaddr *dst, const struct xaddr *a, const struct xaddr *b)
147{
148 int i;
149
150 if (dst == NULL || a == NULL || b == NULL || a->af != b->af)
151 return -1;
152
153 memcpy(dst, a, sizeof(*dst));
154 switch (a->af) {
155 case AF_INET:
156 dst->v4.s_addr &= b->v4.s_addr;
157 return 0;
158 case AF_INET6:
159 dst->scope_id = a->scope_id;
160 for (i = 0; i < 4; i++)
161 dst->addr32[i] &= b->addr32[i];
162 return 0;
163 default:
164 return -1;
165 }
166}
167
168/*
169 * Compare addresses 'a' and 'b'
170 * Return 0 if addresses are identical, -1 if (a < b) or 1 if (a > b)
171 */
172static int
173addr_cmp(const struct xaddr *a, const struct xaddr *b)
174{
175 int i;
176
177 if (a->af != b->af)
178 return a->af == AF_INET6 ? 1 : -1;
179
180 switch (a->af) {
181 case AF_INET:
182 if (a->v4.s_addr == b->v4.s_addr)
183 return 0;
184 return ntohl(a->v4.s_addr) > ntohl(b->v4.s_addr) ? 1 : -1;
185 case AF_INET6:
186 for (i = 0; i < 16; i++)
187 if (a->addr8[i] - b->addr8[i] != 0)
188 return a->addr8[i] > b->addr8[i] ? 1 : -1;
189 if (a->scope_id == b->scope_id)
190 return 0;
191 return a->scope_id > b->scope_id ? 1 : -1;
192 default:
193 return -1;
194 }
195}
196
197/*
198 * Parse string address 'p' into 'n'
199 * Returns 0 on success, -1 on failure.
200 */
201static int
202addr_pton(const char *p, struct xaddr *n)
203{
204 struct addrinfo hints, *ai;
205
206 memset(&hints, '\0', sizeof(hints));
207 hints.ai_flags = AI_NUMERICHOST;
208
209 if (p == NULL || getaddrinfo(p, NULL, &hints, &ai) != 0)
210 return -1;
211
212 if (ai == NULL || ai->ai_addr == NULL)
213 return -1;
214
215 if (n != NULL &&
216 addr_sa_to_xaddr(ai->ai_addr, ai->ai_addrlen, n) == -1) {
217 freeaddrinfo(ai);
218 return -1;
219 }
220
221 freeaddrinfo(ai);
222 return 0;
223}
224
225/*
226 * Perform bitwise negation of address
227 * Returns 0 on success, -1 on failure.
228 */
229static int
230addr_invert(struct xaddr *n)
231{
232 int i;
233
234 if (n == NULL)
235 return (-1);
236
237 switch (n->af) {
238 case AF_INET:
239 n->v4.s_addr = ~n->v4.s_addr;
240 return (0);
241 case AF_INET6:
242 for (i = 0; i < 4; i++)
243 n->addr32[i] = ~n->addr32[i];
244 return (0);
245 default:
246 return (-1);
247 }
248}
249
250/*
251 * Calculate a netmask of length 'l' for address family 'af' and
252 * store it in 'n'.
253 * Returns 0 on success, -1 on failure.
254 */
255static int
256addr_hostmask(int af, u_int l, struct xaddr *n)
257{
258 if (addr_netmask(af, l, n) == -1 || addr_invert(n) == -1)
259 return (-1);
260 return (0);
261}
262
263/*
264 * Test whether address 'a' is all zeros (i.e. 0.0.0.0 or ::)
265 * Returns 0 on if address is all-zeros, -1 if not all zeros or on failure.
266 */
267static int
268addr_is_all0s(const struct xaddr *a)
269{
270 int i;
271
272 switch (a->af) {
273 case AF_INET:
274 return (a->v4.s_addr == 0 ? 0 : -1);
275 case AF_INET6:;
276 for (i = 0; i < 4; i++)
277 if (a->addr32[i] != 0)
278 return (-1);
279 return (0);
280 default:
281 return (-1);
282 }
283}
284
285/*
286 * Test whether host portion of address 'a', as determined by 'masklen'
287 * is all zeros.
288 * Returns 0 on if host portion of address is all-zeros,
289 * -1 if not all zeros or on failure.
290 */
291static int
292addr_host_is_all0s(const struct xaddr *a, u_int masklen)
293{
294 struct xaddr tmp_addr, tmp_mask, tmp_result;
295
296 memcpy(&tmp_addr, a, sizeof(tmp_addr));
297 if (addr_hostmask(a->af, masklen, &tmp_mask) == -1)
298 return (-1);
299 if (addr_and(&tmp_result, &tmp_addr, &tmp_mask) == -1)
300 return (-1);
301 return (addr_is_all0s(&tmp_result));
302}
303
304/*
305 * Parse a CIDR address (x.x.x.x/y or xxxx:yyyy::/z).
306 * Return -1 on parse error, -2 on inconsistency or 0 on success.
307 */
308static int
309addr_pton_cidr(const char *p, struct xaddr *n, u_int *l)
310{
311 struct xaddr tmp;
312 long unsigned int masklen = 999;
313 char addrbuf[64], *mp, *cp;
314
315 /* Don't modify argument */
316 if (p == NULL || strlcpy(addrbuf, p, sizeof(addrbuf)) > sizeof(addrbuf))
317 return -1;
318
319 if ((mp = strchr(addrbuf, '/')) != NULL) {
320 *mp = '\0';
321 mp++;
322 masklen = strtoul(mp, &cp, 10);
323 if (*mp == '\0' || *cp != '\0' || masklen > 128)
324 return -1;
325 }
326
327 if (addr_pton(addrbuf, &tmp) == -1)
328 return -1;
329
330 if (mp == NULL)
331 masklen = addr_unicast_masklen(tmp.af);
332 if (masklen_valid(tmp.af, masklen) == -1)
333 return -2;
334 if (addr_host_is_all0s(&tmp, masklen) != 0)
335 return -2;
336
337 if (n != NULL)
338 memcpy(n, &tmp, sizeof(*n));
339 if (l != NULL)
340 *l = masklen;
341
342 return 0;
343}
344
345static int
346addr_netmatch(const struct xaddr *host, const struct xaddr *net, u_int masklen)
347{
348 struct xaddr tmp_mask, tmp_result;
349
350 if (host->af != net->af)
351 return -1;
352
353 if (addr_netmask(host->af, masklen, &tmp_mask) == -1)
354 return -1;
355 if (addr_and(&tmp_result, host, &tmp_mask) == -1)
356 return -1;
357 return addr_cmp(&tmp_result, net);
358}
359
360/*
361 * Match "addr" against list pattern list "_list", which may contain a
362 * mix of CIDR addresses and old-school wildcards.
363 *
364 * If addr is NULL, then no matching is performed, but _list is parsed
365 * and checked for well-formedness.
366 *
367 * Returns 1 on match found (never returned when addr == NULL).
368 * Returns 0 on if no match found, or no errors found when addr == NULL.
369 * Returns -1 on invalid list entry.
370 */
371int
372addr_match_list(const char *addr, const char *_list)
373{
374 char *list, *cp, *o;
375 struct xaddr try_addr, match_addr;
376 u_int masklen, neg;
377 int ret = 0, r;
378
379 if (addr != NULL && addr_pton(addr, &try_addr) != 0) {
380 debug2("%s: couldn't parse address %.100s", __func__, addr);
381 return 0;
382 }
383 if ((o = list = strdup(_list)) == NULL)
384 return -1;
385 while ((cp = strsep(&list, ",")) != NULL) {
386 neg = *cp == '!';
387 if (neg)
388 cp++;
389 if (*cp == '\0') {
390 ret = -1;
391 break;
392 }
393 /* Prefer CIDR address matching */
394 r = addr_pton_cidr(cp, &match_addr, &masklen);
395 if (r == -2) {
396 error("Inconsistent mask length for "
397 "network \"%.100s\"", cp);
398 ret = -1;
399 break;
400 } else if (r == 0) {
401 if (addr != NULL && addr_netmatch(&try_addr,
402 &match_addr, masklen) == 0) {
403 foundit:
404 if (neg) {
405 ret = 0;
406 break;
407 }
408 ret = 1;
409 }
410 continue;
411 } else {
412 /* If CIDR parse failed, try wildcard string match */
413 if (addr != NULL && match_pattern(addr, cp) == 1)
414 goto foundit;
415 }
416 }
417 free(o);
418
419 return ret;
420}
diff --git a/match.h b/match.h
index d1d538654..18f683070 100644
--- a/match.h
+++ b/match.h
@@ -1,4 +1,4 @@
1/* $OpenBSD: match.h,v 1.13 2006/03/25 22:22:43 djm Exp $ */ 1/* $OpenBSD: match.h,v 1.14 2008/06/10 03:57:27 djm Exp $ */
2 2
3/* 3/*
4 * Author: Tatu Ylonen <ylo@cs.hut.fi> 4 * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -21,4 +21,7 @@ int match_host_and_ip(const char *, const char *, const char *);
21int match_user(const char *, const char *, const char *, const char *); 21int match_user(const char *, const char *, const char *, const char *);
22char *match_list(const char *, const char *, u_int *); 22char *match_list(const char *, const char *, u_int *);
23 23
24/* addrmatch.c */
25int addr_match_list(const char *, const char *);
26
24#endif 27#endif
diff --git a/servconf.c b/servconf.c
index 94dff1fd6..07a201034 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: servconf.c,v 1.180 2008/05/08 12:21:16 djm Exp $ */ 1/* $OpenBSD: servconf.c,v 1.181 2008/06/10 03:57:27 djm Exp $ */
2/* 2/*
3 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 3 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
4 * All rights reserved 4 * All rights reserved
@@ -603,15 +603,17 @@ match_cfg_line(char **condition, int line, const char *user, const char *host,
603 debug("connection from %.100s matched 'Host " 603 debug("connection from %.100s matched 'Host "
604 "%.100s' at line %d", host, arg, line); 604 "%.100s' at line %d", host, arg, line);
605 } else if (strcasecmp(attrib, "address") == 0) { 605 } else if (strcasecmp(attrib, "address") == 0) {
606 if (!address) { 606 switch (addr_match_list(address, arg)) {
607 result = 0; 607 case 1:
608 continue;
609 }
610 if (match_hostname(address, arg, len) != 1)
611 result = 0;
612 else
613 debug("connection from %.100s matched 'Address " 608 debug("connection from %.100s matched 'Address "
614 "%.100s' at line %d", address, arg, line); 609 "%.100s' at line %d", address, arg, line);
610 break;
611 case 0:
612 result = 0;
613 break;
614 case -1:
615 return -1;
616 }
615 } else { 617 } else {
616 error("Unsupported Match attribute %s", attrib); 618 error("Unsupported Match attribute %s", attrib);
617 return -1; 619 return -1;
diff --git a/sshd_config.5 b/sshd_config.5
index 0d8c140bf..dc42959e2 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -34,8 +34,8 @@
34.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 34.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36.\" 36.\"
37.\" $OpenBSD: sshd_config.5,v 1.90 2008/05/08 12:21:16 djm Exp $ 37.\" $OpenBSD: sshd_config.5,v 1.91 2008/06/10 03:57:27 djm Exp $
38.Dd $Mdocdate: May 8 2008 $ 38.Dd $Mdocdate: June 10 2008 $
39.Dt SSHD_CONFIG 5 39.Dt SSHD_CONFIG 5
40.Os 40.Os
41.Sh NAME 41.Sh NAME
@@ -557,6 +557,7 @@ line are satisfied, the keywords on the following lines override those
557set in the global section of the config file, until either another 557set in the global section of the config file, until either another
558.Cm Match 558.Cm Match
559line or the end of the file. 559line or the end of the file.
560.Pp
560The arguments to 561The arguments to
561.Cm Match 562.Cm Match
562are one or more criteria-pattern pairs. 563are one or more criteria-pattern pairs.
@@ -566,6 +567,27 @@ The available criteria are
566.Cm Host , 567.Cm Host ,
567and 568and
568.Cm Address . 569.Cm Address .
570The match patterns may consist of single entries or comma-separated
571lists and may use the wildcard and negation operators described in the
572.Sx SSH_KNOWN_HOSTS FILE FORMAT
573section of
574.Xr sshd 8 .
575.Pp
576The patterns in an
577.Cm Address
578criteria may additionally contain addresses to match in CIDR
579address/masklen format, e.g.
580.Dq 192.0.2.0/24
581or
582.Dq 3ffe:ffff::/32 .
583Note that the mask length provided must be consistent with the address -
584it is an error to specify a mask length that is too long for the address
585or one with bits set in this host portion of the address. For example,
586.Dq 192.0.2.0/33
587and
588.Dq 192.0.2.0/8
589respectively.
590.Pp
569Only a subset of keywords may be used on the lines following a 591Only a subset of keywords may be used on the lines following a
570.Cm Match 592.Cm Match
571keyword. 593keyword.