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