Skip to content
Snippets Groups Projects
extconf.c 164 KiB
Newer Older
  • Learn to ignore specific revisions
  • 			do_exec = 0;
    		if (do_exec && !ast_opt_exec_includes) {
    			ast_log(LOG_WARNING, "Cannot perform #exec unless execincludes option is enabled in asterisk.conf (options section)!\n");
    			do_exec = 0;
    		}
    		if (do_include || do_exec) {
    			if (c) {
    
    George Joseph's avatar
    George Joseph committed
    				char real_inclusion_name[525];
    
    				/* Strip off leading and trailing "'s and <>'s */
    				while((*c == '<') || (*c == '>') || (*c == '\"')) c++;
    				/* Get rid of leading mess */
    				cur = c;
    
    				while (!ast_strlen_zero(cur)) {
    					c = cur + strlen(cur) - 1;
    					if ((*c == '>') || (*c == '<') || (*c == '\"'))
    						*c = '\0';
    					else
    						break;
    				}
    				/* #exec </path/to/executable>
    				   We create a tmp file, then we #include it, then we delete it. */
    
    Corey Farrell's avatar
    Corey Farrell committed
    					char cmd[1024];
    
    
    					snprintf(exec_file, sizeof(exec_file), "/var/tmp/exec.%d.%ld", (int)time(NULL), (long)pthread_self());
    
    Corey Farrell's avatar
    Corey Farrell committed
    					if (snprintf(cmd, sizeof(cmd), "%s > %s 2>&1", cur, exec_file) >= sizeof(cmd)) {
    						ast_log(LOG_ERROR, "Failed to construct command string to execute %s.\n", cur);
    
    						return -1;
    					}
    
    					ast_safe_system(cmd);
    					cur = exec_file;
    				} else
    					exec_file[0] = '\0';
    				/* A #include */
    				/* ast_log(LOG_WARNING, "Reading in included file %s withcomments=%d\n", cur, withcomments); */
    
    				ast_include_new(cfg, configfile, cur, do_exec, cur2, lineno, real_inclusion_name, sizeof(real_inclusion_name));
    
    				do_include = ast_config_internal_load(cur, cfg, withcomments, real_inclusion_name) ? 1 : 0;
    
    				if(!ast_strlen_zero(exec_file))
    					unlink(exec_file);
    				if(!do_include)
    					return 0;
    				/* ast_log(LOG_WARNING, "Done reading in included file %s withcomments=%d\n", cur, withcomments); */
    
    				ast_log(LOG_WARNING, "Directive '#%s' needs an argument (%s) at line %d of %s\n",
    
    						do_exec ? "exec" : "include",
    						do_exec ? "/path/to/executable" : "filename",
    						lineno,
    						configfile);
    			}
    		}
    
    			ast_log(LOG_WARNING, "Unknown directive '%s' at line %d of %s\n", cur, lineno, configfile);
    	} else {
    		/* Just a line (variable = value) */
    		if (!*cat) {
    			ast_log(LOG_WARNING,
    				"parse error: No category context for line %d of %s\n", lineno, configfile);
    			return -1;
    		}
    		c = strchr(cur, '=');
    		if (c) {
    			*c = 0;
    			c++;
    			/* Ignore > in => */
    			if (*c== '>') {
    				object = 1;
    				c++;
    			} else
    				object = 0;
    
    			if ((v = ast_variable_new(ast_strip(cur), ast_strip(c), configfile))) {
    
    				v->lineno = lineno;
    				v->object = object;
    				/* Put and reset comments */
    				v->blanklines = 0;
    				ast_variable_append(*cat, v);
    				/* add comments */
    				if (withcomments && comment_buffer && comment_buffer[0] ) {
    					v->precomments = ALLOC_COMMENT(comment_buffer);
    				}
    				if (withcomments && lline_buffer && lline_buffer[0] ) {
    					v->sameline = ALLOC_COMMENT(lline_buffer);
    				}
    				if( withcomments )
    					CB_RESET();
    
    			} else {
    				return -1;
    			}
    		} else {
    			ast_log(LOG_WARNING, "EXTENSIONS.CONF: No '=' (equal sign) in line %d of %s\n", lineno, configfile);
    		}
    	}
    	return 0;
    }
    
    static int use_local_dir = 1;
    
    void localized_use_local_dir(void);
    void localized_use_conf_dir(void);
    
    void localized_use_local_dir(void)
    {
    	use_local_dir = 1;
    }
    
    void localized_use_conf_dir(void)
    {
    	use_local_dir = 0;
    }
    
    
    
    static struct ast_config *config_text_file_load(const char *database, const char *table, const char *filename, struct ast_config *cfg, int withcomments, const char *suggested_include_file)
    
    {
    	char fn[256];
    	char buf[8192];
    	char *new_buf, *comment_p, *process_buf;
    	FILE *f;
    	int lineno=0;
    	int comment = 0, nest[MAX_NESTED_COMMENTS];
    	struct ast_category *cat = NULL;
    	int count = 0;
    	struct stat statbuf;
    
    	cat = ast_config_get_current_category(cfg);
    
    	if (filename[0] == '/') {
    		ast_copy_string(fn, filename, sizeof(fn));
    	} else {
    		if (use_local_dir)
    			snprintf(fn, sizeof(fn), "./%s", filename);
    		else
    
    			snprintf(fn, sizeof(fn), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
    
    	}
    
    	if (withcomments && cfg && cfg->include_level < 2 ) {
    		CB_INIT();
    	}
    
    	do {
    		if (stat(fn, &statbuf))
    			continue;
    
    		if (!S_ISREG(statbuf.st_mode)) {
    			ast_log(LOG_WARNING, "'%s' is not a regular file, ignoring\n", fn);
    			continue;
    		}
    		if (option_verbose > 1) {
    			ast_verbose(VERBOSE_PREFIX_2 "Parsing '%s': ", fn);
    			fflush(stdout);
    		}
    		if (!(f = fopen(fn, "r"))) {
    
    			if (option_debug)
    				ast_log(LOG_DEBUG, "No file to parse: %s\n", fn);
    
    			if (option_verbose > 1)
    				ast_verbose( "Not found (%s)\n", strerror(errno));
    			continue;
    		}
    		count++;
    
    		if (option_debug)
    			ast_log(LOG_DEBUG, "Parsing %s\n", fn);
    
    		if (option_verbose > 1)
    			ast_verbose("Found\n");
    		while(!feof(f)) {
    			lineno++;
    			if (fgets(buf, sizeof(buf), f)) {
    
    				if ( withcomments ) {
    
    					CB_ADD(lline_buffer);       /* add the current lline buffer to the comment buffer */
    					lline_buffer[0] = 0;        /* erase the lline buffer */
    				}
    
    				while ((comment_p = strchr(new_buf, COMMENT_META))) {
    					if ((comment_p > new_buf) && (*(comment_p-1) == '\\')) {
    						/* Yuck, gotta memmove */
    						memmove(comment_p - 1, comment_p, strlen(comment_p) + 1);
    						new_buf = comment_p;
    					} else if(comment_p[1] == COMMENT_TAG && comment_p[2] == COMMENT_TAG && (comment_p[3] != '-')) {
    						/* Meta-Comment start detected ";--" */
    						if (comment < MAX_NESTED_COMMENTS) {
    							*comment_p = '\0';
    							new_buf = comment_p + 3;
    							comment++;
    							nest[comment-1] = lineno;
    						} else {
    							ast_log(LOG_ERROR, "Maximum nest limit of %d reached.\n", MAX_NESTED_COMMENTS);
    						}
    					} else if ((comment_p >= new_buf + 2) &&
    						   (*(comment_p - 1) == COMMENT_TAG) &&
    						   (*(comment_p - 2) == COMMENT_TAG)) {
    						/* Meta-Comment end detected */
    						comment--;
    						new_buf = comment_p + 1;
    						if (!comment) {
    							/* Back to non-comment now */
    							if (process_buf) {
    								/* Actually have to move what's left over the top, then continue */
    								char *oldptr;
    								oldptr = process_buf + strlen(process_buf);
    								if ( withcomments ) {
    									CB_ADD(";");
    									CB_ADD_LEN(oldptr+1,new_buf-oldptr-1);
    								}
    
    								memmove(oldptr, new_buf, strlen(new_buf) + 1);
    								new_buf = oldptr;
    							} else
    								process_buf = new_buf;
    						}
    					} else {
    						if (!comment) {
    
    							/* If ; is found, and we are not nested in a comment,
    
    							   we immediately stop all comment processing */
    							if ( withcomments ) {
    								LLB_ADD(comment_p);
    							}
    
    							new_buf = comment_p;
    						} else
    							new_buf = comment_p + 1;
    					}
    				}
    				if( withcomments && comment && !process_buf )
    				{
    					CB_ADD(buf);  /* the whole line is a comment, store it */
    				}
    
    					char *stripped_process_buf = ast_strip(process_buf);
    					if (!ast_strlen_zero(stripped_process_buf)) {
    						if (process_text_line(cfg, &cat, stripped_process_buf, lineno, filename, withcomments, suggested_include_file)) {
    
    	} while(0);
    	if (comment) {
    		ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment]);
    	}
    	if (cfg && cfg->include_level == 1 && withcomments && comment_buffer) {
    
    		if (comment_buffer) {
    
    			free(comment_buffer);
    			free(lline_buffer);
    
    			comment_buffer=0;
    			lline_buffer=0;
    			comment_buffer_size=0;
    
    			lline_buffer_size=0;
    		}
    	}
    	if (count == 0)
    		return NULL;
    
    	return cfg;
    }
    
    
    static struct ast_config *ast_config_new(void) ;
    
    
    static struct ast_config *ast_config_new(void)
    
    {
    	struct ast_config *config;
    
    	if ((config = ast_calloc(1, sizeof(*config))))
    		config->max_include_level = MAX_INCLUDE_LEVEL;
    	return config;
    }
    
    struct ast_config *localized_config_load(const char *filename);
    
    struct ast_config *localized_config_load(const char *filename)
    {
    	struct ast_config *cfg;
    	struct ast_config *result;
    
    	cfg = ast_config_new();
    	if (!cfg)
    		return NULL;
    
    
    	result = ast_config_internal_load(filename, cfg, 0, "");
    
    	if (!result)
    		ast_config_destroy(cfg);
    
    	return result;
    }
    
    struct ast_config *localized_config_load_with_comments(const char *filename);
    
    struct ast_config *localized_config_load_with_comments(const char *filename)
    {
    	struct ast_config *cfg;
    	struct ast_config *result;
    
    	cfg = ast_config_new();
    	if (!cfg)
    		return NULL;
    
    
    	result = ast_config_internal_load(filename, cfg, 1, "");
    
    	if (!result)
    		ast_config_destroy(cfg);
    
    	return result;
    }
    
    static struct ast_category *next_available_category(struct ast_category *cat)
    {
    	for (; cat && cat->ignored; cat = cat->next);
    
    	return cat;
    }
    
    static char *ast_category_browse(struct ast_config *config, const char *prev)
    
    	struct ast_category *cat = NULL;
    
    	if (prev && config->last_browse && (config->last_browse->name == prev))
    		cat = config->last_browse->next;
    	else if (!prev && config->root)
    		cat = config->root;
    	else if (prev) {
    		for (cat = config->root; cat; cat = cat->next) {
    			if (cat->name == prev) {
    				cat = cat->next;
    				break;
    			}
    		}
    		if (!cat) {
    			for (cat = config->root; cat; cat = cat->next) {
    				if (!strcasecmp(cat->name, prev)) {
    					cat = cat->next;
    					break;
    				}
    			}
    		}
    	}
    
    	if (cat)
    		cat = next_available_category(cat);
    
    	config->last_browse = cat;
    	return (cat) ? cat->name : NULL;
    }
    
    
    
    void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat);
    
    void ast_config_set_current_category(struct ast_config *cfg, const struct ast_category *cat)
    {
    	/* cast below is just to silence compiler warning about dropping "const" */
    	cfg->current = (struct ast_category *) cat;
    }
    
    
    /* NOTE: categories and variables each have a file and lineno attribute. On a save operation, these are used to determine
       which file and line number to write out to. Thus, an entire hierarchy of config files (via #include statements) can be
       recreated. BUT, care must be taken to make sure that every cat and var has the proper file name stored, or you may
    
       be shocked and mystified as to why things are not showing up in the files!
    
    
       Also, All #include/#exec statements are recorded in the "includes" LL in the ast_config structure. The file name
       and line number are stored for each include, plus the name of the file included, so that these statements may be
    
       included in the output files on a file_save operation.
    
    
       The lineno's are really just for relative placement in the file. There is no attempt to make sure that blank lines
       are included to keep the lineno's the same between input and output. The lineno fields are used mainly to determine
       the position of the #include and #exec directives. So, blank lines tend to disappear from a read/rewrite operation,
       and a header gets added.
    
       vars and category heads are output in the order they are stored in the config file. So, if the software
       shuffles these at all, then the placement of #include directives might get a little mixed up, because the
       file/lineno data probably won't get changed.
    
    */
    
    static void gen_header(FILE *f1, const char *configfile, const char *fn, const char *generator)
    {
    	char date[256]="";
    	time_t t;
    	time(&t);
    	ast_copy_string(date, ctime(&t), sizeof(date));
    
    	fprintf(f1, ";!\n");
    	fprintf(f1, ";! Automatically generated configuration file\n");
    	if (strcmp(configfile, fn))
    		fprintf(f1, ";! Filename: %s (%s)\n", configfile, fn);
    	else
    		fprintf(f1, ";! Filename: %s\n", configfile);
    	fprintf(f1, ";! Generator: %s\n", generator);
    	fprintf(f1, ";! Creation Date: %s", date);
    	fprintf(f1, ";!\n");
    }
    
    static void set_fn(char *fn, int fn_size, const char *file, const char *configfile)
    {
    	if (!file || file[0] == 0) {
    		if (configfile[0] == '/')
    			ast_copy_string(fn, configfile, fn_size);
    		else
    			snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, configfile);
    
    	} else if (file[0] == '/')
    
    		ast_copy_string(fn, file, fn_size);
    	else
    		snprintf(fn, fn_size, "%s/%s", ast_config_AST_CONFIG_DIR, file);
    }
    
    
    int localized_config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator);
    
    int localized_config_text_file_save(const char *configfile, const struct ast_config *cfg, const char *generator)
    {
    	FILE *f;
    	char fn[256];
    	struct ast_variable *var;
    	struct ast_category *cat;
    	struct ast_comment *cmt;
    
    	/* reset all the output flags, in case this isn't our first time saving this data */
    
    	for (incl=cfg->includes; incl; incl = incl->next)
    		incl->output = 0;
    
    	/* go thru all the inclusions and make sure all the files involved (configfile plus all its inclusions)
    	   are all truncated to zero bytes and have that nice header*/
    
    	for (incl=cfg->includes; incl; incl = incl->next)
    	{
    		if (!incl->exec) { /* leave the execs alone -- we'll write out the #exec directives, but won't zero out the include files or exec files*/
    			FILE *f1;
    
    			set_fn(fn, sizeof(fn), incl->included_file, configfile); /* normally, fn is just set to incl->included_file, prepended with config dir if relative */
    			f1 = fopen(fn,"w");
    			if (f1) {
    				gen_header(f1, configfile, fn, generator);
    				fclose(f1); /* this should zero out the file */
    			} else {
    				ast_verbose(VERBOSE_PREFIX_2 "Unable to write %s (%s)", fn, strerror(errno));
    			}
    		}
    
    	set_fn(fn, sizeof(fn), 0, configfile); /* just set fn to absolute ver of configfile */
    
    	if ((f = fopen(fn, "w+"))) {
    #else
    	if ((f = fopen(fn, "w"))) {
    
    		if (option_verbose > 1)
    			ast_verbose(VERBOSE_PREFIX_2 "Saving '%s': ", fn);
    
    		/* from here out, we open each involved file and concat the stuff we need to add to the end and immediately close... */
    
    		/* since each var, cat, and associated comments can come from any file, we have to be
    
    		   mobile, and open each file, print, and close it on an entry-by-entry basis */
    
    			set_fn(fn, sizeof(fn), cat->file, configfile);
    			f = fopen(fn, "a");
    			if (!f)
    
    				ast_verbose(VERBOSE_PREFIX_2 "Unable to write %s (%s)", fn, strerror(errno));
    				return -1;
    			}
    
    			/* dump any includes that happen before this category header */
    			for (incl=cfg->includes; incl; incl = incl->next) {
    				if (strcmp(incl->include_location_file, cat->file) == 0){
    					if (cat->lineno > incl->include_location_lineno && !incl->output) {
    						if (incl->exec)
    							fprintf(f,"#exec \"%s\"\n", incl->exec_file);
    						else
    							fprintf(f,"#include \"%s\"\n", incl->included_file);
    						incl->output = 1;
    					}
    				}
    			}
    
    			/* Dump section with any appropriate comment */
    			for (cmt = cat->precomments; cmt; cmt=cmt->next) {
    
    				if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
    					fprintf(f,"%s", cmt->cmt);
    			}
    			if (!cat->precomments)
    				fprintf(f,"\n");
    			fprintf(f, "[%s]", cat->name);
    
    			for(cmt = cat->sameline; cmt; cmt=cmt->next) {
    
    				fprintf(f,"%s", cmt->cmt);
    			}
    			if (!cat->sameline)
    				fprintf(f,"\n");
    
    				set_fn(fn, sizeof(fn), var->file, configfile);
    				f = fopen(fn, "a");
    				if (!f)
    
    					ast_verbose(VERBOSE_PREFIX_2 "Unable to write %s (%s)", fn, strerror(errno));
    					return -1;
    				}
    
    				/* dump any includes that happen before this category header */
    				for (incl=cfg->includes; incl; incl = incl->next) {
    					if (strcmp(incl->include_location_file, var->file) == 0){
    						if (var->lineno > incl->include_location_lineno && !incl->output) {
    							if (incl->exec)
    								fprintf(f,"#exec \"%s\"\n", incl->exec_file);
    							else
    								fprintf(f,"#include \"%s\"\n", incl->included_file);
    							incl->output = 1;
    						}
    					}
    				}
    
    				for (cmt = var->precomments; cmt; cmt=cmt->next) {
    
    					if (cmt->cmt[0] != ';' || cmt->cmt[1] != '!')
    						fprintf(f,"%s", cmt->cmt);
    				}
    
    					fprintf(f, "%s %s %s  %s", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
    
    					fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
    				if (var->blanklines) {
    					blanklines = var->blanklines;
    					while (blanklines--)
    						fprintf(f, "\n");
    				}
    
    				var = var->next;
    			}
    			cat = cat->next;
    		}
    		if ((option_verbose > 1) && !option_debug)
    			ast_verbose("Saved\n");
    	} else {
    
    		if (option_debug)
    			ast_log(LOG_DEBUG, "Unable to open for writing: %s\n", fn);
    
    		if (option_verbose > 1)
    			ast_verbose(VERBOSE_PREFIX_2 "Unable to write (%s)", strerror(errno));
    		return -1;
    	}
    
    
    	/* Now, for files with trailing #include/#exec statements,
    	   we have to make sure every entry is output */
    
    	for (incl=cfg->includes; incl; incl = incl->next) {
    		if (!incl->output) {
    			/* open the respective file */
    			set_fn(fn, sizeof(fn), incl->include_location_file, configfile);
    			f = fopen(fn, "a");
    			if (!f)
    			{
    				ast_verbose(VERBOSE_PREFIX_2 "Unable to write %s (%s)", fn, strerror(errno));
    				return -1;
    			}
    
    			/* output the respective include */
    			if (incl->exec)
    				fprintf(f,"#exec \"%s\"\n", incl->exec_file);
    			else
    				fprintf(f,"#include \"%s\"\n", incl->included_file);
    			fclose(f);
    			incl->output = 1;
    		}
    	}
    
    	return 0;
    }
    
    /* ================ the Line ========================================
       above this line, you have what you need to load a config file,
       and below it, you have what you need to process the extensions.conf
       file into the context/exten/prio stuff. They are both in one file
       to make things simpler */
    
    static struct ast_context *local_contexts = NULL;
    static struct ast_context *contexts = NULL;
    struct ast_context;
    struct ast_app;
    #ifdef LOW_MEMORY
    #define EXT_DATA_SIZE 256
    #else
    #define EXT_DATA_SIZE 8192
    #endif
    
    #ifdef NOT_ANYMORE
    static AST_RWLIST_HEAD_STATIC(switches, ast_switch);
    #endif
    
    #define SWITCH_DATA_LENGTH 256
    
    static const char *ast_get_extension_app(struct ast_exten *e)
    {
    	return e ? e->app : NULL;
    }
    
    static const char *ast_get_extension_name(struct ast_exten *exten)
    {
    	return exten ? exten->exten : NULL;
    }
    
    static AST_RWLIST_HEAD_STATIC(hints, ast_hint);
    
    /*! \brief  ast_change_hint: Change hint for an extension */
    static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne)
    {
    	struct ast_hint *hint;
    	int res = -1;
    
    	AST_RWLIST_TRAVERSE(&hints, hint, list) {
    		if (hint->exten == oe) {
    	    		hint->exten = ne;
    			res = 0;
    			break;
    		}
    	}
    
    	return res;
    }
    
    /*! \brief  ast_add_hint: Add hint to hint list, check initial extension state */
    static int ast_add_hint(struct ast_exten *e)
    {
    	struct ast_hint *hint;
    
    	if (!e)
    		return -1;
    
    
    	/* Search if hint exists, do nothing */
    	AST_RWLIST_TRAVERSE(&hints, hint, list) {
    		if (hint->exten == e) {
    
    			if (option_debug > 1)
    				ast_log(LOG_DEBUG, "HINTS: Not re-adding existing hint %s: %s\n", ast_get_extension_name(e), ast_get_extension_app(e));
    
    	if (option_debug > 1)
    		ast_log(LOG_DEBUG, "HINTS: Adding hint %s: %s\n", ast_get_extension_name(e), ast_get_extension_app(e));
    
    
    	if (!(hint = ast_calloc(1, sizeof(*hint)))) {
    		return -1;
    	}
    	/* Initialize and insert new item at the top */
    	hint->exten = e;
    	AST_RWLIST_INSERT_HEAD(&hints, hint, list);
    
    	return 0;
    }
    
    /*! \brief add the extension in the priority chain.
     * returns 0 on success, -1 on failure
     */
    static int add_pri(struct ast_context *con, struct ast_exten *tmp,
    	struct ast_exten *el, struct ast_exten *e, int replace)
    {
    	struct ast_exten *ep;
    
    	for (ep = NULL; e ; ep = e, e = e->peer) {
    		if (e->priority >= tmp->priority)
    			break;
    	}
    	if (!e) {	/* go at the end, and ep is surely set because the list is not empty */
    		ep->peer = tmp;
    		return 0;	/* success */
    	}
    	if (e->priority == tmp->priority) {
    		/* Can't have something exactly the same.  Is this a
    		   replacement?  If so, replace, otherwise, bonk. */
    		if (!replace) {
    			ast_log(LOG_WARNING, "Unable to register extension '%s', priority %d in '%s', already in use\n", tmp->exten, tmp->priority, con->name);
    			tmp->datad(tmp->data);
    			free(tmp);
    			return -1;
    		}
    		/* we are replacing e, so copy the link fields and then update
    		 * whoever pointed to e to point to us
    		 */
    		tmp->next = e->next;	/* not meaningful if we are not first in the peer list */
    		tmp->peer = e->peer;	/* always meaningful */
    		if (ep)			/* We're in the peer list, just insert ourselves */
    			ep->peer = tmp;
    		else if (el)		/* We're the first extension. Take over e's functions */
    			el->next = tmp;
    		else			/* We're the very first extension.  */
    			con->root = tmp;
    		if (tmp->priority == PRIORITY_HINT)
    			ast_change_hint(e,tmp);
    		/* Destroy the old one */
    		e->datad(e->data);
    		free(e);
    	} else {	/* Slip ourselves in just before e */
    		tmp->peer = e;
    		tmp->next = e->next;	/* extension chain, or NULL if e is not the first extension */
    		if (ep)			/* Easy enough, we're just in the peer list */
    			ep->peer = tmp;
    		else {			/* we are the first in some peer list, so link in the ext list */
    			if (el)
    				el->next = tmp;	/* in the middle... */
    			else
    				con->root = tmp; /* ... or at the head */
    			e->next = NULL;	/* e is no more at the head, so e->next must be reset */
    		}
    		/* And immediately return success. */
    		if (tmp->priority == PRIORITY_HINT)
    			 ast_add_hint(tmp);
    	}
    	return 0;
    }
    
    /*! \brief  ast_remove_hint: Remove hint from extension */
    static int ast_remove_hint(struct ast_exten *e)
    {
    	/* Cleanup the Notifys if hint is removed */
    	struct ast_hint *hint;
    	struct ast_state_cb *cblist, *cbprev;
    	int res = -1;
    
    	if (!e)
    		return -1;
    
    	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&hints, hint, list) {
    		if (hint->exten == e) {
    			cbprev = NULL;
    			cblist = hint->callbacks;
    			while (cblist) {
    				/* Notify with -1 and remove all callbacks */
    				cbprev = cblist;
    				cblist = cblist->next;
    				free(cbprev);
    			}
    			hint->callbacks = NULL;
    			AST_RWLIST_REMOVE_CURRENT(&hints, list);
    			free(hint);
    	   		res = 0;
    			break;
    		}
    	}
    	AST_RWLIST_TRAVERSE_SAFE_END
    
    	return res;
    }
    
    static void destroy_exten(struct ast_exten *e)
    {
    	if (e->priority == PRIORITY_HINT)
    		ast_remove_hint(e);
    
    	if (e->datad)
    		e->datad(e->data);
    	free(e);
    }
    
    char *days[] =
    {
    	"sun",
    	"mon",
    	"tue",
    	"wed",
    	"thu",
    	"fri",
    	"sat",
    	NULL,
    };
    
    char *months[] =
    {
    	"jan",
    	"feb",
    	"mar",
    	"apr",
    	"may",
    	"jun",
    	"jul",
    	"aug",
    	"sep",
    	"oct",
    	"nov",
    	"dec",
    	NULL,
    };
    
    
    int ast_build_timing(struct ast_timing *i, const char *info_in);
    
    
    int ast_build_timing(struct ast_timing *i, const char *info_in)
    
    	int j, num_fields, last_sep = -1;
    
    	if (ast_strlen_zero(info_in)) {
    
    	/* make a copy just in case we were passed a static string */
    
    
    	/* count the number of fields in the timespec */
    	for (j = 0, num_fields = 1; info[j] != '\0'; j++) {
    
    		if (info[j] == '|' || info[j] == ',') {
    
    			last_sep = j;
    			num_fields++;
    		}
    	}
    
    	/* save the timezone, if it is specified */
    	if (num_fields == 5) {
    		i->timezone = ast_strdup(info + last_sep + 1);
    	}
    
    
    	/* Assume everything except time */
    	i->monthmask = 0xfff;	/* 12 bits */
    	i->daymask = 0x7fffffffU; /* 31 bits */
    	i->dowmask = 0x7f; /* 7 bits */
    	/* on each call, use strsep() to move info to the next argument */
    
    	get_timerange(i, strsep(&info, "|,"));
    
    		i->dowmask = get_range(strsep(&info, "|,"), 7, days, "day of week");
    
    		i->daymask = get_range(strsep(&info, "|,"), 31, NULL, "day");
    
    		i->monthmask = get_range(strsep(&info, "|,"), 12, months, "month");
    
    	return 1;
    }
    
    /*!
     * \brief helper functions to sort extensions and patterns in the desired way,
     * so that more specific patterns appear first.
     *
     * ext_cmp1 compares individual characters (or sets of), returning
     * an int where bits 0-7 are the ASCII code of the first char in the set,
     * while bit 8-15 are the cardinality of the set minus 1.
     * This way more specific patterns (smaller cardinality) appear first.
     * Wildcards have a special value, so that we can directly compare them to
     * sets by subtracting the two values. In particular:
     * 	0x000xx		one character, xx
     * 	0x0yyxx		yy character set starting with xx
     * 	0x10000		'.' (one or more of anything)
     * 	0x20000		'!' (zero or more of anything)
     * 	0x30000		NUL (end of string)
     * 	0x40000		error in set.
     * The pointer to the string is advanced according to needs.
     * NOTES:
     *	1. the empty set is equivalent to NUL.
     *	2. given that a full set has always 0 as the first element,
     *	   we could encode the special cases as 0xffXX where XX
     *	   is 1, 2, 3, 4 as used above.
     */
    static int ext_cmp1(const char **p)
    {
    	uint32_t chars[8];
    	int c, cmin = 0xff, count = 0;
    	const char *end;
    
    	/* load, sign extend and advance pointer until we find
    	 * a valid character.
    	 */
    	while ( (c = *(*p)++) && (c == ' ' || c == '-') )
    		;	/* ignore some characters */
    
    	/* always return unless we have a set of chars */
    	switch (c) {
    	default:	/* ordinary character */
    		return 0x0000 | (c & 0xff);
    
    	case 'N':	/* 2..9 */
    		return 0x0700 | '2' ;
    
    	case 'X':	/* 0..9 */
    		return 0x0900 | '0';
    
    	case 'Z':	/* 1..9 */
    		return 0x0800 | '1';
    
    	case '.':	/* wildcard */
    		return 0x10000;
    
    	case '!':	/* earlymatch */
    		return 0x20000;	/* less specific than NULL */
    
    	case '\0':	/* empty string */
    		*p = NULL;
    		return 0x30000;
    
    	case '[':	/* pattern */
    		break;
    	}
    	/* locate end of set */
    
    	end = strchr(*p, ']');
    
    
    	if (end == NULL) {
    		ast_log(LOG_WARNING, "Wrong usage of [] in the extension\n");
    		return 0x40000;	/* XXX make this entry go last... */
    	}
    
    
    Steve Murphy's avatar
    Steve Murphy committed
    	memset(chars, '\0', sizeof(chars));	/* clear all chars in the set */
    
    	for (; *p < end  ; (*p)++) {
    		unsigned char c1, c2;	/* first-last char in range */
    		c1 = (unsigned char)((*p)[0]);
    		if (*p + 2 < end && (*p)[1] == '-') { /* this is a range */
    			c2 = (unsigned char)((*p)[2]);
    			*p += 2;	/* skip a total of 3 chars */
    		} else			/* individual character */
    			c2 = c1;
    		if (c1 < cmin)
    			cmin = c1;
    		for (; c1 <= c2; c1++) {
    			uint32_t mask = 1 << (c1 % 32);
    			if ( (chars[ c1 / 32 ] & mask) == 0)
    				count += 0x100;
    			chars[ c1 / 32 ] |= mask;
    		}
    	}
    	(*p)++;
    	return count == 0 ? 0x30000 : (count | cmin);
    }
    
    /*!
     * \brief the full routine to compare extensions in rules.
     */
    static int ext_cmp(const char *a, const char *b)
    {
    	/* make sure non-patterns come first.
    	 * If a is not a pattern, it either comes first or
    	 * we use strcmp to compare the strings.
    	 */
    	int ret = 0;
    
    	if (a[0] != '_')
    		return (b[0] == '_') ? -1 : strcmp(a, b);
    
    	/* Now we know a is a pattern; if b is not, a comes first */
    	if (b[0] != '_')
    		return 1;
    #if 0	/* old mode for ext matching */
    	return strcmp(a, b);
    #endif
    	/* ok we need full pattern sorting routine */
    	while (!ret && a && b)
    		ret = ext_cmp1(&a) - ext_cmp1(&b);
    	if (ret == 0)
    		return 0;
    	else
    		return (ret > 0) ? 1 : -1;
    }
    
    /*! \brief copy a string skipping whitespace */
    static int ext_strncpy(char *dst, const char *src, int len)
    {
    	int count=0;
    
    	while (*src && (count < len - 1)) {
    		switch(*src) {
    		case ' ':
    			/*	otherwise exten => [a-b],1,... doesn't work */
    			/*		case '-': */
    			/* Ignore */
    			break;
    		default:
    			*dst = *src;
    			dst++;
    		}
    		src++;
    		count++;
    	}
    	*dst = '\0';
    
    	return count;
    }
    
    /*
     * Wrapper around _extension_match_core() to do performance measurement
     * using the profiling code.
     */
    
    int ast_check_timing(const struct ast_timing *i);