Add agent groupings, fix the "incorrect" message on first login attempt
[asterisk/asterisk.git] / config.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Configuration File Parser
5  * 
6  * Copyright (C) 1999, Mark Spencer
7  *
8  * Mark Spencer <markster@linux-support.net>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
14 #include <stdio.h>
15 #include <unistd.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <errno.h>
19 #include <time.h>
20 #include <asterisk/config.h>
21 #include <asterisk/options.h>
22 #include <asterisk/logger.h>
23 #include "asterisk.h"
24 #include "astconf.h"
25
26 #define MAX_INCLUDE_LEVEL 10
27
28 struct ast_category {
29         char name[80];
30         struct ast_variable *root;
31         struct ast_category *next;
32 #ifdef PRESERVE_COMMENTS
33         struct ast_comment *precomments;
34         struct ast_comment *sameline;
35 #endif  
36 };
37
38 struct ast_config {
39         /* Maybe this structure isn't necessary but we'll keep it
40            for now */
41         struct ast_category *root;
42         struct ast_category *prev;
43 #ifdef PRESERVE_COMMENTS
44         struct ast_comment *trailingcomments;
45 #endif  
46 };
47
48 #ifdef PRESERVE_COMMENTS
49 struct ast_comment_struct
50 {
51         struct ast_comment *root;
52         struct ast_comment *prev;
53 };
54 #endif
55
56 static char *strip(char *buf)
57 {
58         char *start;
59         /* Strip off trailing whitespace, returns, etc */
60         while(strlen(buf) && (buf[strlen(buf)-1]<33))
61                 buf[strlen(buf)-1] = '\0';
62         start = buf;
63         /* Strip off leading whitespace, returns, etc */
64         while(*start && (*start < 33))
65                 *start++ = '\0';
66         return start;
67 }
68
69 #ifdef PRESERVE_COMMENTS
70 static void free_comments(struct ast_comment *com)
71 {
72         struct ast_comment *l;
73         while (com) {
74                 l = com;
75                 com = com->next;
76                 free(l);
77         }
78 }
79 #endif
80
81 void ast_destroy(struct ast_config *ast)
82 {
83         struct ast_category *cat, *catn;
84         struct ast_variable *v, *vn;
85
86         if (!ast)
87                 return;
88
89         cat = ast->root;
90         while(cat) {
91                 v = cat->root;
92                 while(v) {
93                         vn = v;
94                         free(v->name);
95                         free(v->value);
96 #ifdef PRESERVE_COMMENTS
97                         free_comments(v->precomments);
98                         free_comments(v->sameline);
99 #endif                  
100                         v = v->next;
101                         free(vn);
102                 }
103                 catn = cat;
104 #ifdef PRESERVE_COMMENTS
105                 free_comments(cat->precomments);
106                 free_comments(cat->sameline);
107 #endif          
108                 cat = cat->next;
109                 free(catn);
110         }
111 #ifdef PRESERVE_COMMENTS
112         free_comments(ast->trailingcomments);
113 #endif  
114         free(ast);
115 }
116
117 int ast_true(char *s)
118 {
119         if (!s)
120                 return 0;
121         /* Determine if this is a true value */
122         if (!strcasecmp(s, "yes") ||
123             !strcasecmp(s, "true") ||
124                 !strcasecmp(s, "y") ||
125                 !strcasecmp(s, "t") ||
126                 !strcasecmp(s, "1"))
127                         return -1;
128         return 0;
129 }
130
131 struct ast_variable *ast_variable_browse(struct ast_config *config, char *category)
132 {
133         struct ast_category *cat;
134         cat = config->root;
135         while(cat) {
136                 if (cat->name == category)
137                         return cat->root;
138                 cat = cat->next;
139         }
140         cat = config->root;
141         while(cat) {
142                 if (!strcasecmp(cat->name, category))
143                         return cat->root;
144                 cat = cat->next;
145         }
146         return NULL;
147 }
148
149 char *ast_variable_retrieve(struct ast_config *config, char *category, char *value)
150 {
151         struct ast_variable *v;
152         if (category) {
153                 v = ast_variable_browse(config, category);
154                 while (v) {
155                         if (value == v->name)
156                                 return v->value;
157                         v=v->next;
158                 }
159                 v = ast_variable_browse(config, category);
160                 while (v) {
161                         if (!strcasecmp(value, v->name))
162                                 return v->value;
163                         v=v->next;
164                 }
165         } else {
166                 struct ast_category *cat;
167                 cat = config->root;
168                 while(cat) {
169                         v = cat->root;
170                         while (v) {
171                                 if (!strcasecmp(value, v->name))
172                                         return v->value;
173                                 v=v->next;
174                         }
175                         cat = cat->next;
176                 }
177         }
178         return NULL;
179 }
180
181 #ifdef PRESERVE_COMMENTS
182 int ast_variable_delete(struct ast_config *cfg, char *category, char *variable, char *value)
183 {
184         struct ast_variable *v, *pv, *bv, *bpv;
185         struct ast_category *cat;
186         cat = cfg->root;
187         while(cat) {
188                 if (cat->name == category) {
189                         break;
190                 }
191                 cat = cat->next;
192         }
193         if (!cat) {
194                 cat = cfg->root;
195                 while(cat) {
196                         if (!strcasecmp(cat->name, category)) {
197                                 break;
198                         }
199                         cat = cat->next;
200                 }
201         }
202         if (!cat)
203                 return -1;
204         v = cat->root;
205         pv = NULL;
206         while (v) {
207                 if ((variable == v->name) && (!value || !strcmp(v->value, value)))
208                         break;
209                 pv = v;
210                 v=v->next;
211         }
212         if (!v) {
213                 /* Get the last one that looks like it */
214                 bv = NULL;
215                 bpv = NULL;
216                 v = cat->root;
217                 pv = NULL;
218                 while (v) {
219                         if (!strcasecmp(variable, v->name) && (!value || !strcmp(v->value, value))) {
220                                 bv = v;
221                                 bpv = pv;
222                         }
223                         pv = v;
224                         v=v->next;
225                 }
226                 v = bv;
227         }
228
229         if (v) {
230                 /* Unlink from original position */
231                 if (pv) 
232                         pv->next = v->next;
233                 else
234                         cat->root = v->next;
235                 v->next = NULL;
236                 free(v->name);
237                 if (v->value)
238                         free(v->value);
239                 free_comments(v->sameline);
240                 free_comments(v->precomments);
241                 return 0;
242         }
243         return -1;
244 }
245
246 int ast_category_delete(struct ast_config *cfg, char *category)
247 {
248         struct ast_variable *v, *pv;
249         struct ast_category *cat, *cprev;
250         cat = cfg->root;
251         cprev = NULL;
252         while(cat) {
253                 if (cat->name == category) {
254                         break;
255                 }
256                 cprev = cat;
257                 cat = cat->next;
258         }
259         if (!cat) {
260                 cat = cfg->root;
261                 cprev = NULL;
262                 while(cat) {
263                         if (!strcasecmp(cat->name, category)) {
264                                 break;
265                         }
266                         cprev = cat;
267                         cat = cat->next;
268                 }
269         }
270         if (!cat)
271                 return -1;
272         /* Unlink it */
273         if (cprev)
274                 cprev->next = cat->next;
275         else
276                 cfg->root = cat->next;
277         v = cat->root;
278         while (v) {
279                 pv = v;
280                 v=v->next;
281                 if (pv->value)
282                         free(pv->value);
283                 if (pv->name)
284                         free(pv->name);
285                 free_comments(pv->sameline);
286                 free_comments(pv->precomments);
287                 free(pv);
288         }
289         free_comments(cat->sameline);
290         free_comments(cat->precomments);
291         free(cat);
292         return 0;
293 }
294
295 struct ast_variable *ast_variable_append_modify(struct ast_config *config, char *category, char *variable, char *value, int newcat, int newvar, int move)
296 {
297         struct ast_variable *v, *pv=NULL, *bv, *bpv;
298         struct ast_category *cat, *pcat;
299         cat = config->root;
300         if (!newcat) {
301                 while(cat) {
302                         if (cat->name == category) {
303                                 break;
304                         }
305                         cat = cat->next;
306                 }
307                 if (!cat) {
308                         cat = config->root;
309                         while(cat) {
310                                 if (!strcasecmp(cat->name, category)) {
311                                         break;
312                                 }
313                                 cat = cat->next;
314                         }
315                 }
316         }
317         if (!cat) {
318                 cat = malloc(sizeof(struct ast_category));
319                 if (!cat)
320                         return NULL;
321                 memset(cat, 0, sizeof(struct ast_category));
322                 strncpy(cat->name, category, sizeof(cat->name));
323                 if (config->root) {
324                         /* Put us at the end */
325                         pcat = config->root;
326                         while(pcat->next)
327                                 pcat = pcat->next;
328                         pcat->next = cat;
329                 } else {
330                         /* We're the first one */
331                         config->root = cat;
332                 }
333                         
334         }
335         if (!newvar) {
336                 v = cat->root;
337                 pv = NULL;
338                 while (v) {
339                         if (variable == v->name)
340                                 break;
341                         pv = v;
342                         v=v->next;
343                 }
344                 if (!v) {
345                         /* Get the last one that looks like it */
346                         bv = NULL;
347                         bpv = NULL;
348                         v = cat->root;
349                         pv = NULL;
350                         while (v) {
351                                 if (!strcasecmp(variable, v->name)) {
352                                         bv = v;
353                                         bpv = pv;
354                                 }
355                                 pv = v;
356                                 v=v->next;
357                         }
358                         v = bv;
359                 }
360         } else v = NULL;
361         if (v && move) {
362                 /* Unlink from original position */
363                 if (pv) 
364                         pv->next = v->next;
365                 else
366                         cat->root = v->next;
367                 v->next = NULL;
368         }
369         if (!v) {
370                 v = malloc(sizeof(struct ast_variable));
371                 if (!v)
372                         return NULL;
373                 memset(v, 0, sizeof(struct ast_variable));
374                 v->name = strdup(variable);
375                 move = 1;
376         }
377         if (v->value)
378                 free(v->value);
379         if (value)
380                 v->value = strdup(value);
381         else
382                 v->value = strdup("");
383         if (move) {
384                 if (cat->root) {
385                         pv = cat->root;
386                         while (pv->next) 
387                                 pv = pv->next;
388                         pv->next = v;
389                 } else {
390                         cat->root = v;
391                 }
392         }
393         return v;
394 }
395 #endif          
396
397 int ast_category_exist(struct ast_config *config, char *category_name)
398 {
399         struct ast_category *category = NULL;
400
401         category = config->root;
402
403         while(category) {
404                 if (!strcasecmp(category->name,category_name)) 
405                         return 1;
406                 category = category->next;
407         } 
408
409         return 0;
410 }
411
412 #ifdef PRESERVE_COMMENTS
413 static struct ast_comment *build_comment(char *cmt)
414 {
415         struct ast_comment *c;
416         int len = strlen(cmt) + 1;
417         c = malloc(sizeof(struct ast_comment) + len);
418         if (c) {
419                 /* Memset the header */
420                 memset(c, 0, sizeof(struct ast_comment));
421                 /* Copy the rest */
422                 strcpy(c->cmt, cmt);
423         }
424         return c;
425 }
426 #endif
427
428 static struct ast_config *__ast_load(char *configfile, struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, int includelevel
429 #ifdef PRESERVE_COMMENTS
430 , struct ast_comment_struct *acs
431 #endif
432 );
433
434 static int cfg_process(struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, char *buf, int lineno, char *configfile, int includelevel 
435 #ifdef PRESERVE_COMMENTS
436 ,struct ast_comment_struct *acs
437 #endif
438 )
439 {
440         char *c;
441         char *cur;
442         struct ast_variable *v;
443 #ifdef PRESERVE_COMMENTS
444         struct ast_comment *com = NULL;
445 #endif  
446         int object;
447         /* Strip off lines using ; as comment */
448         c = strchr(buf, ';');
449         if (c) {
450                 *c = '\0';
451 #ifdef PRESERVE_COMMENTS
452                 c++;
453                 if (*c != '!')
454                         com = build_comment(c);
455 #endif                  
456         }
457         cur = strip(buf);
458         if (strlen(cur)) {
459                 /* Actually parse the entry */
460                 if (cur[0] == '[') {
461                         /* A category header */
462                         c = strchr(cur, ']');
463                         if (c) {
464                                 *c = 0;
465                                 *_tmpc = malloc(sizeof(struct ast_category));
466                                 if (!*_tmpc) {
467                                         ast_destroy(tmp);
468                                         ast_log(LOG_WARNING,
469                                                 "Out of memory, line %d\n", lineno);
470                                         return -1;
471                                 }
472                                 memset(*_tmpc, 0, sizeof(struct ast_category));
473                                 strncpy((*_tmpc)->name, cur+1, sizeof((*_tmpc)->name) - 1);
474                                 (*_tmpc)->root =  NULL;
475 #ifdef PRESERVE_COMMENTS
476                                 (*_tmpc)->precomments = acs->root;
477                                 (*_tmpc)->sameline = com;
478 #endif                          
479                                 if (!tmp->prev)
480                                         tmp->root = *_tmpc;
481                                 else
482                                         tmp->prev->next = *_tmpc;
483
484                                 tmp->prev = *_tmpc;
485 #ifdef PRESERVE_COMMENTS
486                                 acs->root = NULL;
487                                 acs->prev = NULL;
488 #endif                          
489                                 *_last =  NULL;
490                         } else {
491                                 ast_log(LOG_WARNING, 
492                                         "parse error: no closing ']', line %d of %s\n", lineno, configfile);
493                         }
494                 } else if (cur[0] == '#') {
495                         /* A directive */
496                         cur++;
497                         c = cur;
498                         while(*c && (*c > 32)) c++;
499                         if (*c) {
500                                 *c = '\0';
501                                 c++;
502                                 /* Find real argument */
503                                 while(*c  && (*c < 33)) c++;
504                                 if (!*c)
505                                         c = NULL;
506                         } else 
507                                 c = NULL;
508                         if (!strcasecmp(cur, "include")) {
509                                 /* A #include */
510                                 if (c) {
511                                         while((*c == '<') || (*c == '>') || (*c == '\"')) c++;
512                                         /* Get rid of leading mess */
513                                         cur = c;
514                                         while(strlen(cur)) {
515                                                 c = cur + strlen(cur) - 1;
516                                                 if ((*c == '>') || (*c == '<') || (*c == '\"'))
517                                                         *c = '\0';
518                                                 else
519                                                         break;
520                                         }
521                                         if (includelevel < MAX_INCLUDE_LEVEL) {
522                                                 __ast_load(cur, tmp, _tmpc, _last, includelevel + 1
523 #ifdef PRESERVE_COMMENTS
524                                                 ,acs
525 #endif
526                                                 );
527                                         } else 
528                                                 ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", includelevel);
529                                 } else
530                                         ast_log(LOG_WARNING, "Directive '#include' needs an argument (filename) at line %d of %s\n", lineno, configfile);
531                                 /* Strip off leading and trailing "'s and <>'s */
532                         } else 
533                                 ast_log(LOG_WARNING, "Unknown directive '%s' at line %d of %s\n", cur, lineno, configfile);
534                 } else {
535                         /* Just a line (variable = value) */
536                         if (!*_tmpc) {
537                                 ast_log(LOG_WARNING,
538                                         "parse error: No category context for line %d of %s\n", lineno, configfile);
539                                 ast_destroy(tmp);
540                                 return -1;
541                         }
542                         c = strchr(cur, '=');
543                         if (c) {
544                                 *c = 0;
545                                 c++;
546                                 /* Ignore > in => */
547                                 if (*c== '>') {
548                                         object = 1;
549                                         c++;
550                                 } else
551                                         object = 0;
552                                 v = malloc(sizeof(struct ast_variable));
553                                 if (v) {
554                                         memset(v, 0, sizeof(struct ast_variable));
555                                         v->next = NULL;
556                                         v->name = strdup(strip(cur));
557                                         v->value = strdup(strip(c));
558                                         v->lineno = lineno;
559                                         v->object = object;
560                                         /* Put and reset comments */
561 #ifdef PRESERVE_COMMENTS
562                                         v->precomments = acs->root;
563                                         v->sameline = com;
564                                         acs->prev = NULL;
565                                         acs->root = NULL;
566 #endif                                  
567                                         v->blanklines = 0;
568                                         if (*_last)
569                                                 (*_last)->next = v;
570                                         else
571                                                 (*_tmpc)->root = v;
572                                         *_last = v;
573                                 } else {
574                                         ast_destroy(tmp);
575                                         ast_log(LOG_WARNING, "Out of memory, line %d\n", lineno);
576                                         return -1;
577                                 }
578                         } else {
579                                 ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
580                         }
581                                                                                                                 
582                 }
583         } else {
584                 /* store any comments if there are any */
585 #ifdef PRESERVE_COMMENTS
586                 if (com) {
587                         if (acs->prev)
588                                 acs->prev->next = com;
589                         else
590                                 acs->root = com;
591                         acs->prev = com;
592                 } else {
593                 if (*_last) 
594                         (*_last)->blanklines++;
595
596                 }
597 #endif
598         }
599         return 0;
600 }
601
602 #ifdef PRESERVE_COMMENTS
603 static void dump_comments(FILE *f, struct ast_comment *comment)
604 {
605         while (comment) {
606                 fprintf(f, ";%s", comment->cmt);
607                 comment = comment->next;
608         }
609 }
610 #endif
611
612 int ast_save(char *configfile, struct ast_config *cfg, char *generator)
613 {
614         FILE *f;
615         char fn[256];
616         char date[256];
617         time_t t;
618         struct ast_variable *var;
619         struct ast_category *cat;
620         int blanklines = 0;
621         if (configfile[0] == '/') {
622                 strncpy(fn, configfile, sizeof(fn)-1);
623         } else {
624                 snprintf(fn, sizeof(fn), "%s/%s", AST_CONFIG_DIR, configfile);
625         }
626         time(&t);
627         strncpy(date, ctime(&t), sizeof(date));
628         if ((f = fopen(fn, "w"))) {
629                 if ((option_verbose > 1) && !option_debug)
630                         ast_verbose(  VERBOSE_PREFIX_2 "Saving '%s': ", fn);
631                 fprintf(f, ";!\n");
632                 fprintf(f, ";! Automatically generated configuration file\n");
633                 fprintf(f, ";! Filename: %s (%s)\n", configfile, fn);
634                 fprintf(f, ";! Generator: %s\n", generator);
635                 fprintf(f, ";! Creation Date: %s", date);
636                 fprintf(f, ";!\n");
637                 cat = cfg->root;
638                 while(cat) {
639 #ifdef PRESERVE_COMMENTS
640                         /* Dump any precomments */
641                         dump_comments(f, cat->precomments);
642 #endif
643                         /* Dump section with any appropriate comment */
644 #ifdef PRESERVE_COMMENTS
645                         if (cat->sameline) 
646                                 fprintf(f, "[%s]  ; %s\n", cat->name, cat->sameline->cmt);
647                         else
648 #endif
649                                 fprintf(f, "[%s]\n", cat->name);
650                         var = cat->root;
651                         while(var) {
652 #ifdef PRESERVE_COMMENTS
653                                 dump_comments(f, var->precomments);
654 #endif                          
655                                 if (var->sameline) 
656                                         fprintf(f, "%s %s %s  ; %s\n", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
657                                 else    
658                                         fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
659                                 if (var->blanklines) {
660                                         blanklines = var->blanklines;
661                                         while (blanklines) {
662                                                 fprintf(f, "\n");
663                                                 blanklines--;
664                                         }
665                                 }
666                                         
667                                 var = var->next;
668                         }
669 #if 0
670                         /* Put an empty line */
671                         fprintf(f, "\n");
672 #endif
673                         cat = cat->next;
674                 }
675 #ifdef PRESERVE_COMMENTS
676                 dump_comments(f, cfg->trailingcomments);
677 #endif          
678         } else {
679                 if (option_debug)
680                         printf("Unable to open for writing: %s\n", fn);
681                 else if (option_verbose > 1)
682                         printf( "Unable to write (%s)", strerror(errno));
683                 return -1;
684         }
685         fclose(f);
686         return 0;
687 }
688
689 static struct ast_config *__ast_load(char *configfile, struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, int includelevel
690 #ifdef PRESERVE_COMMENTS
691 , struct ast_comment_struct *acs
692 #endif
693 )
694 {
695         char fn[256];
696         char buf[512];
697         FILE *f;
698         int lineno=0;
699         int master=0;
700
701         if (configfile[0] == '/') {
702                 strncpy(fn, configfile, sizeof(fn)-1);
703         } else {
704                 snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, configfile);
705         }
706         if ((option_verbose > 1) && !option_debug) {
707                 ast_verbose(  VERBOSE_PREFIX_2 "Parsing '%s': ", fn);
708                 fflush(stdout);
709         }
710         if ((f = fopen(fn, "r"))) {
711                 if (option_debug)
712                         ast_log(LOG_DEBUG, "Parsing %s\n", fn);
713                 else if (option_verbose > 1)
714                         ast_verbose( "Found\n");
715                 if (!tmp) {
716                         tmp = malloc(sizeof(struct ast_config));
717                         if (tmp)
718                                 memset(tmp, 0, sizeof(struct ast_config));
719
720                         master = 1;
721                 }
722                 if (!tmp) {
723                         ast_log(LOG_WARNING, "Out of memory\n");
724                         fclose(f);
725                         return NULL;
726                 }
727                 while(!feof(f)) {
728                         fgets(buf, sizeof(buf), f);
729                         lineno++;
730                         if (!feof(f)) {
731                                 if (cfg_process(tmp, _tmpc, _last, buf, lineno, configfile, includelevel
732 #ifdef PRESERVE_COMMENTS
733                                 , acs
734 #endif
735                                 )) {
736                                         fclose(f);
737                                         return NULL;
738                                 }
739                         }
740                 }
741                 fclose(f);              
742         } else {
743                 if (option_debug)
744                         ast_log(LOG_DEBUG, "No file to parse: %s\n", fn);
745                 else if (option_verbose > 1)
746                         ast_verbose( "Not found (%s)\n", strerror(errno));
747         }
748 #ifdef PRESERVE_COMMENTS
749         if (master) {
750                 /* Keep trailing comments */
751                 tmp->trailingcomments = acs->root;
752                 acs->root = NULL;
753                 acs->prev = NULL;
754         }
755 #endif
756         return tmp;
757 }
758
759 struct ast_config *ast_load(char *configfile)
760 {
761         struct ast_category *tmpc=NULL;
762         struct ast_variable *last = NULL;
763 #ifdef PRESERVE_COMMENTS
764         struct ast_comment_struct acs = { NULL, NULL };
765 #endif  
766         return __ast_load(configfile, NULL, &tmpc, &last, 0 
767 #ifdef PRESERVE_COMMENTS
768         ,&acs
769 #endif
770         );
771 }
772
773 char *ast_category_browse(struct ast_config *config, char *prev)
774 {       
775         struct ast_category *cat;
776         if (!prev) {
777                 if (config->root)
778                         return config->root->name;
779                 else
780                         return NULL;
781         }
782         cat = config->root;
783         while(cat) {
784                 if (cat->name == prev) {
785                         if (cat->next)
786                                 return cat->next->name;
787                         else
788                                 return NULL;
789                 }
790                 cat = cat->next;
791         }
792         cat = config->root;
793         while(cat) {
794                 if (!strcasecmp(cat->name, prev)) {
795                         if (cat->next)
796                                 return cat->next->name;
797                         else
798                                 return NULL;
799                 }
800                 cat = cat->next;
801         }
802         return NULL;
803 }