1 /*
2     INI LIBRARY
3 
4     Module represents interface to the main INI object.
5 
6     Copyright (C) Dmitri Pal <dpal@redhat.com> 2010
7 
8     INI Library is free software: you can redistribute it and/or modify
9     it under the terms of the GNU Lesser General Public License as published by
10     the Free Software Foundation, either version 3 of the License, or
11     (at your option) any later version.
12 
13     INI Library is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16     GNU Lesser General Public License for more details.
17 
18     You should have received a copy of the GNU Lesser General Public License
19     along with INI Library.  If not, see <http://www.gnu.org/licenses/>.
20 */
21 
22 #include "config.h"
23 #include <sys/types.h>
24 #include <regex.h>
25 #include <errno.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <stdint.h>
29 #include <stdlib.h>
30 #include <stdarg.h>
31 /* For error text */
32 #include <libintl.h>
33 #define _(String) gettext (String)
34 #include "trace.h"
35 #include "collection.h"
36 #include "collection_tools.h"
37 #include "ini_configobj.h"
38 #include "ini_config_priv.h"
39 #include "ini_defines.h"
40 #include "ini_valueobj.h"
41 #include "ini_configobj.h"
42 
43 /* Internal structure used during the merge operation */
44 struct merge_data {
45     struct collection_item *ci;
46     uint32_t flags;
47     int error;
48     int found;
49 };
50 
51 /* Callback */
ini_cleanup_cb(const char * property,int property_len,int type,void * data,int length,void * custom_data)52 void ini_cleanup_cb(const char *property,
53                     int property_len,
54                     int type,
55                     void *data,
56                     int length,
57                     void *custom_data)
58 {
59     struct value_obj *vo = NULL;
60 
61     TRACE_FLOW_ENTRY();
62     TRACE_INFO_STRING("Cleaning ", property);
63 
64     /* Banary items are the values */
65     if(type == COL_TYPE_BINARY) {
66         vo = *((struct value_obj **)(data));
67         value_destroy(vo);
68     }
69 
70     TRACE_FLOW_EXIT();
71 }
72 
73 /* Clean the search state */
ini_config_clean_state(struct ini_cfgobj * ini_config)74 void ini_config_clean_state(struct ini_cfgobj *ini_config)
75 {
76     TRACE_FLOW_ENTRY();
77 
78     if (ini_config) {
79         if (ini_config->iterator) col_unbind_iterator(ini_config->iterator);
80         ini_config->iterator = NULL;
81         free(ini_config->section);
82         ini_config->section = NULL;
83         free(ini_config->name);
84         ini_config->name = NULL;
85         ini_config->section_len = 0;
86         ini_config->name_len = 0;
87     }
88 
89     TRACE_FLOW_EXIT();
90 }
91 
92 
93 
94 /* Traverse the collection and clean the object */
ini_config_destroy(struct ini_cfgobj * ini_config)95 void ini_config_destroy(struct ini_cfgobj *ini_config)
96 {
97     TRACE_FLOW_ENTRY();
98 
99     ini_config_clean_state(ini_config);
100 
101     if (ini_config) {
102         if (ini_config->cfg) {
103 
104             col_destroy_collection_with_cb(ini_config->cfg,
105                                            ini_cleanup_cb,
106                                            NULL);
107         }
108         if (ini_config->last_comment) {
109             ini_comment_destroy(ini_config->last_comment);
110         }
111 
112         if (ini_config->error_list) {
113             col_destroy_collection(ini_config->error_list);
114         }
115 
116         free(ini_config);
117     }
118 
119     TRACE_FLOW_EXIT();
120 }
121 
122 /* Create a config object */
ini_config_create(struct ini_cfgobj ** ini_config)123 int ini_config_create(struct ini_cfgobj **ini_config)
124 {
125     int error = EOK;
126     struct ini_cfgobj *new_co = NULL;
127 
128     TRACE_FLOW_ENTRY();
129 
130     if (!ini_config) {
131         TRACE_ERROR_NUMBER("Invalid argument", EINVAL);
132         return EINVAL;
133     }
134 
135     new_co = malloc(sizeof(struct ini_cfgobj));
136     if (!new_co) {
137         TRACE_ERROR_NUMBER("Failed to allocate memory", ENOMEM);
138         return ENOMEM;
139     }
140 
141     new_co->cfg = NULL;
142     new_co->boundary = INI_WRAP_BOUNDARY;
143     new_co->last_comment = NULL;
144     new_co->section = NULL;
145     new_co->name = NULL;
146     new_co->section_len = 0;
147     new_co->name_len = 0;
148     new_co->iterator = NULL;
149     new_co->error_list = NULL;
150     new_co->count = 0;
151 
152     /* Create a collection to hold configuration data */
153     error = col_create_collection(&(new_co->cfg),
154                                   INI_CONFIG_NAME,
155                                   COL_CLASS_INI_CONFIG);
156     if (error != EOK) {
157         TRACE_ERROR_NUMBER("Failed to create collection.", error);
158         ini_config_destroy(new_co);
159         return error;
160     }
161 
162     /* Create error list collection */
163     error = col_create_collection(&(new_co->error_list),
164                                   INI_ERROR,
165                                   COL_CLASS_INI_PERROR);
166     if (error) {
167         TRACE_ERROR_NUMBER("Failed to create error list", error);
168         ini_config_destroy(new_co);
169         return error;
170     }
171 
172     *ini_config = new_co;
173 
174     TRACE_FLOW_EXIT();
175     return error;
176 }
177 
178 /* Callback to set the boundary */
ini_boundary_cb(const char * property,int property_len,int type,void * data,int length,void * custom_data,int * dummy)179 static int ini_boundary_cb(const char *property,
180                            int property_len,
181                            int type,
182                            void *data,
183                            int length,
184                            void *custom_data,
185                            int *dummy)
186 {
187     int error = EOK;
188     struct value_obj *vo = NULL;
189     uint32_t boundary;
190 
191     TRACE_FLOW_ENTRY();
192 
193     boundary = *((uint32_t *)(custom_data));
194     /* Banary items are the values */
195     if(type == COL_TYPE_BINARY) {
196         vo = *((struct value_obj **)(data));
197         error = value_set_boundary(vo, boundary);
198     }
199 
200     TRACE_FLOW_EXIT();
201     return error;
202 }
203 
204 /* Set the folding boundary for multiline values.
205  * Use before serializing and saving to a file if the
206  * default boundary of 80 characters does not work for you.
207  */
ini_config_set_wrap(struct ini_cfgobj * ini_config,uint32_t boundary)208 int ini_config_set_wrap(struct ini_cfgobj *ini_config,
209                         uint32_t boundary)
210 {
211     int error = EOK;
212 
213     TRACE_FLOW_ENTRY();
214 
215     if (!ini_config) {
216         TRACE_ERROR_NUMBER("Invalid argument", EINVAL);
217         return EINVAL;
218     }
219 
220     ini_config->boundary = boundary;
221     error = col_traverse_collection(ini_config->cfg,
222                                     COL_TRAVERSE_DEFAULT,
223                                     ini_boundary_cb,
224                                     (void *)(&(ini_config->boundary)));
225     if (error) {
226         TRACE_ERROR_NUMBER("Failed to set wrapping boundary", error);
227         return error;
228     }
229 
230 
231     TRACE_FLOW_EXIT();
232     return error;
233 }
234 
235 /* Configuration copy callback */
ini_copy_cb(struct collection_item * item,void * ext_data,int * skip)236 static int ini_copy_cb(struct collection_item *item,
237                        void *ext_data,
238                        int *skip)
239 {
240     int error = EOK;
241     struct value_obj *vo = NULL;
242     struct value_obj *new_vo = NULL;
243 
244     TRACE_FLOW_ENTRY();
245 
246     *skip = 0;
247 
248     /* Binary items are the values */
249     if(col_get_item_type(item) == COL_TYPE_BINARY) {
250         vo = *((struct value_obj **)(col_get_item_data(item)));
251 
252         error = value_copy(vo, &new_vo);
253         if (error) {
254             TRACE_ERROR_NUMBER("Failed to copy value", error);
255             return error;
256         }
257 
258         error = col_modify_binary_item(item,
259                                        NULL,
260                                        &new_vo,
261                                        sizeof(struct value_obj *));
262         if (error) {
263             TRACE_ERROR_NUMBER("Failed to copy value", error);
264             value_destroy(new_vo);
265             return error;
266         }
267     }
268 
269     TRACE_FLOW_EXIT();
270     return error;
271 }
272 
273 /* Check flags for flag */
ini_flags_have(uint32_t flag,uint32_t flags)274 int ini_flags_have(uint32_t flag, uint32_t flags)
275 {
276     switch (flag) {
277     case INI_MS_MERGE:
278     case INI_MS_ERROR:
279     case INI_MS_OVERWRITE:
280     case INI_MS_PRESERVE:
281         return flag == (flags & INI_MS_MODE_MASK);
282     case INI_MS_DETECT:
283         return flag == (flags & INI_MS_DETECT);
284     default:
285         TRACE_ERROR_NUMBER("Unsupported flag", flag);
286     }
287     return 0;
288 }
289 
290 /* Copy configuration */
ini_config_copy(struct ini_cfgobj * ini_config,struct ini_cfgobj ** ini_new)291 int ini_config_copy(struct ini_cfgobj *ini_config,
292                     struct ini_cfgobj **ini_new)
293 {
294     int error = EOK;
295     struct ini_cfgobj *new_co = NULL;
296 
297     TRACE_FLOW_ENTRY();
298 
299     if ((!ini_config) ||
300         (!ini_new)) {
301         TRACE_ERROR_NUMBER("Invalid argument", EINVAL);
302         return EINVAL;
303     }
304 
305     /* Create a new configuration object */
306     new_co = malloc(sizeof(struct ini_cfgobj));
307     if (!new_co) {
308         TRACE_ERROR_NUMBER("Failed to allocate memory", ENOMEM);
309         return ENOMEM;
310     }
311 
312     new_co->cfg = NULL;
313     new_co->boundary = ini_config->boundary;
314     new_co->last_comment = NULL;
315     new_co->section = NULL;
316     new_co->name = NULL;
317     new_co->section_len = 0;
318     new_co->name_len = 0;
319     new_co->iterator = NULL;
320     new_co->error_list = NULL;
321     new_co->count = 0;
322 
323     error = col_copy_collection_with_cb(&(new_co->cfg),
324                                         ini_config->cfg,
325                                         INI_CONFIG_NAME,
326                                         COL_COPY_NORMAL,
327                                         ini_copy_cb,
328                                         NULL);
329     if (error) {
330         TRACE_ERROR_NUMBER("Failed to copy collection", error);
331         ini_config_destroy(new_co);
332         return error;
333     }
334 
335     if (ini_config->last_comment) {
336         error = ini_comment_copy(ini_config->last_comment,
337                                  &(new_co->last_comment));
338         if (error) {
339             TRACE_ERROR_NUMBER("Failed to copy comment", error);
340             ini_config_destroy(new_co);
341             return error;
342         }
343     }
344 
345     *ini_new = new_co;
346 
347     TRACE_FLOW_EXIT();
348     return error;
349 }
350 
351 
352 /* Callback to process merging of the sections */
merge_section_handler(const char * property,int property_len,int type,void * data,int length,void * custom_data,int * dummy)353 static int merge_section_handler(const char *property,
354                                  int property_len,
355                                  int type,
356                                  void *data,
357                                  int length,
358                                  void *custom_data,
359                                  int *dummy)
360 {
361     int error = EOK;
362     struct value_obj *vo = NULL;
363     struct value_obj *new_vo = NULL;
364     struct value_obj *vo_old = NULL;
365     struct merge_data *passed_data;
366     struct collection_item *acceptor = NULL;
367     struct collection_item *item = NULL;
368     unsigned insertmode;
369     uint32_t mergemode;
370     int suppress = 0;
371     int doinsert = 0;
372 
373     TRACE_FLOW_ENTRY();
374 
375     if ((type != COL_TYPE_BINARY) ||
376         ((type == COL_TYPE_BINARY) &&
377          (strncmp(property, INI_SECTION_KEY,
378                      sizeof(INI_SECTION_KEY)) == 0))) {
379         /* Skip items we do not care about */
380         TRACE_FLOW_EXIT();
381         return EOK;
382     }
383 
384     /* Get value */
385     vo = *((struct value_obj **)(data));
386 
387     /* Copy it */
388     error = value_copy(vo, &new_vo);
389     if (error) {
390         TRACE_ERROR_NUMBER("Failed to copy value", error);
391         return error;
392     }
393 
394     passed_data = (struct merge_data *)(custom_data);
395     acceptor = passed_data->ci;
396     mergemode = passed_data->flags & INI_MV2S_MASK;
397 
398     switch (mergemode) {
399     case INI_MV2S_ERROR:     insertmode = COL_INSERT_DUPERROR;
400                              doinsert = 1;
401                              break;
402     case INI_MV2S_PRESERVE:  insertmode = COL_INSERT_DUPERROR;
403                              doinsert = 1;
404                              suppress = 1;
405                              break;
406     case INI_MV2S_ALLOW:     insertmode = COL_INSERT_NOCHECK;
407                              doinsert = 1;
408                              break;
409     case INI_MV2S_OVERWRITE: /* Special handling */
410     case INI_MV2S_DETECT:
411     default:
412                              break;
413     }
414 
415     /* Do not insert but search for dups first */
416     if (!doinsert) {
417         TRACE_INFO_STRING("Overwrite mode. Looking for:",
418                           property);
419 
420         error = col_get_item(acceptor,
421                              property,
422                              COL_TYPE_BINARY,
423                              COL_TRAVERSE_DEFAULT,
424                              &item);
425 
426         if (error) {
427             TRACE_ERROR_NUMBER("Failed searching for dup", error);
428             value_destroy(new_vo);
429             return error;
430         }
431 
432         /* Check if there is a dup */
433         if (item) {
434             /* Check if we are in the detect mode */
435             if (mergemode == INI_MV2S_DETECT) {
436                 passed_data->error = EEXIST;
437                 doinsert = 1;
438                 insertmode = COL_INSERT_NOCHECK;
439             }
440             else {
441 
442                 /* We are in the OVERWRITE mode.
443                  * Dup exists - update it.
444                  */
445                 vo_old = *((struct value_obj **)(col_get_item_data(item)));
446                 error = col_modify_binary_item(item,
447                                                NULL,
448                                                &new_vo,
449                                                sizeof(struct value_obj *));
450                 if (error) {
451                     TRACE_ERROR_NUMBER("Failed updating the value", error);
452                     value_destroy(new_vo);
453                     return error;
454                 }
455 
456                 /* If we failed to update it is better to leak then crash,
457                  * so destroy original value only on the successful update.
458                  */
459                 value_destroy(vo_old);
460             }
461         }
462         else {
463             /* No dup found so we can insert with no check */
464             doinsert = 1;
465             insertmode = COL_INSERT_NOCHECK;
466         }
467     }
468 
469     if (doinsert) {
470         /* Add value to collection */
471         error = col_insert_binary_property(acceptor,
472                                            NULL,
473                                            COL_DSP_END,
474                                            NULL,
475                                            0,
476                                            insertmode,
477                                            property,
478                                            &new_vo,
479                                            sizeof(struct value_obj *));
480         if (error) {
481             value_destroy(new_vo);
482 
483             if ((suppress) && (error == EEXIST)) {
484                 /* We are here is we do not allow dups
485                  * but found one and need to ignore it.
486                  */
487                 TRACE_INFO_STRING("Preseved exisitng value",
488                                   property);
489                 error = 0;
490             }
491             else {
492                 /* Check if this is a critical error or not */
493                 if ((mergemode == INI_MV2S_ERROR) && (error == EEXIST)) {
494                     TRACE_ERROR_NUMBER("Failed to add value object to "
495                                        "the section in error mode ", error);
496                     passed_data->error = EEXIST;
497                     *dummy = 1;
498                 }
499                 else {
500                     TRACE_ERROR_NUMBER("Failed to add value object"
501                                        " to the section", error);
502                     return error;
503                 }
504             }
505         }
506     }
507 
508     TRACE_FLOW_EXIT();
509     return error;
510 }
511 
512 
513 /* Internal function to merge two configs */
merge_two_sections(struct collection_item * donor,struct collection_item * acceptor,uint32_t flags)514 static int merge_two_sections(struct collection_item *donor,
515                               struct collection_item *acceptor,
516                               uint32_t flags)
517 {
518     int error = EOK;
519     struct merge_data data;
520 
521     TRACE_FLOW_ENTRY();
522 
523     data.ci = acceptor;
524     data.flags = flags;
525     data.error = 0;
526     data.found = 0;
527 
528     error = col_traverse_collection(donor,
529                                     COL_TRAVERSE_ONELEVEL,
530                                     merge_section_handler,
531                                     (void *)(&data));
532     if (error) {
533         TRACE_ERROR_NUMBER("Merge values failed", error);
534         return error;
535     }
536 
537     TRACE_FLOW_EXIT();
538     return data.error;
539 }
540 
541 
542 
543 /* Callback to process the accepting config */
acceptor_handler(const char * property,int property_len,int type,void * data,int length,void * custom_data,int * dummy)544 static int acceptor_handler(const char *property,
545                             int property_len,
546                             int type,
547                             void *data,
548                             int length,
549                             void *custom_data,
550                             int *dummy)
551 {
552     int error = EOK;
553     struct merge_data *passed_data;
554     struct collection_item *acceptor = NULL;
555     struct collection_item *donor = NULL;
556     uint32_t mergemode;
557 
558     TRACE_FLOW_ENTRY();
559 
560     /* This callback is called when the dup section is found */
561     passed_data = (struct merge_data *)(custom_data);
562     passed_data->found = 1;
563 
564     donor = passed_data->ci;
565     acceptor = *((struct collection_item **)(data));
566 
567     mergemode = passed_data->flags & INI_MS_MODE_MASK;
568 
569     if (passed_data->flags & INI_MS_DETECT) {
570         TRACE_INFO_STRING("Detect mode", "");
571         passed_data->error = EEXIST;
572     }
573 
574     switch (mergemode) {
575     case INI_MS_ERROR:      /* Report error and return */
576                             TRACE_INFO_STRING("Error ",
577                                               "duplicate section");
578                             passed_data->error = EEXIST;
579                             break;
580 
581     case INI_MS_PRESERVE:   /* Preserve what we have */
582                             TRACE_INFO_STRING("Preserve mode", "");
583                             break;
584 
585     case INI_MS_OVERWRITE:  /* Empty existing section */
586                             TRACE_INFO_STRING("Ovewrite mode", "");
587                             error = empty_section(acceptor);
588                             if (error) {
589                                 TRACE_ERROR_NUMBER("Failed to "
590                                                     "empty section",
591                                                     error);
592                                 return error;
593                             }
594                             error = merge_two_sections(donor,
595                                                        acceptor,
596                                                        passed_data->flags);
597                             if (error) {
598                                 TRACE_ERROR_NUMBER("Failed to merge "
599                                                     "sections", error);
600                                 if (error == EEXIST) {
601                                     passed_data->error = error;
602                                 }
603                                 return error;
604                             }
605                             break;
606 
607     case INI_MS_MERGE:      /* Merge */
608     default:                TRACE_INFO_STRING("Merge mode", "");
609                             error = merge_two_sections(donor,
610                                                        acceptor,
611                                                        passed_data->flags);
612                             if (error) {
613                                 if (error != EEXIST) {
614                                     TRACE_ERROR_NUMBER("Failed to merge "
615                                                        "sections", error);
616                                     return error;
617                                 }
618 
619                                 if (!(passed_data->flags & INI_MS_DETECT)) {
620                                     passed_data->error = error;
621                                 }
622 
623                                 error = EOK;
624                             }
625                             break;
626     }
627 
628     if (error == EOK) {
629         *dummy = 1;
630     }
631 
632     TRACE_FLOW_EXIT();
633     return error;
634 }
635 
636 /* Callback to process the donating config */
donor_handler(const char * property,int property_len,int type,void * data,int length,void * custom_data,int * dummy)637 static int donor_handler(const char *property,
638                          int property_len,
639                          int type,
640                          void *data,
641                          int length,
642                          void *custom_data,
643                          int *dummy)
644 {
645     int error = EOK;
646     struct merge_data *passed_data;
647     struct merge_data acceptor_data;
648     struct collection_item *new_ci = NULL;
649 
650     TRACE_FLOW_ENTRY();
651 
652     *dummy = 0;
653 
654     /* Opaque data passed to callback is merge data */
655     passed_data = (struct merge_data *)(custom_data);
656 
657     TRACE_INFO_STRING("Property: ", property);
658     TRACE_INFO_NUMBER("Type is: ", type);
659     TRACE_INFO_NUMBER("Flags: ", passed_data->flags);
660 
661     /* All sections are subcollections */
662     if(type == COL_TYPE_COLLECTIONREF) {
663 
664         /* Prepare data for the next callback */
665         acceptor_data.flags = passed_data->flags;
666         acceptor_data.ci = *((struct collection_item **)(data));
667         acceptor_data.error = 0;
668         acceptor_data.found = 0;
669 
670         /* Try to find same section as the current one */
671         error = col_get_item_and_do(passed_data->ci,
672                                     property,
673                                     COL_TYPE_COLLECTIONREF,
674                                     COL_TRAVERSE_ONELEVEL,
675                                     acceptor_handler,
676                                     (void *)(&acceptor_data));
677         if (error) {
678             TRACE_ERROR_NUMBER("Critical error", error);
679             return error;
680         }
681 
682         /* Was duplicate found ? */
683         if (acceptor_data.found) {
684             /* Check for logical error. It can be only EEXIST */
685             if (acceptor_data.error) {
686                 /* Save error anyway */
687                 passed_data->error = acceptor_data.error;
688                 /* If it is section DETECT or MERGE+DETECT */
689                 if (ini_flags_have(INI_MS_DETECT, passed_data->flags) ||
690                     (!ini_flags_have(INI_MS_ERROR, passed_data->flags) &&
691                      ((passed_data->flags & INI_MV2S_MASK) ==
692                        INI_MV2S_DETECT))) {
693                     TRACE_INFO_NUMBER("Non-critical error",
694                                       acceptor_data.error);
695                 }
696                 else {
697                     /* In any other mode we need to stop */
698                     TRACE_INFO_NUMBER("Merge error detected",
699                                       acceptor_data.error);
700                     /* Force stop */
701                     *dummy = 1;
702                 }
703             }
704         }
705         else {
706             /* Not found? Then create a copy... */
707             error = col_copy_collection_with_cb(&new_ci,
708                                                 acceptor_data.ci,
709                                                 NULL,
710                                                 COL_COPY_NORMAL,
711                                                 ini_copy_cb,
712                                                 NULL);
713             if (error) {
714                 TRACE_ERROR_NUMBER("Failed to copy collection", error);
715                 return error;
716             }
717 
718             /* ... and embed into the existing collection */
719             error = col_add_collection_to_collection(passed_data->ci,
720                                                      NULL,
721                                                      NULL,
722                                                      new_ci,
723                                                      COL_ADD_MODE_EMBED);
724             if (error) {
725                 TRACE_ERROR_NUMBER("Failed to copy collection", error);
726                 col_destroy_collection(new_ci);
727                 return error;
728             }
729         }
730     }
731 
732     TRACE_FLOW_EXIT();
733     return EOK;
734 }
735 
merge_comment(struct ini_cfgobj * donor,struct ini_cfgobj * acceptor)736 static int merge_comment(struct ini_cfgobj *donor,
737                          struct ini_cfgobj *acceptor)
738 {
739     int error = EOK;
740 
741     TRACE_FLOW_ENTRY();
742 
743     if (donor->last_comment) {
744 
745         if (acceptor->last_comment) {
746 
747             error = ini_comment_add(donor->last_comment,
748                                     acceptor->last_comment);
749             if (error) {
750                 TRACE_ERROR_NUMBER("Merge comment failed", error);
751                 return error;
752             }
753 
754         }
755         else {
756             error = ini_comment_copy(donor->last_comment,
757                                      &(acceptor->last_comment));
758             if (error) {
759                 TRACE_ERROR_NUMBER("Copy comment failed", error);
760                 return error;
761             }
762         }
763     }
764 
765     TRACE_FLOW_EXIT();
766     return EOK;
767 }
768 
769 
770 
771 /* Internal function to merge two configs */
merge_configs(struct ini_cfgobj * donor,struct ini_cfgobj * acceptor,uint32_t collision_flags)772 static int merge_configs(struct ini_cfgobj *donor,
773                          struct ini_cfgobj *acceptor,
774                          uint32_t collision_flags)
775 {
776     int error = EOK;
777     struct merge_data data;
778 
779     TRACE_FLOW_ENTRY();
780 
781     data.ci = acceptor->cfg;
782     data.flags = collision_flags;
783     data.error = 0;
784     data.found = 0;
785 
786     /* Loop through the donor collection calling
787      * donor_handler callback for every section we find.
788      */
789     error = col_traverse_collection(donor->cfg,
790                                     COL_TRAVERSE_ONELEVEL,
791                                     donor_handler,
792                                     (void *)(&data));
793     if (error) {
794         TRACE_ERROR_NUMBER("Merge failed", error);
795         return error;
796     }
797 
798     /* Check if we got error */
799     if ((data.error) &&
800         (ini_flags_have(INI_MS_ERROR, collision_flags) ||
801          ((collision_flags & INI_MV2S_MASK) == INI_MV2S_ERROR))) {
802         TRACE_ERROR_NUMBER("Got error in error mode", data.error);
803         return data.error;
804     }
805 
806     /* If boundaries are different re-align the values */
807     if (acceptor->boundary != donor->boundary) {
808         error = ini_config_set_wrap(acceptor, acceptor->boundary);
809         if (error) {
810             TRACE_ERROR_NUMBER("Failed to re-align", error);
811             return error;
812         }
813     }
814 
815     /* Merge last comment */
816     error = merge_comment(donor, acceptor);
817     if (error) {
818         TRACE_ERROR_NUMBER("Failed to merge comment", error);
819         return error;
820     }
821 
822     /* Check if we got error */
823     if ((data.error) &&
824         (ini_flags_have(INI_MS_DETECT, collision_flags) ||
825          ((collision_flags & INI_MV2S_MASK) == INI_MV2S_DETECT))) {
826         TRACE_ERROR_NUMBER("Got error in error or detect mode", data.error);
827         error = data.error;
828     }
829 
830     TRACE_FLOW_EXIT();
831     return error;
832 }
833 
834 /* Check if collision flags are valid */
valid_collision_flags(uint32_t collision_flags)835 int valid_collision_flags(uint32_t collision_flags)
836 {
837     uint32_t flag;
838 
839     TRACE_FLOW_ENTRY();
840 
841     flag = collision_flags & INI_MV1S_MASK;
842     if ((flag != INI_MV1S_OVERWRITE) &&
843         (flag != INI_MV1S_ERROR) &&
844         (flag != INI_MV1S_PRESERVE) &&
845         (flag != INI_MV1S_ALLOW) &&
846         (flag != INI_MV1S_DETECT)) {
847         TRACE_ERROR_STRING("Invalid value collision flag","");
848         return 0;
849     }
850 
851     flag = collision_flags & INI_MV2S_MASK;
852     if ((flag != INI_MV2S_OVERWRITE) &&
853         (flag != INI_MV2S_ERROR) &&
854         (flag != INI_MV2S_PRESERVE) &&
855         (flag != INI_MV2S_ALLOW) &&
856         (flag != INI_MV2S_DETECT)) {
857         TRACE_ERROR_STRING("Invalid value cross-section collision flag","");
858         return 0;
859     }
860 
861     /* Any combination of DETECT and a MODE flag is valid. */
862     flag = collision_flags & INI_MS_MODE_MASK;
863     if ((flag != INI_MS_MERGE) &&
864         (flag != INI_MS_OVERWRITE) &&
865         (flag != INI_MS_ERROR) &&
866         (flag != INI_MS_PRESERVE)) {
867         TRACE_ERROR_STRING("Invalid section collision flag","");
868         return 0;
869     }
870 
871     TRACE_FLOW_EXIT();
872     return 1;
873 }
874 
875 /* Merge two configurations together creating a new one */
ini_config_merge(struct ini_cfgobj * first,struct ini_cfgobj * second,uint32_t collision_flags,struct ini_cfgobj ** result)876 int ini_config_merge(struct ini_cfgobj *first,
877                      struct ini_cfgobj *second,
878                      uint32_t collision_flags,
879                      struct ini_cfgobj **result)
880 {
881     int error = EOK;
882     struct ini_cfgobj *new_co = NULL;
883 
884     TRACE_FLOW_ENTRY();
885 
886     /* Check input params */
887     if ((!first) ||
888         (!second) ||
889         (!result)) {
890         TRACE_ERROR_NUMBER("Invalid argument", EINVAL);
891         return EINVAL;
892     }
893 
894     /* Check collision flags */
895     if (!valid_collision_flags(collision_flags)) {
896         TRACE_ERROR_NUMBER("Invalid flags.", EINVAL);
897         return EINVAL;
898     }
899 
900     /* NOTE: We assume that the configuration we merge to
901      * is consistent regarding duplicate values.
902      * For example, if the duplicates are not allowed,
903      * the parsing function should have been instructed
904      * to not allow duplicates.
905      * If in future we decide to be explicite we would need
906      * to introduce a "compacting" function and call it here
907      * after we create a copy.
908      * For now it is treated as a corner case and thus not worth
909      * implementing.
910      */
911 
912     /* Create a new config object */
913     error = ini_config_copy(first, &new_co);
914     if (error) {
915         TRACE_ERROR_NUMBER("Failed to copy configuration", error);
916         return error;
917     }
918 
919     /* Merge configs */
920     error = merge_configs(second, new_co, collision_flags);
921     if (error) {
922         TRACE_ERROR_NUMBER("Failed to merge configuration", error);
923         if ((error == EEXIST) &&
924             ((ini_flags_have(INI_MS_DETECT, collision_flags) &&
925               ((collision_flags & INI_MV2S_MASK) != INI_MV2S_ERROR)) ||
926              (!ini_flags_have(INI_MS_ERROR, collision_flags) &&
927               ((collision_flags & INI_MV2S_MASK) == INI_MV2S_DETECT)))) {
928             TRACE_ERROR_NUMBER("Got error in detect mode", error);
929             /* Fall through! */
930         }
931         else {
932             /* Got an error in any other mode */
933             TRACE_ERROR_NUMBER("Got error in non detect mode", error);
934             ini_config_destroy(new_co);
935             return error;
936         }
937     }
938 
939     *result = new_co;
940     TRACE_FLOW_EXIT();
941     return error;
942 
943 }
944 
945 /* How many errors do we have in the list ? */
ini_config_error_count(struct ini_cfgobj * cfg_ctx)946 unsigned ini_config_error_count(struct ini_cfgobj *cfg_ctx)
947 {
948     unsigned count = 0;
949 
950     TRACE_FLOW_ENTRY();
951 
952     count = cfg_ctx->count;
953 
954     TRACE_FLOW_EXIT();
955     return count;
956 
957 }
958 
959 /* Free error strings */
ini_config_free_errors(char ** errors)960 void ini_config_free_errors(char **errors)
961 {
962     TRACE_FLOW_ENTRY();
963 
964     col_free_property_list(errors);
965 
966     TRACE_FLOW_EXIT();
967 }
968 
969 /* Get the list of error strings */
ini_config_get_errors(struct ini_cfgobj * cfg_ctx,char *** errors)970 int ini_config_get_errors(struct ini_cfgobj *cfg_ctx,
971                           char ***errors)
972 {
973     char **errlist = NULL;
974     struct collection_iterator *iterator = NULL;
975     int error;
976     struct collection_item *item = NULL;
977     struct ini_parse_error *pe;
978     unsigned int count = 0;
979     char *line;
980 
981     TRACE_FLOW_ENTRY();
982 
983     /* If we have something to print print it */
984     if ((!errors) || (!cfg_ctx)) {
985         TRACE_ERROR_NUMBER("Invalid parameter.", EINVAL);
986         return EINVAL;
987     }
988 
989     errlist = calloc(cfg_ctx->count + 1, sizeof(char *));
990     if (!errlist) {
991         TRACE_ERROR_NUMBER("Failed to allocate memory for errors.", ENOMEM);
992         return ENOMEM;
993     }
994 
995     /* Bind iterator */
996     error =  col_bind_iterator(&iterator,
997                                cfg_ctx->error_list,
998                                COL_TRAVERSE_DEFAULT);
999     if (error) {
1000         TRACE_ERROR_NUMBER("Faile to bind iterator:", error);
1001         ini_config_free_errors(errlist);
1002         return error;
1003     }
1004 
1005     while(1) {
1006         /* Loop through a collection */
1007         error = col_iterate_collection(iterator, &item);
1008         if (error) {
1009             TRACE_ERROR_NUMBER("Error iterating collection", error);
1010             col_unbind_iterator(iterator);
1011             ini_config_free_errors(errlist);
1012             return error;
1013         }
1014 
1015         /* Are we done ? */
1016         if (item == NULL) break;
1017 
1018         /* Process collection header */
1019         if (col_get_item_type(item) == COL_TYPE_COLLECTION) {
1020             continue;
1021         }
1022         else {
1023             /* Put error into provided format */
1024             pe = (struct ini_parse_error *)(col_get_item_data(item));
1025 
1026             /* Would be nice to have asprintf function...
1027              * ...but for now we know that all the errors
1028              * are pretty short and will fir into the predefined
1029              * error length buffer.
1030              */
1031             line = malloc(MAX_ERROR_LINE + 1);
1032             if (!line) {
1033                 TRACE_ERROR_NUMBER("Failed to get memory for error.", ENOMEM);
1034                 col_unbind_iterator(iterator);
1035                 ini_config_free_errors(errlist);
1036                 return ENOMEM;
1037             }
1038 
1039             snprintf(line, MAX_ERROR_LINE, LINE_FORMAT,
1040                      col_get_item_property(item, NULL),
1041                      pe->error,
1042                      pe->line,
1043                      ini_get_error_str(pe->error,
1044                                        INI_FAMILY_PARSING));
1045 
1046             errlist[count] = line;
1047             count++;
1048         }
1049 
1050     }
1051 
1052     /* Do not forget to unbind iterator - otherwise there will be a leak */
1053     col_unbind_iterator(iterator);
1054 
1055     *errors = errlist;
1056 
1057     TRACE_FLOW_EXIT();
1058     return error;
1059 }
1060 
ini_rules_read_from_file(const char * filename,struct ini_cfgobj ** _rules_obj)1061 int ini_rules_read_from_file(const char *filename,
1062                              struct ini_cfgobj **_rules_obj)
1063 {
1064     int ret;
1065     struct ini_cfgfile *cfgfile = NULL;
1066 
1067     if (_rules_obj == NULL) {
1068         return EINVAL;
1069     }
1070 
1071     ret = ini_config_create(_rules_obj);
1072     if (ret != EOK) {
1073         return ret;
1074     }
1075 
1076     ret = ini_config_file_open(filename, 0, &cfgfile);
1077     if (ret != EOK) {
1078         goto done;
1079     }
1080 
1081     ret = ini_config_parse(cfgfile, 0, INI_MV1S_ALLOW, 0, *_rules_obj);
1082     if (ret != EOK) {
1083         goto done;
1084     }
1085 
1086 done:
1087     if (ret != EOK) {
1088         ini_config_destroy(*_rules_obj);
1089         *_rules_obj = NULL;
1090     }
1091 
1092     ini_config_file_destroy(cfgfile);
1093     return ret;
1094 }
1095 
1096 /* This is used for testing only */
ini_dummy_noerror(const char * rule_name,struct ini_cfgobj * rules_obj,struct ini_cfgobj * config_obj,struct ini_errobj * errobj,void ** data)1097 static int ini_dummy_noerror(const char *rule_name,
1098                              struct ini_cfgobj *rules_obj,
1099                              struct ini_cfgobj *config_obj,
1100                              struct ini_errobj *errobj,
1101                              void **data)
1102 {
1103     return 0;
1104 }
1105 
1106 /* This is used for testing only */
ini_dummy_error(const char * rule_name,struct ini_cfgobj * rules_obj,struct ini_cfgobj * config_obj,struct ini_errobj * errobj,void ** data)1107 static int ini_dummy_error(const char *rule_name,
1108                            struct ini_cfgobj *rules_obj,
1109                            struct ini_cfgobj *config_obj,
1110                            struct ini_errobj *errobj,
1111                            void **data)
1112 {
1113     return ini_errobj_add_msg(errobj, "Error");
1114 }
1115 
is_allowed_section(const char * tested_section,char ** allowed_sections,size_t num_sec,regex_t * allowed_sections_re,size_t num_sec_re,int case_insensitive)1116 static int is_allowed_section(const char *tested_section,
1117                               char **allowed_sections,
1118                               size_t num_sec,
1119                               regex_t *allowed_sections_re,
1120                               size_t num_sec_re,
1121                               int case_insensitive)
1122 {
1123     int ret;
1124     int i;
1125 
1126     if (case_insensitive) {
1127         for (i = 0; i < num_sec; i++) {
1128             if (strcasecmp(tested_section, allowed_sections[i]) == 0) {
1129                 return 1;
1130             }
1131         }
1132     } else { /* case sensitive */
1133         for (i = 0; i < num_sec; i++) {
1134             if (strcmp(tested_section, allowed_sections[i]) == 0) {
1135                 return 1;
1136             }
1137         }
1138     }
1139 
1140     for (i = 0; i < num_sec_re; i++) {
1141         ret = regexec(&allowed_sections_re[i], tested_section, 0, NULL, 0);
1142         if (ret == 0) {
1143             return 1;
1144         }
1145     }
1146 
1147     return 0;
1148 }
1149 
ini_allowed_sections(const char * rule_name,struct ini_cfgobj * rules_obj,struct ini_cfgobj * config_obj,struct ini_errobj * errobj,void ** data)1150 static int ini_allowed_sections(const char *rule_name,
1151                                 struct ini_cfgobj *rules_obj,
1152                                 struct ini_cfgobj *config_obj,
1153                                 struct ini_errobj *errobj,
1154                                 void **data)
1155 {
1156     struct value_obj *vo = NULL;
1157     int ret;
1158     char *regex_str = NULL;
1159     char **allowed_sections = NULL;
1160     char *insensitive_str;
1161     char **cfg_sections = NULL;
1162     int num_cfg_sections;
1163     char **attributes = NULL;
1164     int num_attributes;
1165     size_t num_sec = 0;
1166     size_t num_sec_re = 0;
1167     regex_t *allowed_sections_re = NULL;
1168     size_t buf_size;
1169     int reg_err;
1170     int is_allowed;
1171     int case_insensitive = 0;
1172     int regcomp_flags = REG_NOSUB;
1173     int i;
1174 
1175     /* Get number of 'section' and 'section_re' attributes
1176      * in this rule */
1177     attributes = ini_get_attribute_list(rules_obj,
1178                                         rule_name,
1179                                         &num_attributes,
1180                                         NULL);
1181     if (attributes == NULL) {
1182         ret = ENOMEM;
1183         goto done;
1184     }
1185 
1186     for (i = 0; i < num_attributes; i++) {
1187         if (strcmp("section", attributes[i]) == 0) {
1188             num_sec++;
1189         }
1190 
1191         if (strcmp("section_re", attributes[i]) == 0) {
1192             num_sec_re++;
1193         }
1194     }
1195 
1196     ini_free_attribute_list(attributes);
1197 
1198     if (num_sec == 0 && num_sec_re == 0) {
1199         /* This rule is empty. */
1200         ret = ini_errobj_add_msg(errobj,
1201                                  "No allowed sections specified. "
1202                                  "Use 'section = default' to allow only "
1203                                  "default section");
1204         goto done;
1205     }
1206 
1207     ret = ini_get_config_valueobj(rule_name,
1208                                   "case_insensitive",
1209                                   rules_obj,
1210                                   INI_GET_NEXT_VALUE,
1211                                   &vo);
1212     if (ret) {
1213         goto done;
1214     }
1215 
1216     if (vo) {
1217         insensitive_str = ini_get_string_config_value(vo, &ret);
1218         if (ret) {
1219             goto done;
1220         }
1221 
1222         if (strcasecmp(insensitive_str, "yes") == 0
1223             || strcasecmp(insensitive_str, "true") == 0
1224             || strcmp(insensitive_str, "1") == 0) {
1225             case_insensitive = 1;
1226             regcomp_flags |= REG_ICASE;
1227         }
1228 
1229         free(insensitive_str);
1230     }
1231 
1232     /* Create arrays for section_re regexes and section name
1233      * strings. */
1234     allowed_sections = calloc(num_sec + 1, sizeof(char *));
1235     if (allowed_sections == NULL) {
1236         ret = ENOMEM;
1237         goto done;
1238     }
1239 
1240     allowed_sections_re = calloc(num_sec_re + 1, sizeof(regex_t));
1241     if (allowed_sections_re == NULL) {
1242         ret = ENOMEM;
1243         goto done;
1244     }
1245 
1246     /* Get all allowed section names and store them to
1247      * allowed_sections array */
1248     for (i = 0; i < num_sec; i++) {
1249         ret = ini_get_config_valueobj(rule_name,
1250                                       "section",
1251                                       rules_obj,
1252                                       INI_GET_NEXT_VALUE,
1253                                       &vo);
1254         if (ret) {
1255             goto done;
1256         }
1257 
1258         allowed_sections[i] = ini_get_string_config_value(vo, &ret);
1259         if (ret) {
1260             goto done;
1261         }
1262     }
1263 
1264     /* Get all regular section_re regular expresions and
1265      * store them to allowed_sections_re array */
1266     for (i = 0; i < num_sec_re; i++) {
1267         ret = ini_get_config_valueobj(rule_name,
1268                                       "section_re",
1269                                       rules_obj,
1270                                       INI_GET_NEXT_VALUE,
1271                                       &vo);
1272         if (ret) {
1273             goto done;
1274         }
1275 
1276         regex_str = ini_get_string_config_value(vo, &ret);
1277         if (ret) {
1278             goto done;
1279         }
1280 
1281         reg_err = regcomp(&allowed_sections_re[i], regex_str, regcomp_flags);
1282         if (reg_err) {
1283             char *err_str;
1284 
1285             buf_size = regerror(reg_err, &allowed_sections_re[i], NULL, 0);
1286             err_str = malloc(buf_size);
1287             if (err_str == NULL) {
1288                 ret = ENOMEM;
1289                 goto done;
1290             }
1291 
1292             regerror(reg_err, &allowed_sections_re[i], err_str, buf_size);
1293             ret = ini_errobj_add_msg(errobj,
1294                                      "Validator failed to use regex [%s]:[%s]",
1295                                      regex_str, err_str);
1296             free(err_str);
1297             ret = ret ? ret : EINVAL;
1298             goto done;
1299         }
1300         free(regex_str);
1301         regex_str = NULL;
1302     }
1303 
1304     /* Finally get list of all sections in configuration and
1305      * check if they are matched by some string in allowed_sections
1306      * or regex in allowed_sections_re */
1307     cfg_sections = ini_get_section_list(config_obj, &num_cfg_sections, &ret);
1308     if (ret != EOK) {
1309         goto done;
1310     }
1311 
1312     for (i = 0; i < num_cfg_sections; i++) {
1313         is_allowed = is_allowed_section(cfg_sections[i],
1314                                         allowed_sections,
1315                                         num_sec,
1316                                         allowed_sections_re,
1317                                         num_sec_re,
1318                                         case_insensitive);
1319         if (!is_allowed) {
1320             ret = ini_errobj_add_msg(errobj,
1321                                      "Section [%s] is not allowed. "
1322                                      "Check for typos.",
1323                                      cfg_sections[i]);
1324             if (ret) {
1325                 goto done;
1326             }
1327         }
1328     }
1329 
1330     ret = EOK;
1331 done:
1332     if (allowed_sections != NULL) {
1333         for (i = 0; allowed_sections[i] != NULL; i++) {
1334             free(allowed_sections[i]);
1335         }
1336         free(allowed_sections);
1337     }
1338     if (allowed_sections_re != NULL) {
1339         for (i = 0; i < num_sec_re; i++) {
1340             regfree(&allowed_sections_re[i]);
1341         }
1342         free(allowed_sections_re);
1343     }
1344     ini_free_section_list(cfg_sections);
1345     free(regex_str);
1346 
1347     return ret;
1348 }
1349 
check_if_allowed(char * section,char * attr,char ** allowed,int num_allowed,struct ini_errobj * errobj)1350 static int check_if_allowed(char *section, char *attr, char **allowed,
1351                             int num_allowed, struct ini_errobj *errobj)
1352 {
1353     int is_allowed = 0;
1354     int ret;
1355     int i;
1356 
1357     for (i = 0; i < num_allowed; i++) {
1358         if (strcmp(attr, allowed[i]) == 0) {
1359             is_allowed = 1;
1360             break;
1361         }
1362     }
1363 
1364     if (!is_allowed) {
1365         ret = ini_errobj_add_msg(errobj,
1366                                  "Attribute '%s' is not allowed in "
1367                                  "section '%s'. Check for typos.",
1368                                  attr, section);
1369         return ret;
1370     }
1371 
1372     return 0;
1373 }
1374 
ini_allowed_options(const char * rule_name,struct ini_cfgobj * rules_obj,struct ini_cfgobj * config_obj,struct ini_errobj * errobj,void ** data)1375 static int ini_allowed_options(const char *rule_name,
1376                                struct ini_cfgobj *rules_obj,
1377                                struct ini_cfgobj *config_obj,
1378                                struct ini_errobj *errobj,
1379                                void **data)
1380 {
1381     struct value_obj *vo = NULL;
1382     int ret;
1383     char *section_regex;
1384     int num_sections;
1385     char **sections = NULL;
1386     char **attributes = NULL;
1387     int num_attributes;
1388     int num_opts = 0;
1389     int i;
1390     int a;
1391     regex_t preg;
1392     size_t buf_size;
1393     char *err_str = NULL;
1394     int reg_err;
1395     char **allowed = NULL;
1396 
1397     /* Get section regex */
1398     ret = ini_get_config_valueobj(rule_name,
1399                                   "section_re",
1400                                   rules_obj,
1401                                   INI_GET_FIRST_VALUE,
1402                                   &vo);
1403     if (ret != 0) {
1404         return ret;
1405     }
1406 
1407     if (vo == NULL) {
1408         ret = ini_errobj_add_msg(errobj,
1409                                  "Validator misses 'section_re' parameter");
1410         if (ret) {
1411             return ret;
1412         }
1413         return EINVAL;
1414     }
1415 
1416     section_regex = ini_get_string_config_value(vo, NULL);
1417     if (section_regex == NULL || section_regex[0] == '\0') {
1418         ret = ini_errobj_add_msg(errobj,
1419                                  "Validator misses 'section_re' parameter");
1420         if (ret) {
1421             return ret;
1422         }
1423 
1424         free(section_regex);
1425         return EINVAL;
1426     }
1427 
1428     /* compile the regular expression */
1429     reg_err = regcomp(&preg, section_regex, REG_NOSUB);
1430     if (reg_err) {
1431         buf_size = regerror(reg_err, &preg, NULL, 0);
1432         err_str = malloc(buf_size);
1433         if (err_str == NULL) {
1434             ret = ENOMEM;
1435             goto done;
1436         }
1437 
1438         regerror(reg_err, &preg, err_str, buf_size);
1439         ret = ini_errobj_add_msg(errobj,
1440                                  "Cannot compile regular expression from "
1441                                  "option 'section_re'. Error: '%s'", err_str);
1442         ret = ret ? ret : EINVAL;
1443         goto done;
1444     }
1445 
1446     /* Get all sections from config_obj */
1447     sections = ini_get_section_list(config_obj, &num_sections, &ret);
1448     if (ret != EOK) {
1449         goto done;
1450     }
1451 
1452     /* Get number of 'option' attributes in this rule
1453      * and create an array long enough to store them all */
1454     attributes = ini_get_attribute_list(rules_obj,
1455                                         rule_name,
1456                                         &num_attributes,
1457                                         NULL);
1458     if (attributes == NULL) {
1459         ret = ENOMEM;
1460         goto done;
1461     }
1462 
1463     for (i = 0; i < num_attributes; i++) {
1464         if (strcmp("option", attributes[i]) == 0) {
1465             num_opts++;
1466         }
1467     }
1468 
1469     ini_free_attribute_list(attributes);
1470     attributes = NULL;
1471 
1472     allowed = calloc(num_opts + 1, sizeof(char *));
1473     if (allowed == NULL) {
1474         ret = ENOMEM;
1475         goto done;
1476     }
1477 
1478     for (i = 0; i < num_opts; i++) {
1479         ret = ini_get_config_valueobj(rule_name,
1480                                       "option",
1481                                       rules_obj,
1482                                       INI_GET_NEXT_VALUE,
1483                                       &vo);
1484         if (ret) {
1485             goto done;
1486         }
1487 
1488         allowed[i] = ini_get_string_config_value(vo, &ret);
1489         if (ret) {
1490             goto done;
1491         }
1492     }
1493 
1494     for (i = 0; i < num_sections; i++) {
1495         if (regexec(&preg, sections[i], 0, NULL, 0) == 0) {
1496             /* Regex matched section */
1497             /* Get options from this section */
1498             attributes = ini_get_attribute_list(config_obj,
1499                                                 sections[i],
1500                                                 &num_attributes,
1501                                                 NULL);
1502             if (attributes == NULL) {
1503                 ret = ENOMEM;
1504                 goto done;
1505             }
1506 
1507             for (a = 0; a < num_attributes; a++) {
1508                 ret = check_if_allowed(sections[i], attributes[a], allowed,
1509                                        num_opts, errobj);
1510                 if (ret != 0) {
1511                     goto done;
1512                 }
1513             }
1514             ini_free_attribute_list(attributes);
1515             attributes = NULL;
1516         }
1517     }
1518 
1519     ret = 0;
1520 done:
1521     if (allowed != NULL) {
1522         for (i = 0; allowed[i] != NULL; i++) {
1523             free(allowed[i]);
1524         }
1525         free(allowed);
1526     }
1527     ini_free_section_list(sections);
1528     free(section_regex);
1529     ini_free_attribute_list(attributes);
1530     regfree(&preg);
1531     free(err_str);
1532     return ret;
1533 }
1534 
1535 static ini_validator_func *
get_validator(char * validator_name,struct ini_validator ** validators)1536 get_validator(char *validator_name,
1537               struct ini_validator **validators)
1538 {
1539     struct ini_validator *ext_validator;
1540 
1541     /* First we check all internal validators */
1542     if (strcmp(validator_name, "ini_dummy_noerror") == 0) {
1543         return ini_dummy_noerror;
1544     } else if (strcmp(validator_name, "ini_dummy_error") == 0) {
1545         return ini_dummy_error;
1546     } else if (strcmp(validator_name, "ini_allowed_options") == 0) {
1547         return ini_allowed_options;
1548     } else if (strcmp(validator_name, "ini_allowed_sections") == 0) {
1549         return ini_allowed_sections;
1550     }
1551 
1552     if (validators == NULL) {
1553         return NULL;
1554     }
1555 
1556     for (; *validators != NULL; ++validators) {
1557         ext_validator = *validators;
1558 
1559         /* Skip invalid external validator. Name is required */
1560         if (ext_validator->name == NULL) {
1561             continue;
1562         }
1563         if (strcmp(validator_name, ext_validator->name) == 0) {
1564             return ext_validator->func;
1565         }
1566     }
1567 
1568     return NULL;
1569 }
1570 
ini_rules_check(struct ini_cfgobj * rules_obj,struct ini_cfgobj * config_obj,struct ini_validator ** extra_validators,struct ini_errobj * errobj)1571 int ini_rules_check(struct ini_cfgobj *rules_obj,
1572                     struct ini_cfgobj *config_obj,
1573                     struct ini_validator **extra_validators,
1574                     struct ini_errobj *errobj)
1575 {
1576     char **sections;
1577     int ret;
1578     int num_sections;
1579     char *vname;
1580     ini_validator_func *vfunc;
1581     struct value_obj *vo = NULL;
1582     struct ini_errobj *localerr = NULL;
1583     int i;
1584 
1585     /* Get all sections from the rules object */
1586     sections = ini_get_section_list(rules_obj, &num_sections, &ret);
1587     if (ret != EOK) {
1588         return ret;
1589     }
1590 
1591     /* Now iterate through all the sections. If the section
1592      * name begins with a prefix "rule/", then it is a rule
1593      * name. */
1594     for (i = 0; i < num_sections; i++) {
1595         if (!strncmp(sections[i], "rule/", sizeof("rule/") - 1)) {
1596             ret = ini_get_config_valueobj(sections[i],
1597                                           "validator",
1598                                           rules_obj,
1599                                           INI_GET_FIRST_VALUE,
1600                                           &vo);
1601             if (ret != 0) {
1602                 /* Failed to get value object. This should not
1603                  * happen. */
1604                 continue;
1605             }
1606 
1607             if (vo == NULL) {
1608                 ret = ini_errobj_add_msg(errobj,
1609                                          "Rule '%s' has no validator.",
1610                                          sections[i]);
1611                 if (ret != EOK) {
1612                     return ret;
1613                 }
1614                 /* Skip problematic rule */
1615                 continue;
1616             }
1617 
1618             vname = ini_get_string_config_value(vo, NULL);
1619             vfunc = get_validator(vname, extra_validators);
1620             if (vfunc == NULL) {
1621                 ret = ini_errobj_add_msg(errobj,
1622                                          "Rule '%s' uses unknown "
1623                                          "validator '%s'.",
1624                                          sections[i], vname);
1625                 if (ret != EOK) {
1626                     goto done;
1627                 }
1628                 /* Skip problematic rule */
1629                 free(vname);
1630                 continue;
1631             }
1632             free(vname);
1633 
1634             /* Do not pass global errobj to validators, they
1635              * could corrupt it. Create local one for each
1636              * validator. */
1637             ret = ini_errobj_create(&localerr);
1638             if (ret != EOK) {
1639                 goto done;
1640             }
1641 
1642             ret = vfunc(sections[i], rules_obj, config_obj, localerr, NULL);
1643             if (ret != 0) {
1644                 /* Just report the error and continue normally,
1645                  * maybe there are some errors in localerr */
1646                 ret = ini_errobj_add_msg(errobj,
1647                                          "Rule '%s' returned error code '%d'",
1648                                          sections[i], ret);
1649                 if (ret != EOK) {
1650                     goto done;
1651                 }
1652             }
1653 
1654             /* Bad validator could destroy the localerr, check
1655              * for NULL */
1656             if (localerr == NULL) {
1657                 continue;
1658             }
1659 
1660             ini_errobj_reset(localerr);
1661             while (!ini_errobj_no_more_msgs(localerr)) {
1662                 ret = ini_errobj_add_msg(errobj,
1663                                          "[%s]: %s",
1664                                          sections[i],
1665                                          ini_errobj_get_msg(localerr));
1666                 if (ret != EOK) {
1667                     goto done;
1668                 }
1669                 ini_errobj_next(localerr);
1670             }
1671 
1672             ini_errobj_destroy(&localerr);
1673         }
1674     }
1675 
1676     ret = EOK;
1677 done:
1678     ini_free_section_list(sections);
1679     ini_errobj_destroy(&localerr);
1680     return ret;
1681 }
1682 
1683 /* This is just convenience function, so that
1684  * we manipulate with ini_rules_* functions. */
ini_rules_destroy(struct ini_cfgobj * rules)1685 void ini_rules_destroy(struct ini_cfgobj *rules)
1686 {
1687     ini_config_destroy(rules);
1688 }
1689 
ini_errobj_create(struct ini_errobj ** _errobj)1690 int ini_errobj_create(struct ini_errobj **_errobj)
1691 {
1692     struct ini_errobj *new_errobj = NULL;
1693 
1694     if (_errobj == NULL) {
1695         return EINVAL;
1696     }
1697 
1698     new_errobj = calloc(1, sizeof(struct ini_errobj));
1699     if (new_errobj == NULL) {
1700         return ENOMEM;
1701     }
1702 
1703     *_errobj = new_errobj;
1704     return EOK;
1705 }
1706 
ini_errobj_destroy(struct ini_errobj ** errobj)1707 void ini_errobj_destroy(struct ini_errobj **errobj)
1708 {
1709     struct ini_errmsg *to_remove;
1710 
1711     if (errobj == NULL || *errobj == NULL) {
1712         return;
1713     }
1714 
1715     while ((*errobj)->first_msg) {
1716         to_remove = (*errobj)->first_msg;
1717         (*errobj)->first_msg = (*errobj)->first_msg->next;
1718         free(to_remove->str);
1719         free(to_remove);
1720     }
1721 
1722     free(*errobj);
1723     *errobj = NULL;
1724 }
1725 
ini_errobj_add_msg(struct ini_errobj * errobj,const char * format,...)1726 int ini_errobj_add_msg(struct ini_errobj *errobj, const char *format, ...)
1727 {
1728     int ret;
1729     va_list args;
1730     struct ini_errmsg *new;
1731 
1732     new = calloc(1, sizeof(struct ini_errmsg));
1733     if (new == NULL) {
1734         return ENOMEM;
1735     }
1736 
1737     va_start(args, format);
1738     ret = vasprintf(&new->str, format, args);
1739     va_end(args);
1740     if (ret == -1) {
1741         free(new);
1742         return ENOMEM;
1743     }
1744 
1745     if (errobj->count == 0) {
1746         /* First addition to the list, all pointers are NULL */
1747         errobj->first_msg = new;
1748         errobj->last_msg = new;
1749         errobj->cur_msg = new;
1750         errobj->count++;
1751     } else {
1752         errobj->last_msg->next = new;
1753         errobj->last_msg = errobj->last_msg->next;
1754         errobj->count++;
1755     }
1756 
1757     return EOK;
1758 }
1759 
ini_errobj_reset(struct ini_errobj * errobj)1760 void ini_errobj_reset(struct ini_errobj *errobj)
1761 {
1762     errobj->cur_msg = errobj->first_msg;
1763 }
1764 
ini_errobj_get_msg(struct ini_errobj * errobj)1765 const char *ini_errobj_get_msg(struct ini_errobj *errobj)
1766 {
1767     if (errobj->cur_msg != NULL) {
1768         return errobj->cur_msg->str;
1769     }
1770 
1771     /* Should this be allowed? */
1772     return NULL;
1773 }
1774 
ini_errobj_next(struct ini_errobj * errobj)1775 void ini_errobj_next(struct ini_errobj *errobj)
1776 {
1777     if (errobj->cur_msg != NULL) {
1778         errobj->cur_msg = errobj->cur_msg->next;
1779     }
1780 
1781     /* If we can not move next, just return */
1782     return;
1783 }
1784 
ini_errobj_no_more_msgs(struct ini_errobj * errobj)1785 int ini_errobj_no_more_msgs(struct ini_errobj *errobj)
1786 {
1787     return errobj->cur_msg == NULL;
1788 }
1789 
ini_errobj_count(struct ini_errobj * errobj)1790 size_t ini_errobj_count(struct ini_errobj *errobj)
1791 {
1792     return errobj->count;
1793 }
1794