1 /*
2 Copyright 2020 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 /* FIXME: handle 127.0.0.2, 127.255.255.254, ::1,
544 * 0000:0000:0000:0000:0000:0000:0000:0001, 0:00:000:0000:000:00:0:1, 0::1 and
545 * other variants
546 */
547
IsLoopbackAddress(const char * address)548 bool IsLoopbackAddress(const char *address)
549 {
550 if(strcmp(address, "localhost") == 0)
551 {
552 return true;
553 }
554
555 if(strcmp(address, "127.0.0.1") == 0)
556 {
557 return true;
558 }
559
560 return false;
561 }
562
563 /**
564 * Simple check to avoid writing to illegal memory addresses.
565 * NOT a proper test for valid IP.
566 */
AddressTypeCheckValidity(char * s,AddressType address_type)567 static AddressType AddressTypeCheckValidity(char *s, AddressType address_type)
568 {
569 if(NULL_OR_EMPTY(s))
570 {
571 return ADDRESS_TYPE_OTHER;
572 }
573 if(strlen(s) >= CF_MAX_IP_LEN)
574 {
575 return ADDRESS_TYPE_OTHER;
576 }
577 return address_type;
578 }
579
580 /**
581 * Parses "hostname:port" or "[hostname]:port", where hostname may also be
582 * IPv4 or IPv6 address string.
583 *
584 * @param hostname will point to the hostname, or NULL if no or empty hostname
585 * @param port will point to the port, or NULL if no or empty port
586 * @WARNING modifies #s to '\0' terminate hostname if followed by port.
587 */
ParseHostPort(char * s,char ** hostname,char ** port)588 AddressType ParseHostPort(char *s, char **hostname, char **port)
589 {
590 s = TrimWhitespace(s);
591 if ( NULL_OR_EMPTY(s) )
592 {
593 *hostname = NULL;
594 *port = NULL;
595 return ADDRESS_TYPE_OTHER;
596 }
597
598 AddressType address_type = ADDRESS_TYPE_OTHER;
599 char *h, *p; // hostname, port temporaries
600
601 h = s;
602 p = NULL;
603
604 char *first_colon = strchr(s, ':');
605 char *first_dot = strchr(s, '.');
606
607 if (s[0] == '[') // [host or ip]:port
608 {
609 h = s + 1;
610 p = strchr(h, ']');
611 if (p != NULL)
612 {
613 if (first_colon != NULL && first_colon < p)
614 {
615 address_type = ADDRESS_TYPE_IPV6;
616 }
617 else if (isdigit(h[0]))
618 {
619 address_type = ADDRESS_TYPE_IPV4;
620 } // (else it's other by default)
621
622 *p = '\0'; // '\0' terminate host name
623 if (p[1] == ':') // move port* forward
624 {
625 p += 2;
626 }
627 }
628 }
629 else if (first_colon == NULL) // localhost, 192.168.0.1
630 {
631 if (isdigit(h[0]))
632 {
633 address_type = ADDRESS_TYPE_IPV4;
634 }
635 }
636 else if (first_dot == NULL || first_colon < first_dot)
637 {
638 // If only one colon: (cfengine.com:222 or localhost:)
639 if (strchr(first_colon + 1, ':') == NULL)
640 {
641 *first_colon = '\0';
642 p = first_colon + 1;
643 }
644 else // Multiple colons:
645 {
646 address_type = ADDRESS_TYPE_IPV6;
647 }
648 }
649 else // (first_dot < first_colon) : IPv4 or hostname
650 {
651 p = strchr(h, ':');
652 if (p != NULL)
653 {
654 *p = '\0'; // '\0'-terminate hostname
655 p++;
656 }
657 if (isdigit(h[0]))
658 {
659 address_type = ADDRESS_TYPE_IPV4;
660 }
661 }
662
663 *hostname = (h[0] != '\0') ? h : NULL;
664 *port = (p != NULL && p[0] != '\0') ? p : NULL;
665
666 return AddressTypeCheckValidity(*hostname, address_type);
667 }
668