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