1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <addr_lib.h>
26 #include <cfnet.h>
27
28 #include <logging.h>
29 #include <string_lib.h>
30
31 #define CF_ADDRSIZE 128
32
33
34 /* Match two IP strings - with : or . in hex or decimal
35 s1 is the test string, and s2 is the reference e.g.
36 FuzzySetMatch("128.39.74.10/23","128.39.75.56") == 0
37
38 Returns 0 on match. */
39
40 /* TODO rename to AddrSubnetMatch() */
FuzzySetMatch(const char * s1,const char * s2)41 int FuzzySetMatch(const char *s1, const char *s2)
42 {
43 short isCIDR = false, isrange = false, isv6 = false, isv4 = false;
44 char address[CF_ADDRSIZE];
45
46 if (strcmp(s1, s2) == 0)
47 {
48 return 0;
49 }
50
51 if (strstr(s1, "/") != 0)
52 {
53 isCIDR = true;
54 }
55
56 if (strstr(s1, "-") != 0)
57 {
58 isrange = true;
59 }
60
61 if (strstr(s1, ".") != 0)
62 {
63 isv4 = true;
64 }
65
66 if (strstr(s1, ":") != 0)
67 {
68 isv6 = true;
69 }
70
71 if (strstr(s2, ".") != 0)
72 {
73 isv4 = true;
74 }
75
76 if (strstr(s2, ":") != 0)
77 {
78 isv6 = true;
79 }
80
81 if (isv4 && isv6)
82 {
83 /* This is just wrong */
84 return -1;
85 }
86
87 if (isCIDR && isrange)
88 {
89 Log(LOG_LEVEL_ERR, "Cannot mix CIDR notation with xxx-yyy range notation '%s'", s1);
90 return -1;
91 }
92
93 if (!(isv6 || isv4))
94 {
95 Log(LOG_LEVEL_ERR, "Not a valid address range - or not a fully qualified name '%s'", s1);
96 return -1;
97 }
98
99 if (!(isrange || isCIDR))
100 {
101 if (strlen(s2) > strlen(s1))
102 {
103 if (*(s2 + strlen(s1)) != '.')
104 {
105 return -1; // Because xxx.1 should not match xxx.12 in the same octet
106 }
107 }
108
109 return strncmp(s1, s2, strlen(s1)); /* do partial string match */
110 }
111
112 if (isv4)
113 {
114 if (isCIDR)
115 {
116 struct sockaddr_in addr1, addr2;
117 unsigned long mask;
118
119 address[0] = '\0';
120 int ret = sscanf(s1, "%16[^/]/%lu", address, &mask);
121 if (ret != 2 || mask > 32)
122 {
123 Log(LOG_LEVEL_ERR, "Invalid IPv4 CIDR: %s", s1);
124 return -1;
125 }
126 else if (mask == 0)
127 {
128 return 0; /* /0 CIDR matches everything */
129 }
130
131 inet_pton(AF_INET, address, &addr1.sin_addr);
132 inet_pton(AF_INET, s2, &addr2.sin_addr);
133
134 unsigned long a1 = htonl(addr1.sin_addr.s_addr);
135 unsigned long a2 = htonl(addr2.sin_addr.s_addr);
136
137 unsigned long shift = 32 - mask;
138 assert(shift < 32); /* Undefined behaviour if 32 */
139
140 a1 = a1 >> shift;
141 a2 = a2 >> shift;
142
143 if (a1 == a2)
144 {
145 return 0;
146 }
147 else
148 {
149 return -1;
150 }
151 }
152 else
153 {
154 long i, from = -1, to = -1, cmp = -1;
155 char buffer1[64], buffer2[64];
156
157 const char *sp1 = s1;
158 const char *sp2 = s2;
159
160 for (i = 0; i < 4; i++)
161 {
162 buffer1[0] = '\0';
163 sscanf(sp1, "%63[^.]", buffer1);
164 buffer1[63] = '\0';
165
166 if (strlen(buffer1) == 0)
167 {
168 break;
169 }
170
171 sp1 += strlen(buffer1) + 1;
172
173 sscanf(sp2, "%63[^.]", buffer2);
174 buffer2[63] = '\0';
175
176 sp2 += strlen(buffer2) + 1;
177
178 if (strstr(buffer1, "-"))
179 {
180 sscanf(buffer1, "%ld-%ld", &from, &to);
181 sscanf(buffer2, "%ld", &cmp);
182
183 if ((from < 0) || (to < 0))
184 {
185 Log(LOG_LEVEL_DEBUG, "Couldn't read range");
186 return -1;
187 }
188
189 if ((from > cmp) || (cmp > to))
190 {
191 Log(LOG_LEVEL_DEBUG, "Out of range %ld > %ld > %ld, range '%s'", from, cmp, to, buffer2);
192 return -1;
193 }
194 }
195 else
196 {
197 sscanf(buffer1, "%ld", &from);
198 sscanf(buffer2, "%ld", &cmp);
199
200 if (from != cmp)
201 {
202 Log(LOG_LEVEL_DEBUG, "Unequal");
203 return -1;
204 }
205 }
206
207 Log(LOG_LEVEL_DEBUG, "Matched octet '%s' with '%s'", buffer1, buffer2);
208 }
209
210 Log(LOG_LEVEL_DEBUG, "Matched IP range");
211 return 0;
212 }
213 }
214
215 if (isv6)
216 {
217 int i;
218
219 if (isCIDR)
220 {
221 int blocks;
222 struct sockaddr_in6 addr1 = {0};
223 struct sockaddr_in6 addr2 = {0};
224 unsigned long mask;
225
226 address[0] = '\0';
227 int ret = sscanf(s1, "%40[^/]/%lu", address, &mask);
228 if (ret != 2 || mask > 128)
229 {
230 Log(LOG_LEVEL_ERR, "Invalid IPv6 CIDR: %s", s1);
231 return -1;
232 }
233 blocks = mask / 8;
234
235 if (mask % 8 != 0)
236 {
237 Log(LOG_LEVEL_ERR, "Cannot handle ipv6 masks which are not 8 bit multiples (fix me)");
238 return -1;
239 }
240
241 addr1.sin6_family = AF_INET6;
242 inet_pton(AF_INET6, address, &addr1.sin6_addr);
243 addr2.sin6_family = AF_INET6;
244 inet_pton(AF_INET6, s2, &addr2.sin6_addr);
245
246 for (i = 0; i < blocks; i++) /* blocks < 16 */
247 {
248 if (addr1.sin6_addr.s6_addr[i] != addr2.sin6_addr.s6_addr[i])
249 {
250 return -1;
251 }
252 }
253 return 0;
254 }
255 else
256 {
257 long i, from = -1, to = -1, cmp = -1;
258 char buffer1[64], buffer2[64];
259
260 const char *sp1 = s1;
261 const char *sp2 = s2;
262
263 for (i = 0; i < 8; i++)
264 {
265 sscanf(sp1, "%63[^:]", buffer1);
266 buffer1[63] = '\0';
267
268 sp1 += strlen(buffer1) + 1;
269
270 sscanf(sp2, "%63[^:]", buffer2);
271 buffer2[63] = '\0';
272
273 sp2 += strlen(buffer2) + 1;
274
275 if (strstr(buffer1, "-"))
276 {
277 sscanf(buffer1, "%lx-%lx", &from, &to);
278 sscanf(buffer2, "%lx", &cmp);
279
280 if (from < 0 || to < 0)
281 {
282 return -1;
283 }
284
285 if ((from >= cmp) || (cmp > to))
286 {
287 Log(LOG_LEVEL_DEBUG, "%lx < %lx < %lx", from, cmp, to);
288 return -1;
289 }
290 }
291 else
292 {
293 sscanf(buffer1, "%ld", &from);
294 sscanf(buffer2, "%ld", &cmp);
295
296 if (from != cmp)
297 {
298 return -1;
299 }
300 }
301 }
302
303 return 0;
304 }
305 }
306
307 return -1;
308 }
309
FuzzyHostParse(const char * arg2)310 bool FuzzyHostParse(const char *arg2)
311 {
312 long start = -1, end = -1;
313 int n;
314
315 n = sscanf(arg2, "%ld-%ld", &start, &end);
316
317 if (n != 2)
318 {
319 Log(LOG_LEVEL_ERR,
320 "HostRange syntax error: second arg should have X-Y format where X and Y are decimal numbers");
321 return false;
322 }
323
324 return true;
325 }
326
FuzzyHostMatch(const char * arg0,const char * arg1,const char * refhost)327 int FuzzyHostMatch(const char *arg0, const char *arg1, const char *refhost)
328 {
329 char *sp, refbase[1024];
330 long cmp = -1, start = -1, end = -1;
331 char buf1[CF_BUFSIZE], buf2[CF_BUFSIZE];
332
333 strlcpy(refbase, refhost, sizeof(refbase));
334 sp = refbase + strlen(refbase) - 1;
335
336 while (isdigit((int) *sp))
337 {
338 sp--;
339 }
340
341 sp++;
342 sscanf(sp, "%ld", &cmp);
343 *sp = '\0';
344
345 if (cmp < 0)
346 {
347 return 1;
348 }
349
350 if (strlen(refbase) == 0)
351 {
352 return 1;
353 }
354
355 sscanf(arg1, "%ld-%ld", &start, &end);
356
357 if ((cmp < start) || (cmp > end))
358 {
359 return 1;
360 }
361
362 strlcpy(buf1, refbase, CF_BUFSIZE);
363 strlcpy(buf2, arg0, CF_BUFSIZE);
364
365 ToLowerStrInplace(buf1);
366 ToLowerStrInplace(buf2);
367
368 if (strcmp(buf1, buf2) != 0)
369 {
370 return 1;
371 }
372
373 return 0;
374 }
375
FuzzyMatchParse(const char * s)376 bool FuzzyMatchParse(const char *s)
377 {
378 short isCIDR = false, isrange = false, isv6 = false, isv4 = false, isADDR = false;
379 char address[CF_ADDRSIZE];
380 int mask, count = 0;
381
382 for (const char *sp = s; *sp != '\0'; sp++) /* Is this an address or hostname */
383 {
384 if (!isxdigit((int) *sp))
385 {
386 isADDR = false;
387 break;
388 }
389
390 if (*sp == ':') /* Catches any ipv6 address */
391 {
392 isADDR = true;
393 break;
394 }
395
396 if (isdigit((int) *sp)) /* catch non-ipv4 address - no more than 3 digits */
397 {
398 count++;
399 if (count > 3)
400 {
401 isADDR = false;
402 break;
403 }
404 }
405 else
406 {
407 count = 0;
408 }
409 }
410
411 if (!isADDR)
412 {
413 return true;
414 }
415
416 if (strstr(s, "/") != 0)
417 {
418 isCIDR = true;
419 }
420
421 if (strstr(s, "-") != 0)
422 {
423 isrange = true;
424 }
425
426 if (strstr(s, ".") != 0)
427 {
428 isv4 = true;
429 }
430
431 if (strstr(s, ":") != 0)
432 {
433 isv6 = true;
434 }
435
436 if (isv4 && isv6)
437 {
438 Log(LOG_LEVEL_ERR, "Mixture of IPv6 and IPv4 addresses");
439 return false;
440 }
441
442 if (isCIDR && isrange)
443 {
444 Log(LOG_LEVEL_ERR, "Cannot mix CIDR notation with xx-yy range notation");
445 return false;
446 }
447
448 if (isv4 && isCIDR)
449 {
450 if (strlen(s) > 4 + 3 * 4 + 1 + 2) /* xxx.yyy.zzz.mmm/cc */
451 {
452 Log(LOG_LEVEL_ERR, "IPv4 address looks too long");
453 return false;
454 }
455
456 address[0] = '\0';
457 mask = 0;
458 sscanf(s, "%16[^/]/%d", address, &mask);
459
460 if (mask < 8)
461 {
462 Log(LOG_LEVEL_ERR, "Mask value %d in '%s' is less than 8", mask, s);
463 return false;
464 }
465
466 if (mask > 30)
467 {
468 Log(LOG_LEVEL_ERR, "Mask value %d in '%s' is silly (> 30)", mask, s);
469 return false;
470 }
471 }
472
473 if (isv4 && isrange)
474 {
475 long i, from = -1, to = -1;
476 char buffer1[64];
477
478 const char *sp1 = s;
479
480 for (i = 0; i < 4; i++)
481 {
482 buffer1[0] = '\0';
483 sscanf(sp1, "%63[^.]", buffer1);
484 sp1 += strlen(buffer1) + 1;
485
486 if (strstr(buffer1, "-"))
487 {
488 sscanf(buffer1, "%ld-%ld", &from, &to);
489
490 if ((from < 0) || (to < 0))
491 {
492 Log(LOG_LEVEL_ERR, "Error in IP range - looks like address, or bad hostname");
493 return false;
494 }
495
496 if (to < from)
497 {
498 Log(LOG_LEVEL_ERR, "Bad IP range");
499 return false;
500 }
501
502 }
503 }
504 }
505
506 if (isv6 && isCIDR)
507 {
508 char address[CF_ADDRSIZE];
509 int mask;
510
511 if (strlen(s) < 20)
512 {
513 Log(LOG_LEVEL_ERR, "IPv6 address looks too short");
514 return false;
515 }
516
517 if (strlen(s) > 42)
518 {
519 Log(LOG_LEVEL_ERR, "IPv6 address looks too long");
520 return false;
521 }
522
523 address[0] = '\0';
524 mask = 0;
525 sscanf(s, "%40[^/]/%d", address, &mask);
526
527 if (mask % 8 != 0)
528 {
529 Log(LOG_LEVEL_ERR, "Cannot handle ipv6 masks which are not 8 bit multiples (fix me)");
530 return false;
531 }
532
533 if (mask > 15)
534 {
535 Log(LOG_LEVEL_ERR, "IPv6 CIDR mask is too large");
536 return false;
537 }
538 }
539
540 return true;
541 }
542
543
544 /**
545 * Simple check to avoid writing to illegal memory addresses.
546 * NOT a proper test for valid IP.
547 */
AddressTypeCheckValidity(char * s,AddressType address_type)548 static AddressType AddressTypeCheckValidity(char *s, AddressType address_type)
549 {
550 if(NULL_OR_EMPTY(s))
551 {
552 return ADDRESS_TYPE_OTHER;
553 }
554 if(strlen(s) >= CF_MAX_IP_LEN)
555 {
556 return ADDRESS_TYPE_OTHER;
557 }
558 return address_type;
559 }
560
561 /**
562 * Parses "hostname:port" or "[hostname]:port", where hostname may also be
563 * IPv4 or IPv6 address string.
564 *
565 * @param hostname will point to the hostname, or NULL if no or empty hostname
566 * @param port will point to the port, or NULL if no or empty port
567 * @WARNING modifies #s to '\0' terminate hostname if followed by port.
568 */
ParseHostPort(char * s,char ** hostname,char ** port)569 AddressType ParseHostPort(char *s, char **hostname, char **port)
570 {
571 s = TrimWhitespace(s);
572 if ( NULL_OR_EMPTY(s) )
573 {
574 *hostname = NULL;
575 *port = NULL;
576 return ADDRESS_TYPE_OTHER;
577 }
578
579 AddressType address_type = ADDRESS_TYPE_OTHER;
580 char *h, *p; // hostname, port temporaries
581
582 h = s;
583 p = NULL;
584
585 char *first_colon = strchr(s, ':');
586 char *first_dot = strchr(s, '.');
587
588 if (s[0] == '[') // [host or ip]:port
589 {
590 h = s + 1;
591 p = strchr(h, ']');
592 if (p != NULL)
593 {
594 if (first_colon != NULL && first_colon < p)
595 {
596 address_type = ADDRESS_TYPE_IPV6;
597 }
598 else if (isdigit(h[0]))
599 {
600 address_type = ADDRESS_TYPE_IPV4;
601 } // (else it's other by default)
602
603 *p = '\0'; // '\0' terminate host name
604 if (p[1] == ':') // move port* forward
605 {
606 p += 2;
607 }
608 }
609 }
610 else if (first_colon == NULL) // localhost, 192.168.0.1
611 {
612 if (isdigit(h[0]))
613 {
614 address_type = ADDRESS_TYPE_IPV4;
615 }
616 }
617 else if (first_dot == NULL || first_colon < first_dot)
618 {
619 // If only one colon: (cfengine.com:222 or localhost:)
620 if (strchr(first_colon + 1, ':') == NULL)
621 {
622 *first_colon = '\0';
623 p = first_colon + 1;
624 }
625 else // Multiple colons:
626 {
627 address_type = ADDRESS_TYPE_IPV6;
628 }
629 }
630 else // (first_dot < first_colon) : IPv4 or hostname
631 {
632 p = strchr(h, ':');
633 if (p != NULL)
634 {
635 *p = '\0'; // '\0'-terminate hostname
636 p++;
637 }
638 if (isdigit(h[0]))
639 {
640 address_type = ADDRESS_TYPE_IPV4;
641 }
642 }
643
644 *hostname = (h[0] != '\0') ? h : NULL;
645 *port = (p != NULL && p[0] != '\0') ? p : NULL;
646
647 return AddressTypeCheckValidity(*hostname, address_type);
648 }
649