Skip to content
Snippets Groups Projects
extconf.c 184 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	int (*execute)(struct ast_channel *chan, void *data);
    	const char *synopsis;			/*!< Synopsis text for 'show applications' */
    	const char *description;		/*!< Description (help text) for 'show application &lt;name&gt;' */
    	AST_RWLIST_ENTRY(ast_app) list;		/*!< Next app in list */
    	void *module;			/*!< Module this app belongs to */
    	char name[0];				/*!< Name of the application */
    };
    
    
    /*! \brief ast_state_cb: An extension state notify register item */
    struct ast_state_cb {
    	int id;
    	void *data;
    	ast_state_cb_type callback;
    	struct ast_state_cb *next;
    };
    
    /*! \brief Structure for dial plan hints
    
      \note Hints are pointers from an extension in the dialplan to one or
      more devices (tech/name) 
    	- See \ref AstExtState
    */
    struct ast_hint {
    	struct ast_exten *exten;	/*!< Extension */
    	int laststate; 			/*!< Last known state */
    	struct ast_state_cb *callbacks;	/*!< Callback list for this extension */
    	AST_RWLIST_ENTRY(ast_hint) list;/*!< Pointer to next hint in list */
    };
    
    struct store_hint {
    	char *context;
    	char *exten;
    	struct ast_state_cb *callbacks;
    	int laststate;
    	AST_LIST_ENTRY(store_hint) list;
    	char data[1];
    };
    
    AST_LIST_HEAD(store_hints, store_hint);
    
    static const struct cfextension_states {
    	int extension_state;
    	const char * const text;
    } extension_states[] = {
    	{ AST_EXTENSION_NOT_INUSE,                     "Idle" },
    	{ AST_EXTENSION_INUSE,                         "InUse" },
    	{ AST_EXTENSION_BUSY,                          "Busy" },
    	{ AST_EXTENSION_UNAVAILABLE,                   "Unavailable" },
    	{ AST_EXTENSION_RINGING,                       "Ringing" },
    	{ AST_EXTENSION_INUSE | AST_EXTENSION_RINGING, "InUse&Ringing" },
    	{ AST_EXTENSION_ONHOLD,                        "Hold" },
    	{ AST_EXTENSION_INUSE | AST_EXTENSION_ONHOLD,  "InUse&Hold" }
    };
    #define STATUS_NO_CONTEXT	1
    #define STATUS_NO_EXTENSION	2
    #define STATUS_NO_PRIORITY	3
    #define STATUS_NO_LABEL		4
    #define STATUS_SUCCESS		5
    
    
    #if defined ( __i386__) && (defined(__FreeBSD__) || defined(linux))
    #if defined(__FreeBSD__)
    #include <machine/cpufunc.h>
    #elif defined(linux)
    static __inline uint64_t
    rdtsc(void)
    { 
    	uint64_t rv;
    
    	__asm __volatile(".byte 0x0f, 0x31" : "=A" (rv));
    	return (rv);
    }
    #endif
    #else	/* supply a dummy function on other platforms */
    static __inline uint64_t
    rdtsc(void)
    {
    	return 0;
    }
    #endif
    
    
    static struct ast_var_t *ast_var_assign(const char *name, const char *value)
    {	
    	struct ast_var_t *var;
    	int name_len = strlen(name) + 1;
    	int value_len = strlen(value) + 1;
    
    	if (!(var = ast_calloc(sizeof(*var) + name_len + value_len, sizeof(char)))) {
    		return NULL;
    	}
    
    	ast_copy_string(var->name, name, name_len);
    	var->value = var->name + name_len;
    	ast_copy_string(var->value, value, value_len);
    	
    	return var;
    }	
    	
    static void ast_var_delete(struct ast_var_t *var)
    {
    	if (var)
    		free(var);
    }
    
    
    /* chopped this one off at the knees! */
    static int ast_func_write(struct ast_channel *chan, const char *function, const char *value)
    {
    
    	/* ast_log(LOG_ERROR, "Function %s not registered\n", function); we are not interested in the details here */
    
    	return -1;
    }
    
    static unsigned int ast_app_separate_args(char *buf, char delim, char **array, int arraylen)
    {
    	int argc;
    	char *scan;
    	int paren = 0, quote = 0;
    
    	if (!buf || !array || !arraylen)
    		return 0;
    
    	memset(array, 0, arraylen * sizeof(*array));
    
    	scan = buf;
    
    	for (argc = 0; *scan && (argc < arraylen - 1); argc++) {
    		array[argc] = scan;
    		for (; *scan; scan++) {
    			if (*scan == '(')
    				paren++;
    			else if (*scan == ')') {
    				if (paren)
    					paren--;
    			} else if (*scan == '"' && delim != '"') {
    				quote = quote ? 0 : 1;
    				/* Remove quote character from argument */
    				memmove(scan, scan + 1, strlen(scan));
    				scan--;
    			} else if (*scan == '\\') {
    				/* Literal character, don't parse */
    				memmove(scan, scan + 1, strlen(scan));
    			} else if ((*scan == delim) && !paren && !quote) {
    				*scan++ = '\0';
    				break;
    			}
    		}
    	}
    
    	if (*scan)
    		array[argc++] = scan;
    
    	return argc;
    }
    
    static void pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
    {
    	struct ast_var_t *newvariable;
    	struct varshead *headp;
    	const char *nametail = name;
    
    	/* XXX may need locking on the channel ? */
    	if (name[strlen(name)-1] == ')') {
    		char *function = ast_strdupa(name);
    
    		ast_func_write(chan, function, value);
    		return;
    	}
    
    	headp = &globals;
    
    	/* For comparison purposes, we have to strip leading underscores */
    	if (*nametail == '_') {
    		nametail++;
    		if (*nametail == '_')
    			nametail++;
    	}
    
    	AST_LIST_TRAVERSE (headp, newvariable, entries) {
    		if (strcasecmp(ast_var_name(newvariable), nametail) == 0) {
    			/* there is already such a variable, delete it */
    			AST_LIST_REMOVE(headp, newvariable, entries);
    			ast_var_delete(newvariable);
    			break;
    		}
    	}
    
    	if (value) {
    		if ((option_verbose > 1) && (headp == &globals))
    			ast_verbose(VERBOSE_PREFIX_2 "Setting global variable '%s' to '%s'\n", name, value);
    		newvariable = ast_var_assign(name, value);
    		AST_LIST_INSERT_HEAD(headp, newvariable, entries);
    	}
    
    }
    
    static int pbx_builtin_setvar(struct ast_channel *chan, void *data)
    {
    	char *name, *value, *mydata;
    	int argc;
    	char *argv[24];		/* this will only support a maximum of 24 variables being set in a single operation */
    	int global = 0;
    	int x;
    
    	if (ast_strlen_zero(data)) {
    		ast_log(LOG_WARNING, "Set requires at least one variable name/value pair.\n");
    		return 0;
    	}
    
    	mydata = ast_strdupa(data);
    	argc = ast_app_separate_args(mydata, '|', argv, sizeof(argv) / sizeof(argv[0]));
    
    	/* check for a trailing flags argument */
    	if ((argc > 1) && !strchr(argv[argc-1], '=')) {
    		argc--;
    		if (strchr(argv[argc], 'g'))
    			global = 1;
    	}
    
    	for (x = 0; x < argc; x++) {
    		name = argv[x];
    		if ((value = strchr(name, '='))) {
    			*value++ = '\0';
    			pbx_builtin_setvar_helper((global) ? NULL : chan, name, value);
    		} else
    			ast_log(LOG_WARNING, "Ignoring entry '%s' with no = (and not last 'options' entry)\n", name);
    	}
    
    	return(0);
    }
    
    int localized_pbx_builtin_setvar(struct ast_channel *chan, void *data);
    
    int localized_pbx_builtin_setvar(struct ast_channel *chan, void *data)
    {
    	return pbx_builtin_setvar(chan, data);
    }
    
    
    /*! \brief Helper for get_range.
     * return the index of the matching entry, starting from 1.
     * If names is not supplied, try numeric values.
     */
    static int lookup_name(const char *s, char *const names[], int max)
    {
    	int i;
    
    
    	if (names && *s > '9') {
    
    			if (!strcasecmp(s, names[i])) {
    				return i;
    			}
    
    
    	/* Allow months and weekdays to be specified as numbers, as well */
    	if (sscanf(s, "%d", &i) == 1 && i >= 1 && i <= max) {
    		/* What the array offset would have been: "1" would be at offset 0 */
    		return i - 1;
    	}
    	return -1; /* error return */
    
    }
    
    /*! \brief helper function to return a range up to max (7, 12, 31 respectively).
     * names, if supplied, is an array of names that should be mapped to numbers.
     */
    static unsigned get_range(char *src, int max, char *const names[], const char *msg)
    {
    
    	int start, end; /* start and ending position */
    
    
    	/* Check for whole range */
    	if (ast_strlen_zero(src) || !strcmp(src, "*")) {
    
    		return (1 << max) - 1;
    	}
    
    	while ((part = strsep(&src, "&"))) {
    
    		char *endpart = strchr(part, '-');
    		if (endpart) {
    			*endpart++ = '\0';
    		}
    
    		if ((start = lookup_name(part, names, max)) < 0) {
    			ast_log(LOG_WARNING, "Invalid %s '%s', skipping element\n", msg, part);
    			continue;
    
    		if (endpart) { /* find end of range */
    			if ((end = lookup_name(endpart, names, max)) < 0) {
    				ast_log(LOG_WARNING, "Invalid end %s '%s', skipping element\n", msg, endpart);
    				continue;
    
    			end = start;
    		}
    		/* Fill the mask. Remember that ranges are cyclic */
    		mask |= (1 << end);   /* initialize with last element */
    		while (start != end) {
    			if (start >= max) {
    				start = 0;
    			}
    			mask |= (1 << start);
    			start++;
    
    		}
    	}
    	return mask;
    }
    
    /*! \brief store a bitmask of valid times, one bit each 2 minute */
    static void get_timerange(struct ast_timing *i, char *times)
    {
    
    	int st_h, st_m;
    	int endh, endm;
    	int minute_start, minute_end;
    
    
    	/* start disabling all times, fill the fields with 0's, as they may contain garbage */
    	memset(i->minmask, 0, sizeof(i->minmask));
    
    
    	/* 1-minute per bit */
    
    	/* Star is all times */
    	if (ast_strlen_zero(times) || !strcmp(times, "*")) {
    
    		/* 48, because each hour takes 2 integers; 30 bits each */
    		for (x = 0; x < 48; x++) {
    
    			i->minmask[x] = 0x3fffffff; /* 30 bits */
    
    	while ((part = strsep(&times, "&"))) {
    		if (!(endpart = strchr(part, '-'))) {
    			if (sscanf(part, "%d:%d", &st_h, &st_m) != 2 || st_h < 0 || st_h > 23 || st_m < 0 || st_m > 59) {
    				ast_log(LOG_WARNING, "%s isn't a valid time.\n", part);
    				continue;
    			}
    			i->minmask[st_h * 2 + (st_m >= 30 ? 1 : 0)] |= (1 << (st_m % 30));
    			continue;
    
    		*endpart++ = '\0';
    		/* why skip non digits? Mostly to skip spaces */
    		while (*endpart && !isdigit(*endpart)) {
    			endpart++;
    		}
    		if (!*endpart) {
    			ast_log(LOG_WARNING, "Invalid time range starting with '%s-'.\n", part);
    			continue;
    		}
    		if (sscanf(part, "%d:%d", &st_h, &st_m) != 2 || st_h < 0 || st_h > 23 || st_m < 0 || st_m > 59) {
    			ast_log(LOG_WARNING, "'%s' isn't a valid start time.\n", part);
    			continue;
    		}
    		if (sscanf(endpart, "%d:%d", &endh, &endm) != 2 || endh < 0 || endh > 23 || endm < 0 || endm > 59) {
    			ast_log(LOG_WARNING, "'%s' isn't a valid end time.\n", endpart);
    			continue;
    		}
    		minute_start = st_h * 60 + st_m;
    		minute_end = endh * 60 + endm;
    		/* Go through the time and enable each appropriate bit */
    		for (x = minute_start; x != minute_end; x = (x + 1) % (24 * 60)) {
    			i->minmask[x / 30] |= (1 << (x % 30));
    		}
    		/* Do the last one */
    		i->minmask[x / 30] |= (1 << (x % 30));
    
    	}
    	/* All done */
    	return;
    }
    
    static void null_datad(void *foo)
    {
    }
    
    /*! \brief Find realtime engine for realtime family */
    static struct ast_config_engine *find_engine(const char *family, char *database, int dbsiz, char *table, int tabsiz) 
    {
    	struct ast_config_engine *eng, *ret = NULL;
    	struct ast_config_map *map;
    
    
    	for (map = config_maps; map; map = map->next) {
    		if (!strcasecmp(family, map->name)) {
    			if (database)
    				ast_copy_string(database, map->database, dbsiz);
    			if (table)
    				ast_copy_string(table, map->table ? map->table : family, tabsiz);
    			break;
    		}
    	}
    
    	/* Check if the required driver (engine) exist */
    	if (map) {
    		for (eng = config_engine_list; !ret && eng; eng = eng->next) {
    			if (!strcasecmp(eng->name, map->driver))
    				ret = eng;
    		}
    	}
    
    	
    	/* if we found a mapping, but the engine is not available, then issue a warning */
    	if (map && !ret)
    		ast_log(LOG_WARNING, "Realtime mapping for '%s' found to engine '%s', but the engine is not available\n", map->name, map->driver);
    
    	return ret;
    }
    
    struct ast_category *ast_config_get_current_category(const struct ast_config *cfg);
    
    struct ast_category *ast_config_get_current_category(const struct ast_config *cfg)
    {
    	return cfg->current;
    }
    
    
    static struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno);
    
    static struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno)
    
    {
    	struct ast_category *category;
    
    	if ((category = ast_calloc(1, sizeof(*category))))
    		ast_copy_string(category->name, name, sizeof(category->name));
    
    	category->file = strdup(in_file);
    	category->lineno = lineno; /* if you don't know the lineno, set it to 999999 or something real big */
     	return category;
    
    }
    
    struct ast_category *localized_category_get(const struct ast_config *config, const char *category_name);
    
    struct ast_category *localized_category_get(const struct ast_config *config, const char *category_name)
    {
    	return category_get(config, category_name, 0);
    }
    
    static void move_variables(struct ast_category *old, struct ast_category *new)
    {
    	struct ast_variable *var = old->root;
    	old->root = NULL;
    #if 1
    	/* we can just move the entire list in a single op */
    	ast_variable_append(new, var);
    #else
    	while (var) {
    		struct ast_variable *next = var->next;
    		var->next = NULL;
    		ast_variable_append(new, var);
    		var = next;
    	}
    #endif
    }
    
    static void inherit_category(struct ast_category *new, const struct ast_category *base)
    {
    	struct ast_variable *var;
    
    	for (var = base->root; var; var = var->next)
    		ast_variable_append(new, variable_clone(var));
    }
    
    static void ast_category_append(struct ast_config *config, struct ast_category *category);
    
    static void ast_category_append(struct ast_config *config, struct ast_category *category)
    {
    	if (config->last)
    		config->last->next = category;
    	else
    		config->root = category;
    	config->last = category;
    	config->current = category;
    }
    
    static void ast_category_destroy(struct ast_category *cat);
    
    static void ast_category_destroy(struct ast_category *cat)
    {
    	ast_variables_destroy(cat->root);
    
    	free(cat);
    }
    
    static struct ast_config_engine text_file_engine = {
    	.name = "text",
    	.load_func = config_text_file_load,
    };
    
    
    
    static struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, int withcomments, const char *suggested_incl_file);
    
    static struct ast_config *ast_config_internal_load(const char *filename, struct ast_config *cfg, int withcomments, const char *suggested_incl_file)
    
    {
    	char db[256];
    	char table[256];
    	struct ast_config_engine *loader = &text_file_engine;
    	struct ast_config *result; 
    
    	if (cfg->include_level == cfg->max_include_level) {
    		ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", cfg->max_include_level);
    		return NULL;
    	}
    
    	cfg->include_level++;
    	/*  silence is golden!
    		ast_log(LOG_WARNING, "internal loading file %s level=%d\n", filename, cfg->include_level);
    	*/
    
    	if (strcmp(filename, extconfig_conf) && strcmp(filename, "asterisk.conf") && config_engine_list) {
    		struct ast_config_engine *eng;
    
    		eng = find_engine(filename, db, sizeof(db), table, sizeof(table));
    
    
    		if (eng && eng->load_func) {
    			loader = eng;
    		} else {
    			eng = find_engine("global", db, sizeof(db), table, sizeof(table));
    			if (eng && eng->load_func)
    				loader = eng;
    		}
    	}
    
    
    	result = loader->load_func(db, table, filename, cfg, withcomments, suggested_incl_file);
    
    	/* silence is golden 
    	   ast_log(LOG_WARNING, "finished internal loading file %s level=%d\n", filename, cfg->include_level);
    	*/
    
    	if (result)
    		result->include_level--;
    
    	return result;
    }
    
    
    
    static int process_text_line(struct ast_config *cfg, struct ast_category **cat, char *buf, int lineno, const char *configfile, int withcomments, const char *suggested_include_file)
    
    {
    	char *c;
    	char *cur = buf;
    	struct ast_variable *v;
    	char cmd[512], exec_file[512];
    	int object, do_exec, do_include;
    
    	/* Actually parse the entry */
    	if (cur[0] == '[') {
    		struct ast_category *newcat = NULL;
    		char *catname;
    
    		/* A category header */
    		c = strchr(cur, ']');
    		if (!c) {
    			ast_log(LOG_WARNING, "parse error: no closing ']', line %d of %s\n", lineno, configfile);
    			return -1;
    		}
    		*c++ = '\0';
    		cur++;
     		if (*c++ != '(')
     			c = NULL;
    		catname = cur;
    
    		if (!(*cat = newcat = ast_category_new(catname, ast_strlen_zero(suggested_include_file)?configfile:suggested_include_file, lineno))) {
    
    		/* add comments */
    		if (withcomments && comment_buffer && comment_buffer[0] ) {
    			newcat->precomments = ALLOC_COMMENT(comment_buffer);
    		}
    		if (withcomments && lline_buffer && lline_buffer[0] ) {
    			newcat->sameline = ALLOC_COMMENT(lline_buffer);
    		}
    		if( withcomments )
    			CB_RESET();
    		
     		/* If there are options or categories to inherit from, process them now */
     		if (c) {
     			if (!(cur = strchr(c, ')'))) {
     				ast_log(LOG_WARNING, "parse error: no closing ')', line %d of %s\n", lineno, configfile);
     				return -1;
     			}
     			*cur = '\0';
     			while ((cur = strsep(&c, ","))) {
    				if (!strcasecmp(cur, "!")) {
    					(*cat)->ignored = 1;
    				} else if (!strcasecmp(cur, "+")) {
    					*cat = category_get(cfg, catname, 1);
    					if (!*cat) {
    						ast_config_destroy(cfg);
    						if (newcat)
    							ast_category_destroy(newcat);
    						ast_log(LOG_WARNING, "Category addition requested, but category '%s' does not exist, line %d of %s\n", catname, lineno, configfile);
    						return -1;
    					}
    					if (newcat) {
    						move_variables(newcat, *cat);
    						ast_category_destroy(newcat);
    						newcat = NULL;
    					}
    				} else {
    					struct ast_category *base;
     				
    					base = category_get(cfg, cur, 1);
    					if (!base) {
    						ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile);
    						return -1;
    					}
    					inherit_category(*cat, base);
    				}
     			}
     		}
    		if (newcat)
    			ast_category_append(cfg, *cat);
    	} else if (cur[0] == '#') {
    		/* A directive */
    		cur++;
    		c = cur;
    		while(*c && (*c > 32)) c++;
    		if (*c) {
    			*c = '\0';
    			/* Find real argument */
    			c = ast_skip_blanks(c + 1);
    			if (!*c)
    				c = NULL;
    		} else 
    			c = NULL;
    		do_include = !strcasecmp(cur, "include");
    		if(!do_include)
    			do_exec = !strcasecmp(cur, "exec");
    		else
    			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) {
    
    				char *cur2;
    				char real_inclusion_name[256];
    				struct ast_config_include *inclu;
                    
    
    				/* 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. */
    				if (do_exec) { 
    					snprintf(exec_file, sizeof(exec_file), "/var/tmp/exec.%d.%ld", (int)time(NULL), (long)pthread_self());
    					snprintf(cmd, sizeof(cmd), "%s > %s 2>&1", cur, exec_file);
    					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); */
    				
    
    				/* record this inclusion */
    				inclu = 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); */
    				
    			} else {
    				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);
    			}
    		}
    		else 
    			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();
    	}
    	
    #ifdef AST_INCLUDE_GLOB
    	{
    		int glob_ret;
    		glob_t globbuf;
    
    		globbuf.gl_offs = 0;	/* initialize it to silence gcc */
    #ifdef SOLARIS
    		glob_ret = glob(fn, GLOB_NOCHECK, NULL, &globbuf);
    #else
    		glob_ret = glob(fn, GLOB_NOMAGIC|GLOB_BRACE, NULL, &globbuf);
    #endif
    		if (glob_ret == GLOB_NOSPACE)
    			ast_log(LOG_WARNING,
    				"Glob Expansion of pattern '%s' failed: Not enough memory\n", fn);
    		else if (glob_ret  == GLOB_ABORTED)
    			ast_log(LOG_WARNING,
    				"Glob Expansion of pattern '%s' failed: Read error\n", fn);
    		else  {
    			/* loop over expanded files */
    			int i;
    			for (i=0; i<globbuf.gl_pathc; i++) {
    				ast_copy_string(fn, globbuf.gl_pathv[i], sizeof(fn));
    #endif
    	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 */
    				}
    				
    				new_buf = buf;
    				if (comment) 
    					process_buf = NULL;
    				else
    					process_buf = buf;
    				
    				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);
    							}
    							*comment_p = '\0'; 
    							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 */
    				}
    				
    				if (process_buf) {
    
    					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)) {
    
    							cfg = NULL;
    							break;
    						}
    					}
    				}
    			}
    		}
    		fclose(f);		
    	} while(0);
    	if (comment) {
    		ast_log(LOG_WARNING,"Unterminated comment detected beginning on line %d\n", nest[comment]);
    	}
    #ifdef AST_INCLUDE_GLOB
    					if (!cfg)
    						break;
    				}
    				globfree(&globbuf);
    			}
    		}
    #endif
    	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) {