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 <item_lib.h>
26 
27 #include <files_names.h>
28 #include <addr_lib.h>
29 #include <matching.h>
30 #include <misc_lib.h>
31 #include <regex.h> /* StringMatchFull,CompileRegex,StringMatchWithPrecompiledRegex */
32 #include <file_lib.h>
33 #include <files_interfaces.h>
34 
35 #ifdef CYCLE_DETECTION
36 /* While looping over entry lp in an Item list, advance slow half as
37  * fast as lp; let n be the number of steps it has fallen behind; this
38  * increases by one every second time round the loop.  If there's a
39  * cycle of length M, lp shall run round and round it; once slow gets
40  * into the loop, they shall be n % M steps apart; at most 2*M more
41  * times round the loop and n % M shall be 0 so lp == slow.  If the
42  * lead-in to the loop is of length L, this takes at most 2*(L+M)
43  * turns round the loop to discover the cycle.  The added cost is O(1)
44  * per time round the loop, so the typical O(list length) user doesn't
45  * change order when this is enabled, albeit the constant of
46  * proportionality is up.
47  *
48  * Note, however, that none of this works if you're messing with the
49  * structure (e.g. reversing or deleting) of the list as you go.
50  *
51  * To use the macros: before the loop, declare and initialize your
52  * loop variable; pass it as lp to CYCLE_DECLARE(), followed by two
53  * names not in use in your code.  Then, in the body of the loop,
54  * after advancing the loop variable, CYCLE_CHECK() the same three
55  * parameters.  This is apt to require a while loop where you might
56  * otherwise have used a for loop; you also need to make sure your
57  * loop doesn't continue past the checking.  When you compile with
58  * CYCLE_DETECTION defined, your function shall catch cycles, raising
59  * a ProgrammingError() if it sees one.
60  */
61 #define CYCLE_DECLARE(lp, slow, toggle) \
62     const Item *slow = lp; bool toggle = false
63 #define CYCLE_VERIFY(lp, slow) if (!lp) { /* skip */ }              \
64     else if (!slow) ProgrammingError("Loop-detector bug :-(");      \
65     else if (lp == slow) ProgrammingError("Found loop in Item list")
66 #define CYCLE_CHECK(lp, slow, toggle) \
67     CYCLE_VERIFY(lp, slow);                                     \
68     if (toggle) { slow = slow->next; CYCLE_VERIFY(lp, slow); }  \
69     toggle = !toggle
70 #else
71 #define CYCLE_DECLARE(lp, slow, toggle) /* skip */
72 #define CYCLE_CHECK(lp, slow, toggle) /* skip */
73 #endif
74 
75 #ifndef NDEBUG
76 /* Only intended for use in assertions.  Note that its cost is O(list
77  * length), so you don't want to call it inside a loop over the
78  * list. */
ItemIsInList(const Item * list,const Item * item)79 static bool ItemIsInList(const Item *list, const Item *item)
80 {
81     CYCLE_DECLARE(list, slow, toggle);
82     while (list)
83     {
84         if (list == item)
85         {
86             return true;
87         }
88         list = list->next;
89         CYCLE_CHECK(list, slow, toggle);
90     }
91     return false;
92 }
93 #endif /* NDEBUG */
94 
95 /*******************************************************************/
96 
ReverseItemList(Item * list)97 Item *ReverseItemList(Item *list)
98 {
99     /* TODO: cycle-detection, which is somewhat harder here, without
100      * turning this into a quadratic-cost function, albeit only when
101      * assert() is enabled.
102      */
103     Item *tail = NULL;
104     while (list)
105     {
106         Item *here = list;
107         list = here->next;
108         /* assert(!ItemIsInList(here, list)); // quadratic cost */
109         here->next = tail;
110         tail = here;
111     }
112     return tail;
113 }
114 
115 /*******************************************************************/
116 
PrintItemList(const Item * list,Writer * w)117 void PrintItemList(const Item *list, Writer *w)
118 {
119     WriterWriteChar(w, '{');
120     const Item *ip = list;
121     CYCLE_DECLARE(ip, slow, toggle);
122 
123     while (ip != NULL)
124     {
125         if (ip != list)
126         {
127             WriterWriteChar(w, ',');
128         }
129 
130         WriterWriteChar(w, '\'');
131         WriterWrite(w, ip->name);
132         WriterWriteChar(w, '\'');
133 
134         ip = ip->next;
135         CYCLE_CHECK(ip, slow, toggle);
136     }
137 
138     WriterWriteChar(w, '}');
139 }
140 
141 /*********************************************************************/
142 
ItemListSize(const Item * list)143 size_t ItemListSize(const Item *list)
144 {
145     size_t size = 0;
146     const Item *ip = list;
147     CYCLE_DECLARE(ip, slow, toggle);
148 
149     while (ip != NULL)
150     {
151         if (ip->name)
152         {
153             size += strlen(ip->name);
154         }
155         ip = ip->next;
156         CYCLE_CHECK(ip, slow, toggle);
157     }
158 
159     return size;
160 }
161 
162 /*********************************************************************/
163 
ReturnItemIn(Item * list,const char * item)164 Item *ReturnItemIn(Item *list, const char *item)
165 {
166     if (item == NULL || item[0] == '\0')
167     {
168         return NULL;
169     }
170 
171     Item *ptr = list;
172     CYCLE_DECLARE(ptr, slow, toggle);
173     while (ptr != NULL)
174     {
175         if (strcmp(ptr->name, item) == 0)
176         {
177             return ptr;
178         }
179         ptr = ptr->next;
180         CYCLE_CHECK(ptr, slow, toggle);
181     }
182 
183     return NULL;
184 }
185 
186 /*********************************************************************/
187 
ReturnItemInClass(Item * list,const char * item,const char * classes)188 Item *ReturnItemInClass(Item *list, const char *item, const char *classes)
189 {
190     if (item == NULL || item[0] == '\0')
191     {
192         return NULL;
193     }
194 
195     Item *ptr = list;
196     CYCLE_DECLARE(ptr, slow, toggle);
197     while (ptr != NULL)
198     {
199         if (strcmp(ptr->name, item) == 0 &&
200             strcmp(ptr->classes, classes) == 0)
201         {
202             return ptr;
203         }
204         ptr = ptr->next;
205         CYCLE_CHECK(ptr, slow, toggle);
206     }
207 
208     return NULL;
209 }
210 
211 /*********************************************************************/
212 
ReturnItemAtIndex(Item * list,int index)213 Item *ReturnItemAtIndex(Item *list, int index)
214 {
215     Item *ptr = list;
216     for (int i = 0; ptr != NULL && i < index; i++)
217     {
218         ptr = ptr->next;
219     }
220 
221     return ptr;
222 }
223 
224 /*********************************************************************/
225 
IsItemIn(const Item * list,const char * item)226 bool IsItemIn(const Item *list, const char *item)
227 {
228     if (item == NULL || item[0] == '\0')
229     {
230         return true;
231     }
232 
233     const Item *ptr = list;
234     CYCLE_DECLARE(ptr, slow, toggle);
235     while (ptr != NULL)
236     {
237         if (strcmp(ptr->name, item) == 0)
238         {
239             return true;
240         }
241         ptr = ptr->next;
242         CYCLE_CHECK(ptr, slow, toggle);
243     }
244 
245     return false;
246 }
247 
248 /*********************************************************************/
249 /* True precisely if the lists are of equal length and every entry of
250  * the first appears in the second.  As long as each list is known to
251  * have no duplication of its entries, this is equivalent to testing
252  * they have the same set of entries (ignoring order).
253  *
254  * This is not, in general, the same as the lists being equal !  They
255  * may have the same entries in different orders.  If the first list
256  * has some duplicate entries, the second list can have some entries
257  * not in the first, yet compare equal.  Two lists with the same set
258  * of entries but with different multiplicities are equal or different
259  * precisely if of equal length.
260  */
261 
ListsCompare(const Item * list1,const Item * list2)262 bool ListsCompare(const Item *list1, const Item *list2)
263 {
264     if (ListLen(list1) != ListLen(list2))
265     {
266         return false;
267     }
268 
269     const Item *ptr = list1;
270     CYCLE_DECLARE(ptr, slow, toggle);
271     while (ptr != NULL)
272     {
273         if (IsItemIn(list2, ptr->name) == false)
274         {
275             return false;
276         }
277         ptr = ptr->next;
278         CYCLE_CHECK(ptr, slow, toggle);
279     }
280 
281     return true;
282 }
283 
284 /**
285  * Checks whether list1 is a subset of list2, i.e. every entry in list1 must
286  * be found in list2.
287  */
ListSubsetOfList(const Item * list1,const Item * list2)288 bool ListSubsetOfList(const Item *list1, const Item *list2)
289 {
290     const Item *list1_ptr = list1;
291     CYCLE_DECLARE(list1_ptr, slow, toggle);
292 
293     while (list1_ptr != NULL)
294     {
295         if (!IsItemIn(list2, list1_ptr->name))
296         {
297             return false;
298         }
299 
300         list1_ptr = list1_ptr->next;
301         CYCLE_CHECK(list1_ptr, slow, toggle);
302     }
303 
304     return true;               /* all elements of list1 were found in list2 */
305 }
306 
307 /*********************************************************************/
308 
EndOfList(Item * ip)309 Item *EndOfList(Item *ip)
310 {
311     Item *prev = CF_UNDEFINED_ITEM;
312 
313     CYCLE_DECLARE(ip, slow, toggle);
314     while (ip != NULL)
315     {
316         prev = ip;
317         ip = ip->next;
318         CYCLE_CHECK(ip, slow, toggle);
319     }
320 
321     return prev;
322 }
323 
324 /*********************************************************************/
325 
IdempPrependItem(Item ** liststart,const char * itemstring,const char * classes)326 Item *IdempPrependItem(Item **liststart, const char *itemstring, const char *classes)
327 {
328     Item *ip = ReturnItemIn(*liststart, itemstring);
329     if (ip)
330     {
331         return ip;
332     }
333 
334     PrependItem(liststart, itemstring, classes);
335     return *liststart;
336 }
337 
338 /*********************************************************************/
339 
IdempPrependItemClass(Item ** liststart,const char * itemstring,const char * classes)340 Item *IdempPrependItemClass(Item **liststart, const char *itemstring, const char *classes)
341 {
342     Item *ip = ReturnItemInClass(*liststart, itemstring, classes);
343     if (ip)                     // already exists
344     {
345         return ip;
346     }
347 
348     PrependItem(liststart, itemstring, classes);
349     return *liststart;
350 }
351 
352 /*********************************************************************/
353 
IdempItemCount(Item ** liststart,const char * itemstring,const char * classes)354 void IdempItemCount(Item **liststart, const char *itemstring, const char *classes)
355 {
356     Item *ip = ReturnItemIn(*liststart, itemstring);
357 
358     if (ip)
359     {
360         ip->counter++;
361     }
362     else
363     {
364         PrependItem(liststart, itemstring, classes);
365     }
366 
367 // counter+1 is the histogram of occurrences
368 }
369 
370 /*********************************************************************/
371 
PrependItem(Item ** liststart,const char * itemstring,const char * classes)372 Item *PrependItem(Item **liststart, const char *itemstring, const char *classes)
373 {
374     Item *ip = xcalloc(1, sizeof(Item));
375 
376     ip->name = xstrdup(itemstring);
377     if (classes != NULL)
378     {
379         ip->classes = xstrdup(classes);
380     }
381 
382     ip->next = *liststart;
383     *liststart = ip;
384 
385     return ip;
386 }
387 
388 /*********************************************************************/
389 
PrependFullItem(Item ** liststart,const char * itemstring,const char * classes,int counter,time_t t)390 void PrependFullItem(Item **liststart, const char *itemstring, const char *classes, int counter, time_t t)
391 {
392     Item *ip = xcalloc(1, sizeof(Item));
393 
394     ip->name = xstrdup(itemstring);
395     ip->next = *liststart;
396     ip->counter = counter;
397     ip->time = t;
398     if (classes != NULL)
399     {
400         ip->classes = xstrdup(classes);
401     }
402 
403     *liststart = ip;
404 }
405 
406 /*********************************************************************/
407 /* Warning: doing this a lot incurs quadratic costs, as we have to run
408  * to the end of the list each time.  If you're building long lists,
409  * it is usually better to build the list with PrependItemList() and
410  * then use ReverseItemList() to get the entries in the order you
411  * wanted; for modest-sized n, 2*n < n*n, even after you've applied
412  * different fixed scalings to the two sides.
413  */
414 
AppendItem(Item ** liststart,const char * itemstring,const char * classes)415 void AppendItem(Item **liststart, const char *itemstring, const char *classes)
416 {
417     Item *ip = xcalloc(1, sizeof(Item));
418 
419     ip->name = xstrdup(itemstring);
420     if (classes)
421     {
422         ip->classes = xstrdup(classes); /* unused now */
423     }
424 
425     if (*liststart == NULL)
426     {
427         *liststart = ip;
428     }
429     else
430     {
431         Item *lp = EndOfList(*liststart);
432         assert(lp != CF_UNDEFINED_ITEM);
433         lp->next = ip;
434     }
435 }
436 
437 /*********************************************************************/
438 
PrependItemList(Item ** liststart,const char * itemstring)439 void PrependItemList(Item **liststart, const char *itemstring)
440 {
441     Item *ip = xcalloc(1, sizeof(Item));
442     ip->name = xstrdup(itemstring);
443 
444     ip->next = *liststart;
445     *liststart = ip;
446 }
447 
448 /*********************************************************************/
449 
ListLen(const Item * list)450 size_t ListLen(const Item *list)
451 {
452     int count = 0;
453     const Item *ip = list;
454     CYCLE_DECLARE(ip, slow, toggle);
455 
456     while (ip != NULL)
457     {
458         count++;
459         ip = ip->next;
460         CYCLE_CHECK(ip, slow, toggle);
461     }
462 
463     return count;
464 }
465 
466 /***************************************************************************/
467 
CopyList(Item ** dest,const Item * source)468 void CopyList(Item **dest, const Item *source)
469 /* Copy a list. */
470 {
471     if (*dest != NULL)
472     {
473         ProgrammingError("CopyList - list not initialized");
474     }
475 
476     if (source == NULL)
477     {
478         return;
479     }
480 
481     const Item *ip = source;
482     CYCLE_DECLARE(ip, slow, toggle);
483     Item *backwards = NULL;
484     while (ip != NULL)
485     {
486         PrependFullItem(&backwards, ip->name,
487                         ip->classes, ip->counter, ip->time);
488         ip = ip->next;
489         CYCLE_CHECK(ip, slow, toggle);
490     }
491     *dest = ReverseItemList(backwards);
492 }
493 
494 /*********************************************************************/
495 
ConcatLists(Item * list1,Item * list2)496 Item *ConcatLists(Item *list1, Item *list2)
497 /* Notes: * Refrain from freeing list2 after using ConcatLists
498           * list1 must have at least one element in it */
499 {
500     if (list1 == NULL)
501     {
502         ProgrammingError("ConcatLists: first argument must have at least one element");
503     }
504     Item *tail = EndOfList(list1);
505     assert(tail != CF_UNDEFINED_ITEM);
506     assert(tail->next == NULL);
507     /* If any entry in list1 is in list2, so is tail; so this is a
508      * sufficient check that we're not creating a loop: */
509     assert(!ItemIsInList(list2, tail));
510     tail->next = list2;
511     return list1;
512 }
513 
InsertAfter(Item ** filestart,Item * ptr,const char * string)514 void InsertAfter(Item **filestart, Item *ptr, const char *string)
515 {
516     if (*filestart == NULL || ptr == CF_UNDEFINED_ITEM)
517     {
518         AppendItem(filestart, string, NULL);
519         return;
520     }
521 
522     if (ptr == NULL)
523     {
524         AppendItem(filestart, string, NULL);
525         return;
526     }
527 
528     Item *ip = xcalloc(1, sizeof(Item));
529 
530     ip->next = ptr->next;
531     ptr->next = ip;
532     ip->name = xstrdup(string);
533     ip->classes = NULL;
534 }
535 
536 /**
537  *  Splits a string containing a separator like ':' into a linked list of
538  *  separate items,
539  *
540  *  @NOTE backslashes can be used to escape either the separator, or the
541  *        backslash itself, e.g. "\:" or "\\" (ofcourse proper C strings need
542  *        double backslash).
543  *
544  *  @NOTE separator can't be the backslash.
545  */
SplitString(const char * string,char sep)546 Item *SplitString(const char *string, char sep)
547 {
548     Item *liststart = NULL;
549 
550     size_t string_len = strlen(string);
551 
552     /* Temporary buffer for each chunk we append. This has the maximum
553      * possible size we might possibly need. */
554     char *buf = xmalloc(string_len + 1);
555     size_t buf_len = 0;
556 
557     /* We scan the string for separator or backslash. */
558     char sep2[3] = { sep, '\\', '\0' };
559     size_t z = 0;
560 
561     while ((z = strcspn(string, sep2)) < string_len)
562     {
563         memcpy(&buf[buf_len], string, z);
564         buf_len += z;
565 
566         if (string[z] == '\\')
567         {
568             /* Check next character after backslash, if it's backslash or
569              * separator then skip backslash, append character to buffer. */
570             if (string[z+1] == '\\' || string[z+1] == sep)
571             {
572                 z++;
573             }
574 
575             /* Append the single character to our buffer. */
576             buf[buf_len] = string[z];
577             buf_len++;
578 
579             /* SKIP, find next backslash or separator. */
580             string     += z + 1;
581             string_len -= z + 1;
582             continue;
583         }
584         else                   /* separator was found, and it's not escaped */
585         {
586             assert(string[z] == sep);
587             assert((z == 0) ||
588                    (z > 0 && string[z-1] != '\\'));
589         }
590 
591         /* Terminate buffer and add to Item list. */
592         buf[buf_len] = '\0';
593         PrependItem(&liststart, buf, NULL);
594 
595         /* Empty the buffer. */
596         buf_len = 0;
597         /* Start new search from after separator. */
598         string     += z + 1;
599         string_len -= z + 1;
600     }
601 
602     memcpy(&buf[buf_len], string, z);
603     buf_len += z;
604     buf[buf_len] = '\0';
605     PrependItem(&liststart, buf, NULL);
606 
607     free(buf);
608     return ReverseItemList(liststart);
609 }
610 
611 /*********************************************************************/
612 
SplitStringAsItemList(const char * string,char sep)613 Item *SplitStringAsItemList(const char *string, char sep)
614  /* Splits a string containing a separator like :
615     into a linked list of separate items, */
616 {
617     Item *liststart = NULL;
618     char node[256];
619     char format[] = "%255[^\0]";
620 
621     /* Overwrite format's internal \0 with sep: */
622     format[strlen(format)] = sep;
623     assert(strlen(format) + 1 == sizeof(format) || sep == '\0');
624 
625     for (const char *sp = string; *sp != '\0'; sp++)
626     {
627         if (sscanf(sp, format, node) == 1 &&
628             node[0] != '\0')
629         {
630             sp += strlen(node) - 1;
631             PrependItem(&liststart, node, NULL);
632         }
633     }
634 
635     return ReverseItemList(liststart);
636 }
637 
638 /*********************************************************************/
639 
640 /* NB: does not escape entries in list ! */
ItemList2CSV(const Item * list)641 char *ItemList2CSV(const Item *list)
642 {
643     /* After each entry, we need space for either a ',' (before the
644      * next entry) or a final '\0'. */
645     size_t s_size = ItemListSize(list) + ListLen(list);
646     if (s_size == 0)
647     {
648         s_size = 1;
649     }
650 
651     char *s = xmalloc(s_size);
652     *s = '\0';
653 
654     /* No point cycle-checking; done while computing s_size. */
655     for (const Item *ip = list; ip != NULL; ip = ip->next)
656     {
657         if (ip->name)
658         {
659             strcat(s, ip->name);
660         }
661 
662         if (ip->next)
663         {
664             strcat(s, ",");
665         }
666     }
667 
668     assert(strlen(s) + 1 == s_size);
669 
670     return s;
671 }
672 
673 /**
674  * Write all strings in list to buffer #buf, separating them with
675  * #separator. Watch out, no escaping happens.
676  *
677  * @return Final strlen(#buf), or #buf_size if string was truncated.
678  *         Always '\0'-terminates #buf (within #buf_size).
679  */
ItemList2CSV_bound(const Item * list,char * buf,size_t buf_size,char separator)680 size_t ItemList2CSV_bound(const Item *list, char *buf, size_t buf_size,
681                           char separator)
682 {
683     size_t space = buf_size - 1; /* Reserve one for a '\0' */
684     char *tail = buf; /* Point to end of what we've written. */
685     const Item *ip = list;
686     CYCLE_DECLARE(ip, slow, toggle);
687 
688     while (ip != NULL)
689     {
690         size_t len = strlen(ip->name);
691         assert(buf + buf_size == tail + space + 1);
692 
693         if (space < len)                  /* We must truncate */
694         {
695             memcpy(tail, ip->name, space);
696             tail[space] = '\0';   /* a.k.a. buf[buf_size - 1] */
697             return buf_size;     /* This signifies truncation */
698         }
699         else
700         {
701             memcpy(tail, ip->name, len);
702             tail += len;
703             space -= len;
704         }
705 
706         /* Output separator if list has more entries. */
707         if (ip->next != NULL)
708         {
709             if (space > 0)
710             {
711                 *tail = separator;
712                 tail++;
713                 space--;
714             }
715             else /* truncation */
716             {
717                 *tail = '\0';
718                 return buf_size;
719             }
720         }
721 
722         ip = ip->next;
723         CYCLE_CHECK(ip, slow, toggle);
724     }
725 
726     *tail = '\0';
727     return tail - buf;
728 }
729 
730 /*********************************************************************/
731 /* Basic operations                                                  */
732 /*********************************************************************/
733 
IncrementItemListCounter(Item * list,const char * item)734 void IncrementItemListCounter(Item *list, const char *item)
735 {
736     if (item == NULL || item[0] == '\0')
737     {
738         return;
739     }
740 
741     Item *ptr = list;
742     CYCLE_DECLARE(ptr, slow, toggle);
743     while (ptr != NULL)
744     {
745         if (strcmp(ptr->name, item) == 0)
746         {
747             ptr->counter++;
748             return;
749         }
750         ptr = ptr->next;
751         CYCLE_CHECK(ptr, slow, toggle);
752     }
753 }
754 
755 /*********************************************************************/
756 
SetItemListCounter(Item * list,const char * item,int value)757 void SetItemListCounter(Item *list, const char *item, int value)
758 {
759     if (item == NULL || item[0] == '\0')
760     {
761         return;
762     }
763 
764     Item *ptr = list;
765     CYCLE_DECLARE(ptr, slow, toggle);
766     while (ptr != NULL)
767     {
768         if (strcmp(ptr->name, item) == 0)
769         {
770             ptr->counter = value;
771             return;
772         }
773         ptr = ptr->next;
774         CYCLE_CHECK(ptr, slow, toggle);
775     }
776 }
777 
778 /*********************************************************************/
779 
IsMatchItemIn(const Item * list,const char * item)780 bool IsMatchItemIn(const Item *list, const char *item)
781 /* Solve for possible regex/fuzzy models unified */
782 {
783     if (item == NULL || item[0] == '\0')
784     {
785         return true;
786     }
787 
788     const Item *ptr = list;
789     CYCLE_DECLARE(ptr, slow, toggle);
790     while (ptr != NULL)
791     {
792         if (FuzzySetMatch(ptr->name, item) == 0 ||
793             (IsRegex(ptr->name) &&
794              StringMatchFull(ptr->name, item)))
795         {
796             return true;
797         }
798         ptr = ptr->next;
799         CYCLE_CHECK(ptr, slow, toggle);
800     }
801 
802     return false;
803 }
804 
805 /*********************************************************************/
806 /* Cycle-detection: you'll get a double free if there's a cycle. */
807 
DeleteItemList(Item * item)808 void DeleteItemList(Item *item) /* delete starting from item */
809 {
810     while (item != NULL)
811     {
812         Item *here = item;
813         item = here->next; /* before free()ing here */
814 
815         free(here->name);
816         free(here->classes);
817         free(here);
818     }
819 }
820 
821 /*********************************************************************/
822 
DeleteItem(Item ** liststart,Item * item)823 void DeleteItem(Item **liststart, Item *item)
824 {
825     if (item != NULL)
826     {
827         if (item == *liststart)
828         {
829             *liststart = item->next;
830         }
831         else
832         {
833             Item *ip = *liststart;
834             CYCLE_DECLARE(ip, slow, toggle);
835             while (ip && ip->next != item)
836             {
837                 ip = ip->next;
838                 CYCLE_CHECK(ip, slow, toggle);
839             }
840 
841             if (ip != NULL)
842             {
843                 assert(ip->next == item);
844                 ip->next = item->next;
845             }
846         }
847 
848         free(item->name);
849         free(item->classes);
850         free(item);
851     }
852 }
853 
854 /*********************************************************************/
855 
856 /* DeleteItem* function notes:
857  * -They all take an item list and an item specification ("string" argument.)
858  * -Some of them treat the item spec as a literal string, while others
859  *  treat it as a regular expression.
860  * -They all delete the first item meeting their criteria, as below.
861  *  function   deletes item
862  *  ------------------------------------------------------------------------
863  *  DeleteItemStarting  start is literally equal to string item spec
864  *  DeleteItemLiteral  literally equal to string item spec
865  *  DeleteItemMatching  fully matched by regex item spec
866  *  DeleteItemContaining containing string item spec
867  */
868 
869 /*********************************************************************/
870 
DeleteItemGeneral(Item ** list,const char * string,ItemMatchType type)871 bool DeleteItemGeneral(Item **list, const char *string, ItemMatchType type)
872 {
873     if (list == NULL)
874     {
875         return false;
876     }
877 
878     pcre *rx = NULL;
879     if (type == ITEM_MATCH_TYPE_REGEX_COMPLETE_NOT ||
880         type == ITEM_MATCH_TYPE_REGEX_COMPLETE)
881     {
882         rx = CompileRegex(string);
883         if (!rx)
884         {
885             return false;
886         }
887     }
888 
889     Item *ip = *list, *last = NULL;
890     CYCLE_DECLARE(ip, slow, toggle);
891     while (ip != NULL)
892     {
893         if (ip->name != NULL)
894         {
895             bool match = false, flip = false;
896             switch (type)
897             {
898             case ITEM_MATCH_TYPE_LITERAL_START_NOT:
899                 flip = true;
900                 /* fall through */
901             case ITEM_MATCH_TYPE_LITERAL_START:
902                 match = (strncmp(ip->name, string, strlen(string)) == 0);
903                 break;
904 
905             case ITEM_MATCH_TYPE_LITERAL_COMPLETE_NOT:
906                 flip = true;
907                 /* fall through */
908             case ITEM_MATCH_TYPE_LITERAL_COMPLETE:
909                 match = (strcmp(ip->name, string) == 0);
910                 break;
911 
912             case ITEM_MATCH_TYPE_LITERAL_SOMEWHERE_NOT:
913                 flip = true;
914                 /* fall through */
915             case ITEM_MATCH_TYPE_LITERAL_SOMEWHERE:
916                 match = (strstr(ip->name, string) != NULL);
917                 break;
918 
919             case ITEM_MATCH_TYPE_REGEX_COMPLETE_NOT:
920                 flip = true;
921                 /* fall through */
922             case ITEM_MATCH_TYPE_REGEX_COMPLETE:
923                 match = StringMatchFullWithPrecompiledRegex(rx, ip->name);
924                 break;
925             }
926             if (flip)
927             {
928                 match = !match;
929             }
930 
931             if (match)
932             {
933                 if (ip == *list)
934                 {
935                     *list = ip->next;
936                 }
937                 else
938                 {
939                     assert(ip != NULL);
940                     assert(last != NULL);
941                     assert(last->next == ip);
942                     last->next = ip->next;
943                 }
944 
945                 free(ip->name);
946                 free(ip->classes);
947                 free(ip);
948                 if (rx)
949                 {
950                     pcre_free(rx);
951                 }
952 
953                 return true;
954             }
955         }
956         last = ip;
957         ip = ip->next;
958         CYCLE_CHECK(ip, slow, toggle);
959     }
960 
961     if (rx)
962     {
963         pcre_free(rx);
964     }
965 
966     return false;
967 }
968 
969 /*********************************************************************/
970 
DeleteItemStarting(Item ** list,const char * string)971 bool DeleteItemStarting(Item **list, const char *string)       /* delete 1st item starting with string */
972 {
973     return DeleteItemGeneral(list, string, ITEM_MATCH_TYPE_LITERAL_START);
974 }
975 
976 /*********************************************************************/
977 
DeleteItemNotStarting(Item ** list,const char * string)978 bool DeleteItemNotStarting(Item **list, const char *string)    /* delete 1st item starting with string */
979 {
980     return DeleteItemGeneral(list, string, ITEM_MATCH_TYPE_LITERAL_START_NOT);
981 }
982 
983 /*********************************************************************/
984 
DeleteItemLiteral(Item ** list,const char * string)985 bool DeleteItemLiteral(Item **list, const char *string)  /* delete 1st item which is string */
986 {
987     return DeleteItemGeneral(list, string, ITEM_MATCH_TYPE_LITERAL_COMPLETE);
988 }
989 
990 /*********************************************************************/
991 
DeleteItemMatching(Item ** list,const char * string)992 bool DeleteItemMatching(Item **list, const char *string)       /* delete 1st item fully matching regex */
993 {
994     return DeleteItemGeneral(list, string, ITEM_MATCH_TYPE_REGEX_COMPLETE);
995 }
996 
997 /*********************************************************************/
998 
DeleteItemNotMatching(Item ** list,const char * string)999 bool DeleteItemNotMatching(Item **list, const char *string)    /* delete 1st item fully matching regex */
1000 {
1001     return DeleteItemGeneral(list, string, ITEM_MATCH_TYPE_REGEX_COMPLETE_NOT);
1002 }
1003 
1004 /*********************************************************************/
1005 
DeleteItemContaining(Item ** list,const char * string)1006 bool DeleteItemContaining(Item **list, const char *string)     /* delete first item containing string */
1007 {
1008     return DeleteItemGeneral(list, string, ITEM_MATCH_TYPE_LITERAL_SOMEWHERE);
1009 }
1010 
1011 /*********************************************************************/
1012 
DeleteItemNotContaining(Item ** list,const char * string)1013 bool DeleteItemNotContaining(Item **list, const char *string)  /* delete first item containing string */
1014 {
1015     return DeleteItemGeneral(list, string, ITEM_MATCH_TYPE_LITERAL_SOMEWHERE_NOT);
1016 }
1017 
1018 /*********************************************************************/
1019 
RawSaveItemList(const Item * liststart,const char * filename,NewLineMode new_line_mode)1020 bool RawSaveItemList(const Item *liststart, const char *filename, NewLineMode new_line_mode)
1021 {
1022     char new[CF_BUFSIZE];
1023     snprintf(new, sizeof(new), "%s%s", filename, CF_EDITED);
1024     unlink(new);                /* Just in case of races */
1025 
1026     FILE *fp = safe_fopen(
1027         new, (new_line_mode == NewLineMode_Native) ? "wt" : "w");
1028     if (fp == NULL)
1029     {
1030         Log(LOG_LEVEL_ERR, "Couldn't write file '%s'. (fopen: %s)", new, GetErrorStr());
1031         return false;
1032     }
1033 
1034     const Item *ip = liststart;
1035     CYCLE_DECLARE(ip, slow, toggle);
1036     while (ip != NULL)
1037     {
1038         fprintf(fp, "%s\n", ip->name);
1039         ip = ip->next;
1040         CYCLE_CHECK(ip, slow, toggle);
1041     }
1042 
1043     if (fclose(fp) == -1)
1044     {
1045         Log(LOG_LEVEL_ERR, "Unable to close file '%s' while writing. (fclose: %s)", new, GetErrorStr());
1046         return false;
1047     }
1048 
1049     if (rename(new, filename) == -1)
1050     {
1051         Log(LOG_LEVEL_INFO, "Error while renaming file '%s' to '%s'. (rename: %s)", new, filename, GetErrorStr());
1052         return false;
1053     }
1054 
1055     return true;
1056 }
1057 
RawLoadItemList(const char * filename)1058 Item *RawLoadItemList(const char *filename)
1059 {
1060     FILE *fp = safe_fopen(filename, "r");
1061     if (!fp)
1062     {
1063         return NULL;
1064     }
1065 
1066     size_t line_size = CF_BUFSIZE;
1067     char *line = xmalloc(line_size);
1068 
1069     Item *list = NULL;
1070     while (CfReadLine(&line, &line_size, fp) != -1)
1071     {
1072         PrependItem(&list, line, NULL);
1073     }
1074 
1075     free(line);
1076 
1077     if (!feof(fp))
1078     {
1079         Log(LOG_LEVEL_ERR, "Error while reading item list from file: %s", filename);
1080         DeleteItemList(list);
1081         list = NULL;
1082     }
1083     fclose(fp);
1084 
1085     return ReverseItemList(list);
1086 }
1087 
IsInterfaceAddress(const Item * ip_addresses,const char * adr)1088 bool IsInterfaceAddress(const Item *ip_addresses, const char *adr)
1089  /* Does this address belong to a local interface */
1090 {
1091     for (const Item *ip = ip_addresses; ip != NULL; ip = ip->next)
1092     {
1093         if (strncasecmp(adr, ip->name, strlen(adr)) == 0)
1094         {
1095             Log(LOG_LEVEL_DEBUG, "Identifying '%s' as one of my interfaces", adr);
1096             return true;
1097         }
1098     }
1099 
1100     Log(LOG_LEVEL_DEBUG, "'%s' is not one of my interfaces", adr);
1101     return false;
1102 }
1103