diff options
Diffstat (limited to 'trunk/res')
31 files changed, 36082 insertions, 0 deletions
diff --git a/trunk/res/Makefile b/trunk/res/Makefile new file mode 100644 index 000000000..72f4e8ae0 --- /dev/null +++ b/trunk/res/Makefile @@ -0,0 +1,53 @@ +# +# Asterisk -- A telephony toolkit for Linux. +# +# Makefile for resource modules +# +# Copyright (C) 1999-2006, Digium, Inc. +# +# This program is free software, distributed under the terms of +# the GNU General Public License +# + +-include $(ASTTOPDIR)/menuselect.makeopts $(ASTTOPDIR)/menuselect.makedeps + +MODULE_PREFIX=res +MENUSELECT_CATEGORY=RES +MENUSELECT_DESCRIPTION=Resource Modules + +all: _all + +include $(ASTTOPDIR)/Makefile.moddir_rules + +ifneq ($(findstring $(OSARCH), mingw32 cygwin ),) + # cygwin has some dependencies among res_ things. + # We use order-only dependencies, and then add the libraries as required. + res_features.so: | res_monitor.so + res_features.so_LIBS:= -lres_monitor.so + # + res_agi.so: | res_speech.so + res_agi.so_LIBS:= -lres_speech.so +endif + +ael/ael_lex.o: ael/ael_lex.c ../include/asterisk/ael_structs.h ael/ael.tab.h +ael/ael_lex.o: ASTCFLAGS+=-I. -Iael + +ael/ael.tab.o: ael/ael.tab.c ael/ael.tab.h ../include/asterisk/ael_structs.h +ael/ael.tab.o: ASTCFLAGS+=-I. -Iael -DYYENABLE_NLS=0 + +$(if $(filter res_snmp,$(EMBEDDED_MODS)),modules.link,res_snmp.so): snmp/agent.o + +$(if $(filter res_ael_share,$(EMBEDDED_MODS)),modules.link,res_ael_share.so): ael/ael_lex.o ael/ael.tab.o ael/pval.o + +ael/ael_lex.c: + (cd ael; flex ael.flex; sed -i -e "/begin standard C headers/i#include \"asterisk.h\"" ael_lex.c) + (cd ael; sed 's@#if __STDC_VERSION__ >= 199901L@#if !defined __STDC_VERSION__ || __STDC_VERSION__ >= 199901L@' ael_lex.c > zz; mv zz ael_lex.c) + +ael/ael.tab.c ael/ael.tab.h: + (cd ael; bison -v -d ael.y) + +ael/pval.o: ael/pval.c + +clean:: + rm -f snmp/*.o + rm -f ael/*.o diff --git a/trunk/res/ael/ael.flex b/trunk/res/ael/ael.flex new file mode 100644 index 000000000..cf2d36a2c --- /dev/null +++ b/trunk/res/ael/ael.flex @@ -0,0 +1,694 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2006, Digium, Inc. + * + * Steve Murphy <murf@parsetree.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ +/*! \file + * + * \brief Flex scanner description of tokens used in AEL2 . + * + */ + +/* + * Start with flex options: + * + * %x describes the contexts we have: paren, semic and argg, plus INITIAL + */ +%x paren semic argg comment + +/* prefix used for various globally-visible functions and variables. + * This renames also yywrap, but since we do not use it, we just + * add option noyywrap to remove it. + */ +%option prefix="ael_yy" +%option noyywrap 8bit + +/* yyfree normally just frees its arg. It can be null sometimes, + which some systems will complain about, so, we'll define our own version */ +%option noyyfree + +/* batch gives a bit more performance if we are using it in + * a non-interactive mode. We probably don't care much. + */ +%option batch + +/* outfile is the filename to be used instead of lex.yy.c */ +%option outfile="ael_lex.c" + +/* + * These are not supported in flex 2.5.4, but we need them + * at the moment: + * reentrant produces a thread-safe parser. Not 100% sure that + * we require it, though. + * bison-bridge passes an additional yylval argument to yylex(). + * bison-locations is probably not needed. + */ +%option reentrant +%option bison-bridge +%option bison-locations + +%{ +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#if defined(__Darwin__) || defined(__CYGWIN__) +#define GLOB_ABORTED GLOB_ABEND +#endif +# include <glob.h> + +#include "asterisk/logger.h" +#include "asterisk/utils.h" +#include "ael/ael.tab.h" +#include "asterisk/ael_structs.h" + +/* + * A stack to keep track of matching brackets ( [ { } ] ) + */ +static char pbcstack[400]; /* XXX missing size checks */ +static int pbcpos = 0; +static void pbcpush(char x); +static int pbcpop(char x); + +static int parencount = 0; + +/* + * current line, column and filename, updated as we read the input. + */ +static int my_lineno = 1; /* current line in the source */ +static int my_col = 1; /* current column in the source */ +char *my_file = 0; /* used also in the bison code */ +char *prev_word; /* XXX document it */ + +#define MAX_INCLUDE_DEPTH 50 + +/* + * flex is not too smart, and generates global functions + * without prototypes so the compiler may complain. + * To avoid that, we declare the prototypes here, + * even though these functions are not used. + */ +int ael_yyget_column (yyscan_t yyscanner); +void ael_yyset_column (int column_no , yyscan_t yyscanner); + +int ael_yyparse (struct parse_io *); + +/* + * A stack to process include files. + * As we switch into the new file we need to store the previous + * state to restore it later. + */ +struct stackelement { + char *fname; + int lineno; + int colno; + glob_t globbuf; /* the current globbuf */ + int globbuf_pos; /* where we are in the current globbuf */ + YY_BUFFER_STATE bufstate; +}; + +static struct stackelement include_stack[MAX_INCLUDE_DEPTH]; +static int include_stack_index = 0; +static void setup_filestack(char *fnamebuf, int fnamebuf_siz, glob_t *globbuf, int globpos, yyscan_t xscan, int create); + +/* + * if we use the @n feature of bison, we must supply the start/end + * location of tokens in the structure pointed by yylloc. + * Simple tokens are just assumed to be on the same line, so + * the line number is constant, and the column is incremented + * by the length of the token. + */ +#ifdef FLEX_BETA /* set for 2.5.33 */ + +/* compute the total number of lines and columns in the text + * passed as argument. + */ +static void pbcwhere(const char *text, int *line, int *col ) +{ + int loc_line = *line; + int loc_col = *col; + char c; + while ( (c = *text++) ) { + if ( c == '\t' ) { + loc_col += 8 - (loc_col % 8); + } else if ( c == '\n' ) { + loc_line++; + loc_col = 1; + } else + loc_col++; + } + *line = loc_line; + *col = loc_col; +} + +#define STORE_POS do { \ + yylloc->first_line = yylloc->last_line = my_lineno; \ + yylloc->first_column=my_col; \ + yylloc->last_column=my_col+yyleng-1; \ + my_col+=yyleng; \ + } while (0) + +#define STORE_LOC do { \ + yylloc->first_line = my_lineno; \ + yylloc->first_column=my_col; \ + pbcwhere(yytext, &my_lineno, &my_col); \ + yylloc->last_line = my_lineno; \ + yylloc->last_column = my_col - 1; \ + } while (0) +#else +#define STORE_POS +#define STORE_LOC +#endif +%} + + +NOPARENS ([^()\[\]\{\}]|\\[()\[\]\{\}])* + +NOARGG ([^(),\{\}\[\]]|\\[,()\[\]\{\}])* + +NOSEMIC ([^;()\{\}\[\]]|\\[;()\[\]\{\}])* + +HIBIT [\x80-\xff] + +%% + +\{ { STORE_POS; return LC;} +\} { STORE_POS; return RC;} +\( { STORE_POS; return LP;} +\) { STORE_POS; return RP;} +\; { STORE_POS; return SEMI;} +\= { STORE_POS; return EQ;} +\, { STORE_POS; return COMMA;} +\: { STORE_POS; return COLON;} +\& { STORE_POS; return AMPER;} +\| { STORE_POS; return BAR;} +\=\> { STORE_POS; return EXTENMARK;} +\@ { STORE_POS; return AT;} +\/\/[^\n]* {/*comment*/} +context { STORE_POS; return KW_CONTEXT;} +abstract { STORE_POS; return KW_ABSTRACT;} +extend { STORE_POS; return KW_EXTEND;} +macro { STORE_POS; return KW_MACRO;}; +globals { STORE_POS; return KW_GLOBALS;} +local { STORE_POS; return KW_LOCAL;} +ignorepat { STORE_POS; return KW_IGNOREPAT;} +switch { STORE_POS; return KW_SWITCH;} +if { STORE_POS; return KW_IF;} +ifTime { STORE_POS; return KW_IFTIME;} +random { STORE_POS; return KW_RANDOM;} +regexten { STORE_POS; return KW_REGEXTEN;} +hint { STORE_POS; return KW_HINT;} +else { STORE_POS; return KW_ELSE;} +goto { STORE_POS; return KW_GOTO;} +jump { STORE_POS; return KW_JUMP;} +return { STORE_POS; return KW_RETURN;} +break { STORE_POS; return KW_BREAK;} +continue { STORE_POS; return KW_CONTINUE;} +for { STORE_POS; return KW_FOR;} +while { STORE_POS; return KW_WHILE;} +case { STORE_POS; return KW_CASE;} +default { STORE_POS; return KW_DEFAULT;} +pattern { STORE_POS; return KW_PATTERN;} +catch { STORE_POS; return KW_CATCH;} +switches { STORE_POS; return KW_SWITCHES;} +eswitches { STORE_POS; return KW_ESWITCHES;} +includes { STORE_POS; return KW_INCLUDES;} +"/*" { BEGIN(comment); my_col += 2; } + +<comment>[^*\n]* { my_col += yyleng; } +<comment>[^*\n]*\n { ++my_lineno; my_col=1;} +<comment>"*"+[^*/\n]* { my_col += yyleng; } +<comment>"*"+[^*/\n]*\n { ++my_lineno; my_col=1;} +<comment>"*/" { my_col += 2; BEGIN(INITIAL); } + +\n { my_lineno++; my_col = 1; } +[ ]+ { my_col += yyleng; } +[\t]+ { my_col += (yyleng*8)-(my_col%8); } + +([-a-zA-Z0-9'"_/.\<\>\*\\\+!$#\[\]]|{HIBIT})([-a-zA-Z0-9'"_/.!\*\\\+\<\>\{\}$#\[\]]|{HIBIT})* { + STORE_POS; + yylval->str = strdup(yytext); + prev_word = yylval->str; + return word; + } + + + + /* + * context used for arguments of if_head, random_head, switch_head, + * for (last statement), while (XXX why not iftime_head ?). + * End with the matching parentheses. + * A comma at the top level is valid here, unlike in argg where it + * is an argument separator so it must be returned as a token. + */ +<paren>{NOPARENS}\) { + if ( pbcpop(')') ) { /* error */ + STORE_LOC; + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched ')' in expression: %s !\n", my_file, my_lineno, my_col, yytext); + BEGIN(0); + yylval->str = strdup(yytext); + prev_word = 0; + return word; + } + parencount--; + if ( parencount >= 0) { + yymore(); + } else { + STORE_LOC; + yylval->str = strdup(yytext); + yylval->str[yyleng-1] = '\0'; /* trim trailing ')' */ + unput(')'); + BEGIN(0); + return word; + } + } + +<paren>{NOPARENS}[\(\[\{] { + char c = yytext[yyleng-1]; + if (c == '(') + parencount++; + pbcpush(c); + yymore(); + } + +<paren>{NOPARENS}[\]\}] { + char c = yytext[yyleng-1]; + if ( pbcpop(c)) { /* error */ + STORE_LOC; + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched '%c' in expression!\n", + my_file, my_lineno, my_col, c); + BEGIN(0); + yylval->str = strdup(yytext); + return word; + } + yymore(); + } + + + /* + * handlers for arguments to a macro or application calls. + * We enter this context when we find the initial '(' and + * stay here until we close all matching parentheses, + * and find the comma (argument separator) or the closing ')' + * of the (external) call, which happens when parencount == 0 + * before the decrement. + */ +<argg>{NOARGG}[\(\[\{] { + char c = yytext[yyleng-1]; + if (c == '(') + parencount++; + pbcpush(c); + yymore(); + } + +<argg>{NOARGG}\) { + if ( pbcpop(')') ) { /* error */ + STORE_LOC; + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched ')' in expression!\n", my_file, my_lineno, my_col); + BEGIN(0); + yylval->str = strdup(yytext); + return word; + } + + parencount--; + if( parencount >= 0){ + yymore(); + } else { + STORE_LOC; + BEGIN(0); + if ( !strcmp(yytext, ")") ) + return RP; + yylval->str = strdup(yytext); + yylval->str[yyleng-1] = '\0'; /* trim trailing ')' */ + unput(')'); + return word; + } + } + +<argg>{NOARGG}\, { + if( parencount != 0) { /* printf("Folding in a comma!\n"); */ + yymore(); + } else { + STORE_LOC; + if( !strcmp(yytext,"," ) ) + return COMMA; + yylval->str = strdup(yytext); + yylval->str[yyleng-1] = '\0'; + unput(','); + return word; + } + } + +<argg>{NOARGG}[\]\}] { + char c = yytext[yyleng-1]; + if ( pbcpop(c) ) { /* error */ + STORE_LOC; + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched '%c' in expression!\n", my_file, my_lineno, my_col, c); + BEGIN(0); + yylval->str = strdup(yytext); + return word; + } + yymore(); + } + + /* + * context used to find tokens in the right hand side of assignments, + * or in the first and second operand of a 'for'. As above, match + * commas and use ';' as a separator (hence return it as a separate token). + */ +<semic>{NOSEMIC}[\(\[\{] { + char c = yytext[yyleng-1]; + yymore(); + pbcpush(c); + } + +<semic>{NOSEMIC}[\)\]\}] { + char c = yytext[yyleng-1]; + if ( pbcpop(c) ) { /* error */ + STORE_LOC; + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched '%c' in expression!\n", my_file, my_lineno, my_col, c); + BEGIN(0); + yylval->str = strdup(yytext); + return word; + } + yymore(); + } + +<semic>{NOSEMIC}; { + STORE_LOC; + yylval->str = strdup(yytext); + yylval->str[yyleng-1] = '\0'; + unput(';'); + BEGIN(0); + return word; + } + +\#include[ \t]+\"[^\"]+\" { + char fnamebuf[1024],*p1,*p2; + int glob_ret; + glob_t globbuf; /* the current globbuf */ + int globbuf_pos = -1; /* where we are in the current globbuf */ + globbuf.gl_offs = 0; /* initialize it to silence gcc */ + + p1 = strchr(yytext,'"'); + p2 = strrchr(yytext,'"'); + if ( include_stack_index >= MAX_INCLUDE_DEPTH ) { + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Includes nested too deeply! Wow!!! How did you do that?\n", my_file, my_lineno, my_col); + } else if ( (int)(p2-p1) > sizeof(fnamebuf) - 1 ) { + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Filename is incredibly way too long (%d chars!). Inclusion ignored!\n", my_file, my_lineno, my_col, yyleng - 10); + } else { + strncpy(fnamebuf, p1+1, p2-p1-1); + fnamebuf[p2-p1-1] = 0; + if (fnamebuf[0] != '/') { + char fnamebuf2[1024]; + snprintf(fnamebuf2,sizeof(fnamebuf2), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, fnamebuf); + ast_copy_string(fnamebuf,fnamebuf2,sizeof(fnamebuf)); + } +#ifdef SOLARIS + glob_ret = glob(fnamebuf, GLOB_NOCHECK, NULL, &globbuf); +#else + glob_ret = glob(fnamebuf, 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", fnamebuf); + } else if (glob_ret == GLOB_ABORTED) { + ast_log(LOG_WARNING, + "Glob Expansion of pattern '%s' failed: Read error\n", fnamebuf); + } else if (glob_ret == GLOB_NOMATCH) { + ast_log(LOG_WARNING, + "Glob Expansion of pattern '%s' failed: No matches!\n", fnamebuf); + } else { + globbuf_pos = 0; + } + } + if (globbuf_pos > -1) { + setup_filestack(fnamebuf, sizeof(fnamebuf), &globbuf, 0, yyscanner, 1); + } + } + + +<<EOF>> { + char fnamebuf[2048]; + if (include_stack_index > 0 && include_stack[include_stack_index-1].globbuf_pos < include_stack[include_stack_index-1].globbuf.gl_pathc-1) { + free(my_file); + my_file = 0; + yy_delete_buffer( YY_CURRENT_BUFFER, yyscanner ); + include_stack[include_stack_index-1].globbuf_pos++; + setup_filestack(fnamebuf, sizeof(fnamebuf), &include_stack[include_stack_index-1].globbuf, include_stack[include_stack_index-1].globbuf_pos, yyscanner, 0); + /* finish this */ + + } else { + if (include_stack[include_stack_index].fname) { + free(include_stack[include_stack_index].fname); + include_stack[include_stack_index].fname = 0; + } + if ( --include_stack_index < 0 ) { + yyterminate(); + } else { + if (my_file) { + free(my_file); + my_file = 0; + } + globfree(&include_stack[include_stack_index].globbuf); + include_stack[include_stack_index].globbuf_pos = -1; + + yy_delete_buffer( YY_CURRENT_BUFFER, yyscanner ); + yy_switch_to_buffer(include_stack[include_stack_index].bufstate, yyscanner ); + my_lineno = include_stack[include_stack_index].lineno; + my_col = include_stack[include_stack_index].colno; + my_file = strdup(include_stack[include_stack_index].fname); + } + } + } + +%% + +static void pbcpush(char x) +{ + pbcstack[pbcpos++] = x; +} + +void ael_yyfree(void *ptr, yyscan_t yyscanner) +{ + if (ptr) + free( (char*) ptr ); +} + +static int pbcpop(char x) +{ + if ( ( x == ')' && pbcstack[pbcpos-1] == '(' ) + || ( x == ']' && pbcstack[pbcpos-1] == '[' ) + || ( x == '}' && pbcstack[pbcpos-1] == '{' )) { + pbcpos--; + return 0; + } + return 1; /* error */ +} + +static int c_prevword(void) +{ + char *c = prev_word; + if (c == NULL) + return 0; + while ( *c ) { + switch (*c) { + case '{': + case '[': + case '(': + pbcpush(*c); + break; + case '}': + case ']': + case ')': + if (pbcpop(*c)) + return 1; + break; + } + c++; + } + return 0; +} + + +/* + * The following three functions, reset_*, are used in the bison + * code to switch context. As a consequence, we need to + * declare them global and add a prototype so that the + * compiler does not complain. + * + * NOTE: yyg is declared because it is used in the BEGIN macros, + * though that should be hidden as the macro changes + * depending on the flex options that we use - in particular, + * %reentrant changes the way the macro is declared; + * without %reentrant, BEGIN uses yystart instead of yyg + */ + +void reset_parencount(yyscan_t yyscanner ); +void reset_parencount(yyscan_t yyscanner ) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + parencount = 0; + pbcpos = 0; + pbcpush('('); /* push '(' so the last pcbpop (parencount= -1) will succeed */ + c_prevword(); + BEGIN(paren); +} + +void reset_semicount(yyscan_t yyscanner ); +void reset_semicount(yyscan_t yyscanner ) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + pbcpos = 0; + BEGIN(semic); +} + +void reset_argcount(yyscan_t yyscanner ); +void reset_argcount(yyscan_t yyscanner ) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + parencount = 0; + pbcpos = 0; + pbcpush('('); /* push '(' so the last pcbpop (parencount= -1) will succeed */ + c_prevword(); + BEGIN(argg); +} + +/* used elsewhere, but some local vars */ +struct pval *ael2_parse(char *filename, int *errors) +{ + struct pval *pval; + struct parse_io *io; + char *buffer; + struct stat stats; + FILE *fin; + + /* extern int ael_yydebug; */ + + io = calloc(sizeof(struct parse_io),1); + /* reset the global counters */ + prev_word = 0; + my_lineno = 1; + include_stack_index=0; + my_col = 0; + /* ael_yydebug = 1; */ + ael_yylex_init(&io->scanner); + fin = fopen(filename,"r"); + if ( !fin ) { + ast_log(LOG_ERROR,"File %s could not be opened\n", filename); + *errors = 1; + return 0; + } + if (my_file) + free(my_file); + my_file = strdup(filename); + stat(filename, &stats); + buffer = (char*)malloc(stats.st_size+2); + fread(buffer, 1, stats.st_size, fin); + buffer[stats.st_size]=0; + fclose(fin); + + ael_yy_scan_string (buffer ,io->scanner); + ael_yyset_lineno(1 , io->scanner); + + /* ael_yyset_in (fin , io->scanner); OLD WAY */ + + ael_yyparse(io); + + + pval = io->pval; + *errors = io->syntax_error_count; + + ael_yylex_destroy(io->scanner); + free(buffer); + free(io); + + return pval; +} + +static void setup_filestack(char *fnamebuf2, int fnamebuf_siz, glob_t *globbuf, int globpos, yyscan_t yyscanner, int create) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + int error, i; + FILE *in1; + char fnamebuf[2048]; + + if (globbuf && globbuf->gl_pathv && globbuf->gl_pathc > 0) +#if defined(STANDALONE) || defined(LOW_MEMORY) || defined(STANDALONE_AEL) + strncpy(fnamebuf, globbuf->gl_pathv[globpos], fnamebuf_siz); +#else + ast_copy_string(fnamebuf, globbuf->gl_pathv[globpos], fnamebuf_siz); +#endif + else { + ast_log(LOG_ERROR,"Include file name not present!\n"); + return; + } + for (i=0; i<include_stack_index; i++) { + if ( !strcmp(fnamebuf,include_stack[i].fname )) { + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Nice Try!!! But %s has already been included (perhaps by another file), and would cause an infinite loop of file inclusions!!! Include directive ignored\n", + my_file, my_lineno, my_col, fnamebuf); + break; + } + } + error = 1; + if (i == include_stack_index) + error = 0; /* we can use this file */ + if ( !error ) { /* valid file name */ + /* relative vs. absolute */ + if (fnamebuf[0] != '/') + snprintf(fnamebuf2, fnamebuf_siz, "%s/%s", ast_config_AST_CONFIG_DIR, fnamebuf); + else +#if defined(STANDALONE) || defined(LOW_MEMORY) || defined(STANDALONE_AEL) + strncpy(fnamebuf2, fnamebuf, fnamebuf_siz); +#else + ast_copy_string(fnamebuf2, fnamebuf, fnamebuf_siz); +#endif + in1 = fopen( fnamebuf2, "r" ); + + if ( ! in1 ) { + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Couldn't find the include file: %s; ignoring the Include directive!\n", my_file, my_lineno, my_col, fnamebuf2); + } else { + char *buffer; + struct stat stats; + stat(fnamebuf2, &stats); + buffer = (char*)malloc(stats.st_size+1); + fread(buffer, 1, stats.st_size, in1); + buffer[stats.st_size] = 0; + ast_log(LOG_NOTICE," --Read in included file %s, %d chars\n",fnamebuf2, (int)stats.st_size); + fclose(in1); + if (my_file) + free(my_file); + my_file = strdup(fnamebuf2); + include_stack[include_stack_index].fname = strdup(my_file); + include_stack[include_stack_index].lineno = my_lineno; + include_stack[include_stack_index].colno = my_col+yyleng; + if (create) + include_stack[include_stack_index].globbuf = *globbuf; + + include_stack[include_stack_index].globbuf_pos = 0; + + include_stack[include_stack_index].bufstate = YY_CURRENT_BUFFER; + if (create) + include_stack_index++; + yy_switch_to_buffer(ael_yy_scan_string (buffer ,yyscanner),yyscanner); + free(buffer); + my_lineno = 1; + my_col = 1; + BEGIN(INITIAL); + } + } +} diff --git a/trunk/res/ael/ael.tab.c b/trunk/res/ael/ael.tab.c new file mode 100644 index 000000000..aa1ec0fad --- /dev/null +++ b/trunk/res/ael/ael.tab.c @@ -0,0 +1,3413 @@ +/* A Bison parser, made by GNU Bison 2.1a. */ + +/* Skeleton parser for Yacc-like parsing with Bison, + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, when this file is copied by Bison into a + Bison output file, you may use that output file without restriction. + This special exception was added by the Free Software Foundation + in version 1.24 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "2.1a" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Using locations. */ +#define YYLSP_NEEDED 1 + +/* Substitute the variable and function names. */ +#define yyparse ael_yyparse +#define yylex ael_yylex +#define yyerror ael_yyerror +#define yylval ael_yylval +#define yychar ael_yychar +#define yydebug ael_yydebug +#define yynerrs ael_yynerrs +#define yylloc ael_yylloc + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + KW_CONTEXT = 258, + LC = 259, + RC = 260, + LP = 261, + RP = 262, + SEMI = 263, + EQ = 264, + COMMA = 265, + COLON = 266, + AMPER = 267, + BAR = 268, + AT = 269, + KW_MACRO = 270, + KW_GLOBALS = 271, + KW_IGNOREPAT = 272, + KW_SWITCH = 273, + KW_IF = 274, + KW_IFTIME = 275, + KW_ELSE = 276, + KW_RANDOM = 277, + KW_ABSTRACT = 278, + KW_EXTEND = 279, + EXTENMARK = 280, + KW_GOTO = 281, + KW_JUMP = 282, + KW_RETURN = 283, + KW_BREAK = 284, + KW_CONTINUE = 285, + KW_REGEXTEN = 286, + KW_HINT = 287, + KW_FOR = 288, + KW_WHILE = 289, + KW_CASE = 290, + KW_PATTERN = 291, + KW_DEFAULT = 292, + KW_CATCH = 293, + KW_SWITCHES = 294, + KW_ESWITCHES = 295, + KW_INCLUDES = 296, + KW_LOCAL = 297, + word = 298 + }; +#endif +/* Tokens. */ +#define KW_CONTEXT 258 +#define LC 259 +#define RC 260 +#define LP 261 +#define RP 262 +#define SEMI 263 +#define EQ 264 +#define COMMA 265 +#define COLON 266 +#define AMPER 267 +#define BAR 268 +#define AT 269 +#define KW_MACRO 270 +#define KW_GLOBALS 271 +#define KW_IGNOREPAT 272 +#define KW_SWITCH 273 +#define KW_IF 274 +#define KW_IFTIME 275 +#define KW_ELSE 276 +#define KW_RANDOM 277 +#define KW_ABSTRACT 278 +#define KW_EXTEND 279 +#define EXTENMARK 280 +#define KW_GOTO 281 +#define KW_JUMP 282 +#define KW_RETURN 283 +#define KW_BREAK 284 +#define KW_CONTINUE 285 +#define KW_REGEXTEN 286 +#define KW_HINT 287 +#define KW_FOR 288 +#define KW_WHILE 289 +#define KW_CASE 290 +#define KW_PATTERN 291 +#define KW_DEFAULT 292 +#define KW_CATCH 293 +#define KW_SWITCHES 294 +#define KW_ESWITCHES 295 +#define KW_INCLUDES 296 +#define KW_LOCAL 297 +#define word 298 + + + + +/* Copy the first part of user declarations. */ +#line 1 "ael.y" + +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2006, Digium, Inc. + * + * Steve Murphy <murf@parsetree.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ +/*! \file + * + * \brief Bison Grammar description of AEL2. + * + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "asterisk/logger.h" +#include "asterisk/ael_structs.h" + +pval * linku1(pval *head, pval *tail); +static void set_dads(pval *dad, pval *child_list); +void reset_parencount(yyscan_t yyscanner); +void reset_semicount(yyscan_t yyscanner); +void reset_argcount(yyscan_t yyscanner ); + +#define YYLEX_PARAM ((struct parse_io *)parseio)->scanner +#define YYERROR_VERBOSE 1 + +extern char *my_file; +#ifdef AAL_ARGCHECK +int ael_is_funcname(char *name); +#endif +static char *ael_token_subst(const char *mess); + + + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 1 +#endif + +/* Enabling the token table. */ +#ifndef YYTOKEN_TABLE +# define YYTOKEN_TABLE 0 +#endif + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +#line 54 "ael.y" +{ + int intval; /* integer value, typically flags */ + char *str; /* strings */ + struct pval *pval; /* full objects */ +} +/* Line 198 of yacc.c. */ +#line 238 "ael.tab.c" + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + +/* Copy the second part of user declarations. */ +#line 60 "ael.y" + + /* declaring these AFTER the union makes things a lot simpler! */ +void yyerror(YYLTYPE *locp, struct parse_io *parseio, char const *s); +int ael_yylex (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , void * yyscanner); + +/* create a new object with start-end marker */ +pval *npval(pvaltype type, int first_line, int last_line, + int first_column, int last_column); + +/* create a new object with start-end marker, simplified interface. + * Must be declared here because YYLTYPE is not known before + */ +static pval *npval2(pvaltype type, YYLTYPE *first, YYLTYPE *last); + +/* another frontend for npval, this time for a string */ +static pval *nword(char *string, YYLTYPE *pos); + +/* update end position of an object, return the object */ +static pval *update_last(pval *, YYLTYPE *); + + +/* Line 221 of yacc.c. */ +#line 283 "ael.tab.c" + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#elif (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +typedef signed char yytype_int8; +#else +typedef short int yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include <stddef.h> /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if YYENABLE_NLS +# if ENABLE_NLS +# include <libintl.h> /* INFRINGES ON USER NAME SPACE */ +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(e) ((void) (e)) +#else +# define YYUSE(e) /* empty */ +#endif + +/* Identity function, used to suppress warnings about constant conditions. */ +#ifndef lint +# define YYID(n) (n) +#else +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static int +YYID (int i) +#else +static int +YYID (i) + int i; +#endif +{ + return i; +} +#endif + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include <alloca.h> /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include <malloc.h> /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ +# ifndef _STDLIB_H +# define _STDLIB_H 1 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# ifdef __cplusplus +extern "C" { +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifdef __cplusplus +} +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \ + && defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss; + YYSTYPE yyvs; + YYLTYPE yyls; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + + 2 * YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (YYID (0)) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack, Stack, yysize); \ + Stack = &yyptr->Stack; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (YYID (0)) + +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 17 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 327 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 44 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 56 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 142 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 288 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 298 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const yytype_uint16 yyprhs[] = +{ + 0, 0, 3, 5, 7, 10, 13, 15, 17, 19, + 21, 23, 25, 32, 34, 35, 37, 40, 43, 52, + 57, 58, 61, 64, 65, 71, 72, 79, 80, 82, + 86, 89, 90, 93, 96, 98, 100, 102, 104, 106, + 108, 110, 113, 115, 120, 124, 130, 135, 143, 152, + 153, 156, 159, 165, 167, 175, 176, 181, 184, 187, + 192, 194, 197, 199, 202, 206, 210, 212, 215, 219, + 221, 224, 228, 234, 238, 240, 242, 246, 250, 253, + 254, 255, 256, 269, 273, 275, 279, 282, 285, 286, + 292, 295, 298, 301, 305, 307, 310, 311, 313, 317, + 321, 327, 333, 339, 345, 346, 349, 352, 357, 358, + 364, 368, 369, 373, 377, 380, 382, 383, 385, 386, + 390, 391, 394, 399, 403, 408, 409, 412, 414, 416, + 422, 427, 432, 433, 437, 443, 446, 448, 452, 455, + 459, 462, 467 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yytype_int8 yyrhs[] = +{ + 45, 0, -1, 46, -1, 47, -1, 46, 47, -1, + 46, 1, -1, 49, -1, 51, -1, 52, -1, 8, + -1, 43, -1, 37, -1, 50, 3, 48, 4, 59, + 5, -1, 23, -1, -1, 24, -1, 24, 23, -1, + 23, 24, -1, 15, 43, 6, 58, 7, 4, 92, + 5, -1, 16, 4, 53, 5, -1, -1, 54, 53, + -1, 1, 53, -1, -1, 43, 9, 55, 43, 8, + -1, -1, 42, 43, 9, 57, 43, 8, -1, -1, + 43, -1, 58, 10, 43, -1, 58, 1, -1, -1, + 60, 59, -1, 1, 59, -1, 62, -1, 99, -1, + 94, -1, 95, -1, 61, -1, 54, -1, 56, -1, + 43, 1, -1, 8, -1, 17, 25, 43, 8, -1, + 43, 25, 74, -1, 43, 14, 43, 25, 74, -1, + 31, 43, 25, 74, -1, 32, 6, 70, 7, 43, + 25, 74, -1, 31, 32, 6, 70, 7, 43, 25, + 74, -1, -1, 74, 63, -1, 1, 63, -1, 71, + 11, 71, 11, 71, -1, 43, -1, 64, 13, 71, + 13, 71, 13, 71, -1, -1, 6, 67, 69, 7, + -1, 19, 66, -1, 22, 66, -1, 20, 6, 65, + 7, -1, 43, -1, 43, 43, -1, 43, -1, 70, + 43, -1, 70, 11, 43, -1, 70, 12, 43, -1, + 43, -1, 43, 43, -1, 43, 43, 43, -1, 43, + -1, 43, 43, -1, 72, 11, 43, -1, 18, 66, + 4, 90, 5, -1, 4, 63, 5, -1, 54, -1, + 56, -1, 26, 80, 8, -1, 27, 82, 8, -1, + 43, 11, -1, -1, -1, -1, 33, 6, 75, 43, + 8, 76, 43, 8, 77, 43, 7, 74, -1, 34, + 66, 74, -1, 73, -1, 12, 83, 8, -1, 87, + 8, -1, 43, 8, -1, -1, 87, 9, 78, 43, + 8, -1, 29, 8, -1, 28, 8, -1, 30, 8, + -1, 68, 74, 79, -1, 8, -1, 21, 74, -1, + -1, 72, -1, 72, 13, 72, -1, 72, 10, 72, + -1, 72, 13, 72, 13, 72, -1, 72, 10, 72, + 10, 72, -1, 37, 13, 72, 13, 72, -1, 37, + 10, 72, 10, 72, -1, -1, 10, 43, -1, 72, + 81, -1, 72, 81, 14, 48, -1, -1, 43, 6, + 84, 89, 7, -1, 43, 6, 7, -1, -1, 43, + 6, 86, -1, 85, 89, 7, -1, 85, 7, -1, + 43, -1, -1, 69, -1, -1, 89, 10, 88, -1, + -1, 91, 90, -1, 35, 43, 11, 63, -1, 37, + 11, 63, -1, 36, 43, 11, 63, -1, -1, 93, + 92, -1, 74, -1, 99, -1, 38, 43, 4, 63, + 5, -1, 39, 4, 96, 5, -1, 40, 4, 96, + 5, -1, -1, 43, 8, 96, -1, 43, 14, 43, + 8, 96, -1, 1, 96, -1, 48, -1, 48, 13, + 65, -1, 97, 8, -1, 98, 97, 8, -1, 98, + 1, -1, 41, 4, 98, 5, -1, 41, 4, 5, + -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const yytype_uint16 yyrline[] = +{ + 0, 186, 186, 189, 190, 191, 194, 195, 196, 197, + 200, 201, 204, 213, 214, 215, 216, 217, 220, 226, + 232, 233, 234, 237, 237, 243, 243, 250, 251, 252, + 253, 256, 257, 258, 261, 262, 263, 264, 265, 266, + 267, 268, 269, 272, 277, 281, 289, 294, 299, 308, + 309, 310, 316, 321, 325, 333, 333, 337, 340, 343, + 354, 355, 362, 363, 367, 371, 377, 378, 383, 391, + 392, 396, 402, 411, 414, 415, 416, 419, 422, 425, + 426, 427, 425, 433, 437, 438, 439, 440, 443, 443, + 476, 477, 478, 479, 483, 486, 487, 490, 491, 494, + 497, 501, 505, 509, 515, 516, 520, 523, 529, 529, + 534, 542, 542, 553, 560, 563, 564, 567, 568, 571, + 574, 575, 578, 582, 586, 592, 593, 596, 597, 598, + 604, 609, 614, 615, 616, 618, 621, 622, 629, 630, + 631, 634, 637 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "KW_CONTEXT", "LC", "RC", "LP", "RP", + "SEMI", "EQ", "COMMA", "COLON", "AMPER", "BAR", "AT", "KW_MACRO", + "KW_GLOBALS", "KW_IGNOREPAT", "KW_SWITCH", "KW_IF", "KW_IFTIME", + "KW_ELSE", "KW_RANDOM", "KW_ABSTRACT", "KW_EXTEND", "EXTENMARK", + "KW_GOTO", "KW_JUMP", "KW_RETURN", "KW_BREAK", "KW_CONTINUE", + "KW_REGEXTEN", "KW_HINT", "KW_FOR", "KW_WHILE", "KW_CASE", "KW_PATTERN", + "KW_DEFAULT", "KW_CATCH", "KW_SWITCHES", "KW_ESWITCHES", "KW_INCLUDES", + "KW_LOCAL", "word", "$accept", "file", "objects", "object", + "context_name", "context", "opt_abstract", "macro", "globals", + "global_statements", "assignment", "@1", "local_assignment", "@2", + "arglist", "elements", "element", "ignorepat", "extension", "statements", + "timerange", "timespec", "test_expr", "@3", "if_like_head", "word_list", + "hint_word", "word3_list", "goto_word", "switch_statement", "statement", + "@4", "@5", "@6", "@7", "opt_else", "target", "opt_pri", "jumptarget", + "macro_call", "@8", "application_call_head", "@9", "application_call", + "opt_word", "eval_arglist", "case_statements", "case_statement", + "macro_statements", "macro_statement", "switches", "eswitches", + "switchlist", "included_entry", "includeslist", "includes", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, + 295, 296, 297, 298 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 44, 45, 46, 46, 46, 47, 47, 47, 47, + 48, 48, 49, 50, 50, 50, 50, 50, 51, 52, + 53, 53, 53, 55, 54, 57, 56, 58, 58, 58, + 58, 59, 59, 59, 60, 60, 60, 60, 60, 60, + 60, 60, 60, 61, 62, 62, 62, 62, 62, 63, + 63, 63, 64, 64, 65, 67, 66, 68, 68, 68, + 69, 69, 70, 70, 70, 70, 71, 71, 71, 72, + 72, 72, 73, 74, 74, 74, 74, 74, 74, 75, + 76, 77, 74, 74, 74, 74, 74, 74, 78, 74, + 74, 74, 74, 74, 74, 79, 79, 80, 80, 80, + 80, 80, 80, 80, 81, 81, 82, 82, 84, 83, + 83, 86, 85, 87, 87, 88, 88, 89, 89, 89, + 90, 90, 91, 91, 91, 92, 92, 93, 93, 93, + 94, 95, 96, 96, 96, 96, 97, 97, 98, 98, + 98, 99, 99 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 1, 1, 2, 2, 1, 1, 1, 1, + 1, 1, 6, 1, 0, 1, 2, 2, 8, 4, + 0, 2, 2, 0, 5, 0, 6, 0, 1, 3, + 2, 0, 2, 2, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 4, 3, 5, 4, 7, 8, 0, + 2, 2, 5, 1, 7, 0, 4, 2, 2, 4, + 1, 2, 1, 2, 3, 3, 1, 2, 3, 1, + 2, 3, 5, 3, 1, 1, 3, 3, 2, 0, + 0, 0, 12, 3, 1, 3, 2, 2, 0, 5, + 2, 2, 2, 3, 1, 2, 0, 1, 3, 3, + 5, 5, 5, 5, 0, 2, 2, 4, 0, 5, + 3, 0, 3, 3, 2, 1, 0, 1, 0, 3, + 0, 2, 4, 3, 4, 0, 2, 1, 1, 5, + 4, 4, 0, 3, 5, 2, 1, 3, 2, 3, + 2, 4, 3 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 14, 9, 0, 0, 13, 15, 0, 0, 3, 6, + 0, 7, 8, 0, 0, 17, 16, 1, 5, 4, + 0, 27, 0, 0, 0, 0, 11, 10, 0, 28, + 0, 22, 23, 19, 21, 0, 30, 0, 0, 0, + 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, + 39, 40, 0, 0, 38, 34, 36, 37, 35, 125, + 29, 0, 33, 0, 0, 0, 0, 0, 0, 0, + 0, 41, 0, 0, 12, 32, 0, 94, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 74, 75, 0, 84, 127, 118, 0, 0, + 125, 128, 24, 0, 0, 0, 62, 0, 0, 0, + 0, 0, 142, 136, 0, 0, 25, 0, 44, 0, + 0, 0, 0, 0, 55, 0, 57, 0, 58, 0, + 69, 97, 0, 104, 0, 91, 90, 92, 79, 0, + 0, 111, 87, 78, 96, 114, 60, 117, 0, 86, + 88, 18, 126, 43, 0, 46, 0, 0, 0, 63, + 135, 0, 0, 130, 131, 0, 138, 140, 141, 0, + 0, 0, 51, 73, 50, 108, 85, 0, 120, 53, + 0, 0, 0, 0, 0, 70, 0, 0, 0, 76, + 0, 106, 77, 0, 83, 0, 112, 0, 93, 61, + 113, 116, 0, 0, 0, 64, 65, 133, 0, 137, + 139, 0, 45, 110, 118, 0, 0, 0, 0, 0, + 120, 67, 0, 59, 0, 0, 0, 99, 71, 98, + 105, 0, 0, 0, 95, 115, 119, 0, 0, 0, + 0, 26, 0, 56, 0, 0, 0, 72, 121, 68, + 66, 0, 0, 0, 0, 0, 0, 107, 80, 129, + 89, 0, 47, 134, 109, 0, 0, 123, 0, 0, + 103, 102, 101, 100, 0, 48, 122, 124, 0, 52, + 0, 0, 81, 54, 0, 0, 0, 82 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int16 yydefgoto[] = +{ + -1, 6, 7, 8, 113, 9, 10, 11, 12, 24, + 92, 39, 93, 170, 30, 52, 53, 54, 55, 120, + 180, 181, 125, 177, 94, 147, 107, 182, 131, 95, + 121, 193, 274, 284, 202, 198, 132, 191, 134, 123, + 214, 97, 196, 98, 236, 148, 219, 220, 99, 100, + 56, 57, 110, 114, 115, 58 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -219 +static const yytype_int16 yypact[] = +{ + 102, -219, -25, 40, 24, 29, 70, 249, -219, -219, + 74, -219, -219, 107, 11, -219, -219, -219, -219, -219, + -6, 65, 11, 137, 148, 11, -219, -219, 152, -219, + 87, -219, -219, -219, -219, 123, -219, 173, 126, 136, + 123, -219, 159, -11, 186, 190, 194, 196, 158, 86, + -219, -219, 203, 123, -219, -219, -219, -219, -219, 177, + -219, 201, -219, 170, 208, 191, 179, 12, 12, 19, + 214, -219, 181, 213, -219, -219, 115, -219, 183, 222, + 222, 223, 222, 36, 187, 226, 228, 229, 232, 222, + 202, 182, -219, -219, 213, -219, -219, 16, 113, 239, + 177, -219, -219, 240, 179, 213, -219, 22, 12, 101, + 246, 248, -219, 241, 250, 10, -219, 234, -219, 56, + 255, 56, 256, 253, -219, 259, -219, 224, -219, 26, + 225, 157, 258, 119, 261, -219, -219, -219, -219, 213, + 266, -219, -219, -219, 254, -219, 231, -219, 59, -219, + -219, -219, -219, -219, 60, -219, 233, 235, 236, -219, + -219, 12, 237, -219, -219, 224, -219, -219, -219, 263, + 238, 213, -219, -219, -219, 270, -219, 242, 124, -1, + 269, 276, 273, 187, 187, -219, 187, 243, 187, -219, + 244, 274, -219, 247, -219, 115, -219, 213, -219, -219, + -219, 251, 252, 257, 264, -219, -219, -219, 283, -219, + -219, 284, -219, -219, 242, 286, 260, 262, 285, 292, + 124, 265, 267, -219, 267, 172, 15, 176, -219, 165, + -219, -6, 290, 294, -219, -219, -219, 293, 277, 213, + 12, -219, 129, -219, 295, 296, 56, -219, -219, -219, + 268, 291, 298, 187, 187, 187, 187, -219, -219, -219, + -219, 213, -219, -219, -219, 56, 56, -219, 267, 267, + 301, 301, 301, 301, 271, -219, -219, -219, 300, -219, + 307, 267, -219, -219, 275, 309, 213, -219 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int16 yypgoto[] = +{ + -219, -219, -219, 310, -19, -219, -219, -219, -219, 125, + 5, -219, -15, -219, -219, -31, -219, -219, -219, -114, + -219, 154, 25, -219, -219, 143, 217, -218, -82, -219, + -59, -219, -219, -219, -219, -219, -219, -219, -219, -219, + -219, -219, -219, -219, -219, 108, 103, -219, 227, -219, + -219, -219, -65, 209, -219, -51 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -133 +static const yytype_int16 yytable[] = +{ + 96, 28, 133, 111, 251, 172, 252, 174, 101, 62, + -66, 167, 22, 108, 118, 168, -20, -132, 13, 25, + 51, 64, 75, 145, 112, 51, 187, 25, 254, 156, + 25, 26, 65, 157, 158, 144, 183, 27, 51, 184, + 50, 96, 221, 160, 14, 50, 155, 26, 15, 101, + 278, 279, 16, 27, 23, 109, 26, 119, 50, 146, + 76, -49, 27, 283, 77, 159, 200, 203, 78, 201, + 17, 157, 158, 129, 79, 80, 81, 20, 82, 130, + 194, 233, 83, 84, 85, 86, 87, 71, 36, 88, + 89, -49, -49, -49, 37, 32, 207, 38, 48, 91, + 72, 225, 226, 159, 227, 126, 229, 128, 29, 161, + 1, 73, 212, 21, 139, 162, 119, 2, 3, 76, + -49, 149, 150, 77, 40, 4, 5, 78, -31, 190, + 187, 41, 267, 79, 80, 81, 264, 82, 234, 201, + 42, 83, 84, 85, 86, 87, 32, 31, 88, 89, + 34, 276, 277, 33, 43, 44, 35, 48, 91, 216, + 217, 218, 45, 46, 47, 48, 49, 186, 187, 60, + 188, 270, 271, 272, 273, 263, 187, 59, 256, 61, + 262, 76, 253, 187, 63, 77, 255, 187, 141, 78, + 142, 32, 66, 143, 67, 79, 80, 81, 68, 82, + 69, 70, 275, 83, 84, 85, 86, 87, 74, 102, + 88, 89, 257, 103, 104, 90, 105, 76, 47, 48, + 91, 77, 106, 116, 117, 78, 122, 287, 124, 127, + 130, 79, 80, 81, 135, 82, 136, 137, 138, 83, + 84, 85, 86, 87, 151, 140, 88, 89, 153, -2, + 18, 163, -14, 164, 165, 48, 91, 1, 166, 171, + 173, 176, 175, 178, 2, 3, 189, 179, 185, 192, + 195, 210, 4, 5, 199, 197, 204, 213, 205, 206, + 208, 211, 222, 223, 224, 146, 228, 230, 231, 239, + 232, 240, 241, 243, 235, 237, 246, 247, 258, 259, + 238, 260, 261, 244, 268, 245, 265, 266, 249, 269, + 250, 221, 187, 281, 280, 282, 286, 19, 285, 209, + 215, 154, 242, 248, 169, 0, 0, 152 +}; + +static const yytype_int16 yycheck[] = +{ + 59, 20, 84, 68, 222, 119, 224, 121, 59, 40, + 11, 1, 1, 1, 73, 5, 5, 5, 43, 14, + 35, 32, 53, 7, 5, 40, 11, 22, 13, 7, + 25, 37, 43, 11, 12, 94, 10, 43, 53, 13, + 35, 100, 43, 108, 4, 40, 105, 37, 24, 100, + 268, 269, 23, 43, 43, 43, 37, 1, 53, 43, + 4, 5, 43, 281, 8, 43, 7, 7, 12, 10, + 0, 11, 12, 37, 18, 19, 20, 3, 22, 43, + 139, 195, 26, 27, 28, 29, 30, 1, 1, 33, + 34, 35, 36, 37, 7, 9, 161, 10, 42, 43, + 14, 183, 184, 43, 186, 80, 188, 82, 43, 8, + 8, 25, 171, 6, 89, 14, 1, 15, 16, 4, + 5, 8, 9, 8, 1, 23, 24, 12, 5, 10, + 11, 8, 246, 18, 19, 20, 7, 22, 197, 10, + 17, 26, 27, 28, 29, 30, 9, 22, 33, 34, + 25, 265, 266, 5, 31, 32, 4, 42, 43, 35, + 36, 37, 39, 40, 41, 42, 43, 10, 11, 43, + 13, 253, 254, 255, 256, 240, 11, 4, 13, 43, + 239, 4, 10, 11, 25, 8, 10, 11, 6, 12, + 8, 9, 6, 11, 4, 18, 19, 20, 4, 22, + 4, 43, 261, 26, 27, 28, 29, 30, 5, 8, + 33, 34, 231, 43, 6, 38, 25, 4, 41, 42, + 43, 8, 43, 9, 43, 12, 43, 286, 6, 6, + 43, 18, 19, 20, 8, 22, 8, 8, 6, 26, + 27, 28, 29, 30, 5, 43, 33, 34, 8, 0, + 1, 5, 3, 5, 13, 42, 43, 8, 8, 25, + 5, 8, 6, 4, 15, 16, 8, 43, 43, 8, + 4, 8, 23, 24, 43, 21, 43, 7, 43, 43, + 43, 43, 13, 7, 11, 43, 43, 43, 14, 25, + 43, 8, 8, 7, 43, 43, 11, 5, 8, 5, + 43, 8, 25, 43, 13, 43, 11, 11, 43, 11, + 43, 43, 11, 13, 43, 8, 7, 7, 43, 165, + 177, 104, 214, 220, 115, -1, -1, 100 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 8, 15, 16, 23, 24, 45, 46, 47, 49, + 50, 51, 52, 43, 4, 24, 23, 0, 1, 47, + 3, 6, 1, 43, 53, 54, 37, 43, 48, 43, + 58, 53, 9, 5, 53, 4, 1, 7, 10, 55, + 1, 8, 17, 31, 32, 39, 40, 41, 42, 43, + 54, 56, 59, 60, 61, 62, 94, 95, 99, 4, + 43, 43, 59, 25, 32, 43, 6, 4, 4, 4, + 43, 1, 14, 25, 5, 59, 4, 8, 12, 18, + 19, 20, 22, 26, 27, 28, 29, 30, 33, 34, + 38, 43, 54, 56, 68, 73, 74, 85, 87, 92, + 93, 99, 8, 43, 6, 25, 43, 70, 1, 43, + 96, 96, 5, 48, 97, 98, 9, 43, 74, 1, + 63, 74, 43, 83, 6, 66, 66, 6, 66, 37, + 43, 72, 80, 72, 82, 8, 8, 8, 6, 66, + 43, 6, 8, 11, 74, 7, 43, 69, 89, 8, + 9, 5, 92, 8, 70, 74, 7, 11, 12, 43, + 96, 8, 14, 5, 5, 13, 8, 1, 5, 97, + 57, 25, 63, 5, 63, 6, 8, 67, 4, 43, + 64, 65, 71, 10, 13, 43, 10, 11, 13, 8, + 10, 81, 8, 75, 74, 4, 86, 21, 79, 43, + 7, 10, 78, 7, 43, 43, 43, 96, 43, 65, + 8, 43, 74, 7, 84, 69, 35, 36, 37, 90, + 91, 43, 13, 7, 11, 72, 72, 72, 43, 72, + 43, 14, 43, 63, 74, 43, 88, 43, 43, 25, + 8, 8, 89, 7, 43, 43, 11, 5, 90, 43, + 43, 71, 71, 10, 13, 10, 13, 48, 8, 5, + 8, 25, 74, 96, 7, 11, 11, 63, 13, 11, + 72, 72, 72, 72, 76, 74, 63, 63, 71, 71, + 43, 13, 8, 71, 77, 43, 7, 74 +}; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ + +#define YYFAIL goto yyerrlab + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK (1); \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (&yylloc, parseio, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (YYID (0)) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (YYID (N)) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (YYID (0)) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (&yylval, &yylloc, YYLEX_PARAM) +#else +# define YYLEX yylex (&yylval, &yylloc) +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include <stdio.h> /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (YYID (0)) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value, Location, parseio); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (YYID (0)) + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, const YYSTYPE * const yyvaluep, const YYLTYPE * const yylocationp, struct parse_io *parseio) +#else +static void +yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, parseio) + FILE *yyoutput; + int yytype; + const YYSTYPE * const yyvaluep; + const YYLTYPE * const yylocationp; + struct parse_io *parseio; +#endif +{ + if (!yyvaluep) + return; + YYUSE (yylocationp); + YYUSE (parseio); +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# else + YYUSE (yyoutput); +# endif + switch (yytype) + { + default: + break; + } +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_symbol_print (FILE *yyoutput, int yytype, const YYSTYPE * const yyvaluep, const YYLTYPE * const yylocationp, struct parse_io *parseio) +#else +static void +yy_symbol_print (yyoutput, yytype, yyvaluep, yylocationp, parseio) + FILE *yyoutput; + int yytype; + const YYSTYPE * const yyvaluep; + const YYLTYPE * const yylocationp; + struct parse_io *parseio; +#endif +{ + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + YY_LOCATION_PRINT (yyoutput, *yylocationp); + YYFPRINTF (yyoutput, ": "); + yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp, parseio); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_stack_print (yytype_int16 *bottom, yytype_int16 *top) +#else +static void +yy_stack_print (bottom, top) + yytype_int16 *bottom; + yytype_int16 *top; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (; bottom <= top; ++bottom) + YYFPRINTF (stderr, " %d", *bottom); + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (YYID (0)) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yy_reduce_print (YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule, struct parse_io *parseio) +#else +static void +yy_reduce_print (yyvsp, yylsp, yyrule, parseio) + YYSTYPE *yyvsp; + YYLTYPE *yylsp; + int yyrule; + struct parse_io *parseio; +#endif +{ + int yynrhs = yyr2[yyrule]; + int yyi; + unsigned long int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + fprintf (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], + &(yyvsp[(yyi + 1) - (yynrhs)]) + , &(yylsp[(yyi + 1) - (yynrhs)]) , parseio); + fprintf (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyvsp, yylsp, Rule, parseio); \ +} while (YYID (0)) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static YYSIZE_T +yystrlen (const char *yystr) +#else +static YYSIZE_T +yystrlen (yystr) + const char *yystr; +#endif +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static char * +yystpcpy (char *yydest, const char *yysrc) +#else +static char * +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +#endif +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + size_t yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into YYRESULT an error message about the unexpected token + YYCHAR while in state YYSTATE. Return the number of bytes copied, + including the terminating null byte. If YYRESULT is null, do not + copy anything; just return the number of bytes that would be + copied. As a special case, return 0 if an ordinary "syntax error" + message will do. Return YYSIZE_MAXIMUM if overflow occurs during + size calculation. */ +static YYSIZE_T +yysyntax_error (char *yyresult, int yystate, int yychar) +{ + int yyn = yypact[yystate]; + + if (! (YYPACT_NINF < yyn && yyn < YYLAST)) + return 0; + else + { + int yytype = YYTRANSLATE (yychar); + YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); + YYSIZE_T yysize = yysize0; + YYSIZE_T yysize1; + int yysize_overflow = 0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + int yyx; + +# if 0 + /* This is so xgettext sees the translatable formats that are + constructed on the fly. */ + YY_("syntax error, unexpected %s"); + YY_("syntax error, unexpected %s, expecting %s"); + YY_("syntax error, unexpected %s, expecting %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s"); + YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); +# endif + char *yyfmt; + char const *yyf; + static char const yyunexpected[] = "syntax error, unexpected %s"; + static char const yyexpecting[] = ", expecting %s"; + static char const yyor[] = " or %s"; + char yyformat[sizeof yyunexpected + + sizeof yyexpecting - 1 + + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) + * (sizeof yyor - 1))]; + char const *yyprefix = yyexpecting; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 1; + + yyarg[0] = yytname[yytype]; + yyfmt = yystpcpy (yyformat, yyunexpected); + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + yyformat[sizeof yyunexpected - 1] = '\0'; + break; + } + yyarg[yycount++] = yytname[yyx]; + yysize1 = yysize + yytnamerr (0, yytname[yyx]); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + yyfmt = yystpcpy (yyfmt, yyprefix); + yyprefix = yyor; + } + + yyf = YY_(yyformat); + yysize1 = yysize + yystrlen (yyf); + yysize_overflow |= (yysize1 < yysize); + yysize = yysize1; + + if (yysize_overflow) + return YYSIZE_MAXIMUM; + + if (yyresult) + { + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + char *yyp = yyresult; + int yyi = 0; + while ((*yyp = *yyf) != '\0') + { + if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyf += 2; + } + else + { + yyp++; + yyf++; + } + } + } + return yysize; + } +} +#endif /* YYERROR_VERBOSE */ + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +/*ARGSUSED*/ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp, struct parse_io *parseio) +#else +static void +yydestruct (yymsg, yytype, yyvaluep, yylocationp, parseio) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; + YYLTYPE *yylocationp; + struct parse_io *parseio; +#endif +{ + YYUSE (yyvaluep); + YYUSE (yylocationp); + YYUSE (parseio); + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + case 43: /* "word" */ +#line 178 "ael.y" + { free((yyvaluep->str));}; +#line 1456 "ael.tab.c" + break; + case 46: /* "objects" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1464 "ael.tab.c" + break; + case 47: /* "object" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1472 "ael.tab.c" + break; + case 48: /* "context_name" */ +#line 178 "ael.y" + { free((yyvaluep->str));}; +#line 1477 "ael.tab.c" + break; + case 49: /* "context" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1485 "ael.tab.c" + break; + case 51: /* "macro" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1493 "ael.tab.c" + break; + case 52: /* "globals" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1501 "ael.tab.c" + break; + case 53: /* "global_statements" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1509 "ael.tab.c" + break; + case 54: /* "assignment" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1517 "ael.tab.c" + break; + case 56: /* "local_assignment" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1525 "ael.tab.c" + break; + case 58: /* "arglist" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1533 "ael.tab.c" + break; + case 59: /* "elements" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1541 "ael.tab.c" + break; + case 60: /* "element" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1549 "ael.tab.c" + break; + case 61: /* "ignorepat" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1557 "ael.tab.c" + break; + case 62: /* "extension" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1565 "ael.tab.c" + break; + case 63: /* "statements" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1573 "ael.tab.c" + break; + case 64: /* "timerange" */ +#line 178 "ael.y" + { free((yyvaluep->str));}; +#line 1578 "ael.tab.c" + break; + case 65: /* "timespec" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1586 "ael.tab.c" + break; + case 66: /* "test_expr" */ +#line 178 "ael.y" + { free((yyvaluep->str));}; +#line 1591 "ael.tab.c" + break; + case 68: /* "if_like_head" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1599 "ael.tab.c" + break; + case 69: /* "word_list" */ +#line 178 "ael.y" + { free((yyvaluep->str));}; +#line 1604 "ael.tab.c" + break; + case 71: /* "word3_list" */ +#line 178 "ael.y" + { free((yyvaluep->str));}; +#line 1609 "ael.tab.c" + break; + case 72: /* "goto_word" */ +#line 178 "ael.y" + { free((yyvaluep->str));}; +#line 1614 "ael.tab.c" + break; + case 73: /* "switch_statement" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1622 "ael.tab.c" + break; + case 74: /* "statement" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1630 "ael.tab.c" + break; + case 79: /* "opt_else" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1638 "ael.tab.c" + break; + case 80: /* "target" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1646 "ael.tab.c" + break; + case 81: /* "opt_pri" */ +#line 178 "ael.y" + { free((yyvaluep->str));}; +#line 1651 "ael.tab.c" + break; + case 82: /* "jumptarget" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1659 "ael.tab.c" + break; + case 83: /* "macro_call" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1667 "ael.tab.c" + break; + case 85: /* "application_call_head" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1675 "ael.tab.c" + break; + case 87: /* "application_call" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1683 "ael.tab.c" + break; + case 88: /* "opt_word" */ +#line 178 "ael.y" + { free((yyvaluep->str));}; +#line 1688 "ael.tab.c" + break; + case 89: /* "eval_arglist" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1696 "ael.tab.c" + break; + case 90: /* "case_statements" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1704 "ael.tab.c" + break; + case 91: /* "case_statement" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1712 "ael.tab.c" + break; + case 92: /* "macro_statements" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1720 "ael.tab.c" + break; + case 93: /* "macro_statement" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1728 "ael.tab.c" + break; + case 94: /* "switches" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1736 "ael.tab.c" + break; + case 95: /* "eswitches" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1744 "ael.tab.c" + break; + case 96: /* "switchlist" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1752 "ael.tab.c" + break; + case 97: /* "included_entry" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1760 "ael.tab.c" + break; + case 98: /* "includeslist" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1768 "ael.tab.c" + break; + case 99: /* "includes" */ +#line 165 "ael.y" + { + destroy_pval((yyvaluep->pval)); + prev_word=0; + }; +#line 1776 "ael.tab.c" + break; + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ + +#ifdef YYPARSE_PARAM +#if defined __STDC__ || defined __cplusplus +int yyparse (void *YYPARSE_PARAM); +#else +int yyparse (); +#endif +#else /* ! YYPARSE_PARAM */ +#if defined __STDC__ || defined __cplusplus +int yyparse (struct parse_io *parseio); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + + + + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (void *YYPARSE_PARAM) +#else +int +yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +#endif +#else /* ! YYPARSE_PARAM */ +#if (defined __STDC__ || defined __C99__FUNC__ \ + || defined __cplusplus || defined _MSC_VER) +int +yyparse (struct parse_io *parseio) +#else +int +yyparse (parseio) + struct parse_io *parseio; +#endif +#endif +{ + /* The look-ahead symbol. */ +int yychar; + +/* The semantic value of the look-ahead symbol. */ +YYSTYPE yylval; + +/* Number of syntax errors so far. */ +int yynerrs; +/* Location data for the look-ahead symbol. */ +YYLTYPE yylloc; + + int yystate; + int yyn; + int yyresult; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + /* Look-ahead token as an internal (translated) token number. */ + int yytoken = 0; +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + + /* Three stacks and their tools: + `yyss': related to states, + `yyvs': related to semantic values, + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss = yyssa; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp; + + /* The location stack. */ + YYLTYPE yylsa[YYINITDEPTH]; + YYLTYPE *yyls = yylsa; + YYLTYPE *yylsp; + /* The locations where the error started and ended. */ + YYLTYPE yyerror_range[2]; + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) + + YYSIZE_T yystacksize = YYINITDEPTH; + + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + YYLTYPE yyloc; + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss; + yyvsp = yyvs; + yylsp = yyls; +#if YYLTYPE_IS_TRIVIAL + /* Initialize the default location before parsing starts. */ + yylloc.first_line = yylloc.last_line = 1; + yylloc.first_column = yylloc.last_column = 0; +#endif + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + YYLTYPE *yyls1 = yyls; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yyls1, yysize * sizeof (*yylsp), + &yystacksize); + yyls = yyls1; + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss); + YYSTACK_RELOCATE (yyvs); + YYSTACK_RELOCATE (yyls); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + yylsp = yyls + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + look-ahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to look-ahead token. */ + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a look-ahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + if (yyn == YYFINAL) + YYACCEPT; + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the look-ahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + yystate = yyn; + *++yyvsp = yylval; + *++yylsp = yylloc; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + /* Default location. */ + YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: +#line 186 "ael.y" + { (yyval.pval) = parseio->pval = (yyvsp[(1) - (1)].pval); ;} + break; + + case 3: +#line 189 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (1)].pval);;} + break; + + case 4: +#line 190 "ael.y" + { (yyval.pval) = linku1((yyvsp[(1) - (2)].pval), (yyvsp[(2) - (2)].pval)); ;} + break; + + case 5: +#line 191 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (2)].pval);;} + break; + + case 6: +#line 194 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (1)].pval);;} + break; + + case 7: +#line 195 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (1)].pval);;} + break; + + case 8: +#line 196 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (1)].pval);;} + break; + + case 9: +#line 197 "ael.y" + {(yyval.pval)=0;/* allow older docs to be read */;} + break; + + case 10: +#line 200 "ael.y" + { (yyval.str) = (yyvsp[(1) - (1)].str); ;} + break; + + case 11: +#line 201 "ael.y" + { (yyval.str) = strdup("default"); ;} + break; + + case 12: +#line 204 "ael.y" + { + (yyval.pval) = npval2(PV_CONTEXT, &(yylsp[(1) - (6)]), &(yylsp[(6) - (6)])); + (yyval.pval)->u1.str = (yyvsp[(3) - (6)].str); + (yyval.pval)->u2.statements = (yyvsp[(5) - (6)].pval); + set_dads((yyval.pval),(yyvsp[(5) - (6)].pval)); + (yyval.pval)->u3.abstract = (yyvsp[(1) - (6)].intval);;} + break; + + case 13: +#line 213 "ael.y" + { (yyval.intval) = 1; ;} + break; + + case 14: +#line 214 "ael.y" + { (yyval.intval) = 0; ;} + break; + + case 15: +#line 215 "ael.y" + { (yyval.intval) = 2; ;} + break; + + case 16: +#line 216 "ael.y" + { (yyval.intval)=3; ;} + break; + + case 17: +#line 217 "ael.y" + { (yyval.intval)=3; ;} + break; + + case 18: +#line 220 "ael.y" + { + (yyval.pval) = npval2(PV_MACRO, &(yylsp[(1) - (8)]), &(yylsp[(8) - (8)])); + (yyval.pval)->u1.str = (yyvsp[(2) - (8)].str); (yyval.pval)->u2.arglist = (yyvsp[(4) - (8)].pval); (yyval.pval)->u3.macro_statements = (yyvsp[(7) - (8)].pval); + set_dads((yyval.pval),(yyvsp[(7) - (8)].pval));;} + break; + + case 19: +#line 226 "ael.y" + { + (yyval.pval) = npval2(PV_GLOBALS, &(yylsp[(1) - (4)]), &(yylsp[(4) - (4)])); + (yyval.pval)->u1.statements = (yyvsp[(3) - (4)].pval); + set_dads((yyval.pval),(yyvsp[(3) - (4)].pval));;} + break; + + case 20: +#line 232 "ael.y" + { (yyval.pval) = NULL; ;} + break; + + case 21: +#line 233 "ael.y" + {(yyval.pval) = linku1((yyvsp[(1) - (2)].pval), (yyvsp[(2) - (2)].pval)); ;} + break; + + case 22: +#line 234 "ael.y" + {(yyval.pval)=(yyvsp[(2) - (2)].pval);;} + break; + + case 23: +#line 237 "ael.y" + { reset_semicount(parseio->scanner); ;} + break; + + case 24: +#line 237 "ael.y" + { + (yyval.pval) = npval2(PV_VARDEC, &(yylsp[(1) - (5)]), &(yylsp[(5) - (5)])); + (yyval.pval)->u1.str = (yyvsp[(1) - (5)].str); + (yyval.pval)->u2.val = (yyvsp[(4) - (5)].str); ;} + break; + + case 25: +#line 243 "ael.y" + { reset_semicount(parseio->scanner); ;} + break; + + case 26: +#line 243 "ael.y" + { + (yyval.pval) = npval2(PV_LOCALVARDEC, &(yylsp[(1) - (6)]), &(yylsp[(6) - (6)])); + (yyval.pval)->u1.str = (yyvsp[(2) - (6)].str); + (yyval.pval)->u2.val = (yyvsp[(5) - (6)].str); ;} + break; + + case 27: +#line 250 "ael.y" + { (yyval.pval) = NULL; ;} + break; + + case 28: +#line 251 "ael.y" + { (yyval.pval) = nword((yyvsp[(1) - (1)].str), &(yylsp[(1) - (1)])); ;} + break; + + case 29: +#line 252 "ael.y" + { (yyval.pval) = linku1((yyvsp[(1) - (3)].pval), nword((yyvsp[(3) - (3)].str), &(yylsp[(3) - (3)]))); ;} + break; + + case 30: +#line 253 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (2)].pval);;} + break; + + case 31: +#line 256 "ael.y" + {(yyval.pval)=0;;} + break; + + case 32: +#line 257 "ael.y" + { (yyval.pval) = linku1((yyvsp[(1) - (2)].pval), (yyvsp[(2) - (2)].pval)); ;} + break; + + case 33: +#line 258 "ael.y" + { (yyval.pval)=(yyvsp[(2) - (2)].pval);;} + break; + + case 34: +#line 261 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (1)].pval);;} + break; + + case 35: +#line 262 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (1)].pval);;} + break; + + case 36: +#line 263 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (1)].pval);;} + break; + + case 37: +#line 264 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (1)].pval);;} + break; + + case 38: +#line 265 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (1)].pval);;} + break; + + case 39: +#line 266 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (1)].pval);;} + break; + + case 40: +#line 267 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (1)].pval);;} + break; + + case 41: +#line 268 "ael.y" + {free((yyvsp[(1) - (2)].str)); (yyval.pval)=0;;} + break; + + case 42: +#line 269 "ael.y" + {(yyval.pval)=0;/* allow older docs to be read */;} + break; + + case 43: +#line 272 "ael.y" + { + (yyval.pval) = npval2(PV_IGNOREPAT, &(yylsp[(1) - (4)]), &(yylsp[(4) - (4)])); + (yyval.pval)->u1.str = (yyvsp[(3) - (4)].str);;} + break; + + case 44: +#line 277 "ael.y" + { + (yyval.pval) = npval2(PV_EXTENSION, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)])); + (yyval.pval)->u1.str = (yyvsp[(1) - (3)].str); + (yyval.pval)->u2.statements = (yyvsp[(3) - (3)].pval); set_dads((yyval.pval),(yyvsp[(3) - (3)].pval));;} + break; + + case 45: +#line 281 "ael.y" + { + (yyval.pval) = npval2(PV_EXTENSION, &(yylsp[(1) - (5)]), &(yylsp[(3) - (5)])); + (yyval.pval)->u1.str = malloc(strlen((yyvsp[(1) - (5)].str))+strlen((yyvsp[(3) - (5)].str))+2); + strcpy((yyval.pval)->u1.str,(yyvsp[(1) - (5)].str)); + strcat((yyval.pval)->u1.str,"@"); + strcat((yyval.pval)->u1.str,(yyvsp[(3) - (5)].str)); + free((yyvsp[(1) - (5)].str)); + (yyval.pval)->u2.statements = (yyvsp[(5) - (5)].pval); set_dads((yyval.pval),(yyvsp[(5) - (5)].pval));;} + break; + + case 46: +#line 289 "ael.y" + { + (yyval.pval) = npval2(PV_EXTENSION, &(yylsp[(1) - (4)]), &(yylsp[(4) - (4)])); + (yyval.pval)->u1.str = (yyvsp[(2) - (4)].str); + (yyval.pval)->u2.statements = (yyvsp[(4) - (4)].pval); set_dads((yyval.pval),(yyvsp[(4) - (4)].pval)); + (yyval.pval)->u4.regexten=1;;} + break; + + case 47: +#line 294 "ael.y" + { + (yyval.pval) = npval2(PV_EXTENSION, &(yylsp[(1) - (7)]), &(yylsp[(7) - (7)])); + (yyval.pval)->u1.str = (yyvsp[(5) - (7)].str); + (yyval.pval)->u2.statements = (yyvsp[(7) - (7)].pval); set_dads((yyval.pval),(yyvsp[(7) - (7)].pval)); + (yyval.pval)->u3.hints = (yyvsp[(3) - (7)].str);;} + break; + + case 48: +#line 299 "ael.y" + { + (yyval.pval) = npval2(PV_EXTENSION, &(yylsp[(1) - (8)]), &(yylsp[(8) - (8)])); + (yyval.pval)->u1.str = (yyvsp[(6) - (8)].str); + (yyval.pval)->u2.statements = (yyvsp[(8) - (8)].pval); set_dads((yyval.pval),(yyvsp[(8) - (8)].pval)); + (yyval.pval)->u4.regexten=1; + (yyval.pval)->u3.hints = (yyvsp[(4) - (8)].str);;} + break; + + case 49: +#line 308 "ael.y" + { (yyval.pval) = NULL; ;} + break; + + case 50: +#line 309 "ael.y" + { (yyval.pval) = linku1((yyvsp[(1) - (2)].pval), (yyvsp[(2) - (2)].pval)); ;} + break; + + case 51: +#line 310 "ael.y" + {(yyval.pval)=(yyvsp[(2) - (2)].pval);;} + break; + + case 52: +#line 316 "ael.y" + { + asprintf(&(yyval.str), "%s:%s:%s", (yyvsp[(1) - (5)].str), (yyvsp[(3) - (5)].str), (yyvsp[(5) - (5)].str)); + free((yyvsp[(1) - (5)].str)); + free((yyvsp[(3) - (5)].str)); + free((yyvsp[(5) - (5)].str)); ;} + break; + + case 53: +#line 321 "ael.y" + { (yyval.str) = (yyvsp[(1) - (1)].str); ;} + break; + + case 54: +#line 325 "ael.y" + { + (yyval.pval) = nword((yyvsp[(1) - (7)].str), &(yylsp[(1) - (7)])); + (yyval.pval)->next = nword((yyvsp[(3) - (7)].str), &(yylsp[(3) - (7)])); + (yyval.pval)->next->next = nword((yyvsp[(5) - (7)].str), &(yylsp[(5) - (7)])); + (yyval.pval)->next->next->next = nword((yyvsp[(7) - (7)].str), &(yylsp[(7) - (7)])); ;} + break; + + case 55: +#line 333 "ael.y" + { reset_parencount(parseio->scanner); ;} + break; + + case 56: +#line 333 "ael.y" + { (yyval.str) = (yyvsp[(3) - (4)].str); ;} + break; + + case 57: +#line 337 "ael.y" + { + (yyval.pval)= npval2(PV_IF, &(yylsp[(1) - (2)]), &(yylsp[(2) - (2)])); + (yyval.pval)->u1.str = (yyvsp[(2) - (2)].str); ;} + break; + + case 58: +#line 340 "ael.y" + { + (yyval.pval) = npval2(PV_RANDOM, &(yylsp[(1) - (2)]), &(yylsp[(2) - (2)])); + (yyval.pval)->u1.str=(yyvsp[(2) - (2)].str);;} + break; + + case 59: +#line 343 "ael.y" + { + (yyval.pval) = npval2(PV_IFTIME, &(yylsp[(1) - (4)]), &(yylsp[(4) - (4)])); + (yyval.pval)->u1.list = (yyvsp[(3) - (4)].pval); + prev_word = 0; ;} + break; + + case 60: +#line 354 "ael.y" + { (yyval.str) = (yyvsp[(1) - (1)].str);;} + break; + + case 61: +#line 355 "ael.y" + { + asprintf(&((yyval.str)), "%s%s", (yyvsp[(1) - (2)].str), (yyvsp[(2) - (2)].str)); + free((yyvsp[(1) - (2)].str)); + free((yyvsp[(2) - (2)].str)); + prev_word = (yyval.str);;} + break; + + case 62: +#line 362 "ael.y" + { (yyval.str) = (yyvsp[(1) - (1)].str); ;} + break; + + case 63: +#line 363 "ael.y" + { + asprintf(&((yyval.str)), "%s %s", (yyvsp[(1) - (2)].str), (yyvsp[(2) - (2)].str)); + free((yyvsp[(1) - (2)].str)); + free((yyvsp[(2) - (2)].str)); ;} + break; + + case 64: +#line 367 "ael.y" + { + asprintf(&((yyval.str)), "%s:%s", (yyvsp[(1) - (3)].str), (yyvsp[(3) - (3)].str)); + free((yyvsp[(1) - (3)].str)); + free((yyvsp[(3) - (3)].str)); ;} + break; + + case 65: +#line 371 "ael.y" + { /* there are often '&' in hints */ + asprintf(&((yyval.str)), "%s&%s", (yyvsp[(1) - (3)].str), (yyvsp[(3) - (3)].str)); + free((yyvsp[(1) - (3)].str)); + free((yyvsp[(3) - (3)].str));;} + break; + + case 66: +#line 377 "ael.y" + { (yyval.str) = (yyvsp[(1) - (1)].str);;} + break; + + case 67: +#line 378 "ael.y" + { + asprintf(&((yyval.str)), "%s%s", (yyvsp[(1) - (2)].str), (yyvsp[(2) - (2)].str)); + free((yyvsp[(1) - (2)].str)); + free((yyvsp[(2) - (2)].str)); + prev_word = (yyval.str);;} + break; + + case 68: +#line 383 "ael.y" + { + asprintf(&((yyval.str)), "%s%s%s", (yyvsp[(1) - (3)].str), (yyvsp[(2) - (3)].str), (yyvsp[(3) - (3)].str)); + free((yyvsp[(1) - (3)].str)); + free((yyvsp[(2) - (3)].str)); + free((yyvsp[(3) - (3)].str)); + prev_word=(yyval.str);;} + break; + + case 69: +#line 391 "ael.y" + { (yyval.str) = (yyvsp[(1) - (1)].str);;} + break; + + case 70: +#line 392 "ael.y" + { + asprintf(&((yyval.str)), "%s%s", (yyvsp[(1) - (2)].str), (yyvsp[(2) - (2)].str)); + free((yyvsp[(1) - (2)].str)); + free((yyvsp[(2) - (2)].str));;} + break; + + case 71: +#line 396 "ael.y" + { + asprintf(&((yyval.str)), "%s:%s", (yyvsp[(1) - (3)].str), (yyvsp[(3) - (3)].str)); + free((yyvsp[(1) - (3)].str)); + free((yyvsp[(3) - (3)].str));;} + break; + + case 72: +#line 402 "ael.y" + { + (yyval.pval) = npval2(PV_SWITCH, &(yylsp[(1) - (5)]), &(yylsp[(5) - (5)])); + (yyval.pval)->u1.str = (yyvsp[(2) - (5)].str); + (yyval.pval)->u2.statements = (yyvsp[(4) - (5)].pval); set_dads((yyval.pval),(yyvsp[(4) - (5)].pval));;} + break; + + case 73: +#line 411 "ael.y" + { + (yyval.pval) = npval2(PV_STATEMENTBLOCK, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)])); + (yyval.pval)->u1.list = (yyvsp[(2) - (3)].pval); set_dads((yyval.pval),(yyvsp[(2) - (3)].pval));;} + break; + + case 74: +#line 414 "ael.y" + { (yyval.pval) = (yyvsp[(1) - (1)].pval); ;} + break; + + case 75: +#line 415 "ael.y" + { (yyval.pval) = (yyvsp[(1) - (1)].pval); ;} + break; + + case 76: +#line 416 "ael.y" + { + (yyval.pval) = npval2(PV_GOTO, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)])); + (yyval.pval)->u1.list = (yyvsp[(2) - (3)].pval);;} + break; + + case 77: +#line 419 "ael.y" + { + (yyval.pval) = npval2(PV_GOTO, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)])); + (yyval.pval)->u1.list = (yyvsp[(2) - (3)].pval);;} + break; + + case 78: +#line 422 "ael.y" + { + (yyval.pval) = npval2(PV_LABEL, &(yylsp[(1) - (2)]), &(yylsp[(2) - (2)])); + (yyval.pval)->u1.str = (yyvsp[(1) - (2)].str); ;} + break; + + case 79: +#line 425 "ael.y" + {reset_semicount(parseio->scanner);;} + break; + + case 80: +#line 426 "ael.y" + {reset_semicount(parseio->scanner);;} + break; + + case 81: +#line 427 "ael.y" + {reset_parencount(parseio->scanner);;} + break; + + case 82: +#line 427 "ael.y" + { /* XXX word_list maybe ? */ + (yyval.pval) = npval2(PV_FOR, &(yylsp[(1) - (12)]), &(yylsp[(12) - (12)])); + (yyval.pval)->u1.for_init = (yyvsp[(4) - (12)].str); + (yyval.pval)->u2.for_test=(yyvsp[(7) - (12)].str); + (yyval.pval)->u3.for_inc = (yyvsp[(10) - (12)].str); + (yyval.pval)->u4.for_statements = (yyvsp[(12) - (12)].pval); set_dads((yyval.pval),(yyvsp[(12) - (12)].pval));;} + break; + + case 83: +#line 433 "ael.y" + { + (yyval.pval) = npval2(PV_WHILE, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)])); + (yyval.pval)->u1.str = (yyvsp[(2) - (3)].str); + (yyval.pval)->u2.statements = (yyvsp[(3) - (3)].pval); set_dads((yyval.pval),(yyvsp[(3) - (3)].pval));;} + break; + + case 84: +#line 437 "ael.y" + { (yyval.pval) = (yyvsp[(1) - (1)].pval); ;} + break; + + case 85: +#line 438 "ael.y" + { (yyval.pval) = update_last((yyvsp[(2) - (3)].pval), &(yylsp[(2) - (3)])); ;} + break; + + case 86: +#line 439 "ael.y" + { (yyval.pval) = update_last((yyvsp[(1) - (2)].pval), &(yylsp[(2) - (2)])); ;} + break; + + case 87: +#line 440 "ael.y" + { + (yyval.pval)= npval2(PV_APPLICATION_CALL, &(yylsp[(1) - (2)]), &(yylsp[(2) - (2)])); + (yyval.pval)->u1.str = (yyvsp[(1) - (2)].str);;} + break; + + case 88: +#line 443 "ael.y" + {reset_semicount(parseio->scanner);;} + break; + + case 89: +#line 443 "ael.y" + { + char *bufx; + int tot=0; + pval *pptr; + (yyval.pval) = npval2(PV_VARDEC, &(yylsp[(1) - (5)]), &(yylsp[(5) - (5)])); + (yyval.pval)->u2.val=(yyvsp[(4) - (5)].str); + /* rebuild the original string-- this is not an app call, it's an unwrapped vardec, with a func call on the LHS */ + /* string to big to fit in the buffer? */ + tot+=strlen((yyvsp[(1) - (5)].pval)->u1.str); + for(pptr=(yyvsp[(1) - (5)].pval)->u2.arglist;pptr;pptr=pptr->next) { + tot+=strlen(pptr->u1.str); + tot++; /* for a sep like a comma */ + } + tot+=4; /* for safety */ + bufx = calloc(1, tot); + strcpy(bufx,(yyvsp[(1) - (5)].pval)->u1.str); + strcat(bufx,"("); + /* XXX need to advance the pointer or the loop is very inefficient */ + for (pptr=(yyvsp[(1) - (5)].pval)->u2.arglist;pptr;pptr=pptr->next) { + if ( pptr != (yyvsp[(1) - (5)].pval)->u2.arglist ) + strcat(bufx,","); + strcat(bufx,pptr->u1.str); + } + strcat(bufx,")"); +#ifdef AAL_ARGCHECK + if ( !ael_is_funcname((yyvsp[(1) - (5)].pval)->u1.str) ) + ast_log(LOG_WARNING, "==== File: %s, Line %d, Cols: %d-%d: Function call? The name %s is not in my internal list of function names\n", + my_file, (yylsp[(1) - (5)]).first_line, (yylsp[(1) - (5)]).first_column, (yylsp[(1) - (5)]).last_column, (yyvsp[(1) - (5)].pval)->u1.str); +#endif + (yyval.pval)->u1.str = bufx; + destroy_pval((yyvsp[(1) - (5)].pval)); /* the app call it is not, get rid of that chain */ + prev_word = 0; + ;} + break; + + case 90: +#line 476 "ael.y" + { (yyval.pval) = npval2(PV_BREAK, &(yylsp[(1) - (2)]), &(yylsp[(2) - (2)])); ;} + break; + + case 91: +#line 477 "ael.y" + { (yyval.pval) = npval2(PV_RETURN, &(yylsp[(1) - (2)]), &(yylsp[(2) - (2)])); ;} + break; + + case 92: +#line 478 "ael.y" + { (yyval.pval) = npval2(PV_CONTINUE, &(yylsp[(1) - (2)]), &(yylsp[(2) - (2)])); ;} + break; + + case 93: +#line 479 "ael.y" + { + (yyval.pval) = update_last((yyvsp[(1) - (3)].pval), &(yylsp[(2) - (3)])); + (yyval.pval)->u2.statements = (yyvsp[(2) - (3)].pval); set_dads((yyval.pval),(yyvsp[(2) - (3)].pval)); + (yyval.pval)->u3.else_statements = (yyvsp[(3) - (3)].pval);set_dads((yyval.pval),(yyvsp[(3) - (3)].pval));;} + break; + + case 94: +#line 483 "ael.y" + { (yyval.pval)=0; ;} + break; + + case 95: +#line 486 "ael.y" + { (yyval.pval) = (yyvsp[(2) - (2)].pval); ;} + break; + + case 96: +#line 487 "ael.y" + { (yyval.pval) = NULL ; ;} + break; + + case 97: +#line 490 "ael.y" + { (yyval.pval) = nword((yyvsp[(1) - (1)].str), &(yylsp[(1) - (1)])); ;} + break; + + case 98: +#line 491 "ael.y" + { + (yyval.pval) = nword((yyvsp[(1) - (3)].str), &(yylsp[(1) - (3)])); + (yyval.pval)->next = nword((yyvsp[(3) - (3)].str), &(yylsp[(3) - (3)])); ;} + break; + + case 99: +#line 494 "ael.y" + { + (yyval.pval) = nword((yyvsp[(1) - (3)].str), &(yylsp[(1) - (3)])); + (yyval.pval)->next = nword((yyvsp[(3) - (3)].str), &(yylsp[(3) - (3)])); ;} + break; + + case 100: +#line 497 "ael.y" + { + (yyval.pval) = nword((yyvsp[(1) - (5)].str), &(yylsp[(1) - (5)])); + (yyval.pval)->next = nword((yyvsp[(3) - (5)].str), &(yylsp[(3) - (5)])); + (yyval.pval)->next->next = nword((yyvsp[(5) - (5)].str), &(yylsp[(5) - (5)])); ;} + break; + + case 101: +#line 501 "ael.y" + { + (yyval.pval) = nword((yyvsp[(1) - (5)].str), &(yylsp[(1) - (5)])); + (yyval.pval)->next = nword((yyvsp[(3) - (5)].str), &(yylsp[(3) - (5)])); + (yyval.pval)->next->next = nword((yyvsp[(5) - (5)].str), &(yylsp[(5) - (5)])); ;} + break; + + case 102: +#line 505 "ael.y" + { + (yyval.pval) = nword(strdup("default"), &(yylsp[(1) - (5)])); + (yyval.pval)->next = nword((yyvsp[(3) - (5)].str), &(yylsp[(3) - (5)])); + (yyval.pval)->next->next = nword((yyvsp[(5) - (5)].str), &(yylsp[(5) - (5)])); ;} + break; + + case 103: +#line 509 "ael.y" + { + (yyval.pval) = nword(strdup("default"), &(yylsp[(1) - (5)])); + (yyval.pval)->next = nword((yyvsp[(3) - (5)].str), &(yylsp[(3) - (5)])); + (yyval.pval)->next->next = nword((yyvsp[(5) - (5)].str), &(yylsp[(5) - (5)])); ;} + break; + + case 104: +#line 515 "ael.y" + { (yyval.str) = strdup("1"); ;} + break; + + case 105: +#line 516 "ael.y" + { (yyval.str) = (yyvsp[(2) - (2)].str); ;} + break; + + case 106: +#line 520 "ael.y" + { /* ext[, pri] default 1 */ + (yyval.pval) = nword((yyvsp[(1) - (2)].str), &(yylsp[(1) - (2)])); + (yyval.pval)->next = nword((yyvsp[(2) - (2)].str), &(yylsp[(2) - (2)])); ;} + break; + + case 107: +#line 523 "ael.y" + { /* context, ext, pri */ + (yyval.pval) = nword((yyvsp[(4) - (4)].str), &(yylsp[(4) - (4)])); + (yyval.pval)->next = nword((yyvsp[(1) - (4)].str), &(yylsp[(1) - (4)])); + (yyval.pval)->next->next = nword((yyvsp[(2) - (4)].str), &(yylsp[(2) - (4)])); ;} + break; + + case 108: +#line 529 "ael.y" + {reset_argcount(parseio->scanner);;} + break; + + case 109: +#line 529 "ael.y" + { + /* XXX original code had @2 but i think we need @5 */ + (yyval.pval) = npval2(PV_MACRO_CALL, &(yylsp[(1) - (5)]), &(yylsp[(5) - (5)])); + (yyval.pval)->u1.str = (yyvsp[(1) - (5)].str); + (yyval.pval)->u2.arglist = (yyvsp[(4) - (5)].pval);;} + break; + + case 110: +#line 534 "ael.y" + { + (yyval.pval)= npval2(PV_MACRO_CALL, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)])); + (yyval.pval)->u1.str = (yyvsp[(1) - (3)].str); ;} + break; + + case 111: +#line 542 "ael.y" + {reset_argcount(parseio->scanner);;} + break; + + case 112: +#line 542 "ael.y" + { + if (strcasecmp((yyvsp[(1) - (3)].str),"goto") == 0) { + (yyval.pval) = npval2(PV_GOTO, &(yylsp[(1) - (3)]), &(yylsp[(2) - (3)])); + free((yyvsp[(1) - (3)].str)); /* won't be using this */ + ast_log(LOG_WARNING, "==== File: %s, Line %d, Cols: %d-%d: Suggestion: Use the goto statement instead of the Goto() application call in AEL.\n", my_file, (yylsp[(1) - (3)]).first_line, (yylsp[(1) - (3)]).first_column, (yylsp[(1) - (3)]).last_column ); + } else { + (yyval.pval)= npval2(PV_APPLICATION_CALL, &(yylsp[(1) - (3)]), &(yylsp[(2) - (3)])); + (yyval.pval)->u1.str = (yyvsp[(1) - (3)].str); + } ;} + break; + + case 113: +#line 553 "ael.y" + { + (yyval.pval) = update_last((yyvsp[(1) - (3)].pval), &(yylsp[(3) - (3)])); + if( (yyval.pval)->type == PV_GOTO ) + (yyval.pval)->u1.list = (yyvsp[(2) - (3)].pval); + else + (yyval.pval)->u2.arglist = (yyvsp[(2) - (3)].pval); + ;} + break; + + case 114: +#line 560 "ael.y" + { (yyval.pval) = update_last((yyvsp[(1) - (2)].pval), &(yylsp[(2) - (2)])); ;} + break; + + case 115: +#line 563 "ael.y" + { (yyval.str) = (yyvsp[(1) - (1)].str) ;} + break; + + case 116: +#line 564 "ael.y" + { (yyval.str) = strdup(""); ;} + break; + + case 117: +#line 567 "ael.y" + { (yyval.pval) = nword((yyvsp[(1) - (1)].str), &(yylsp[(1) - (1)])); ;} + break; + + case 118: +#line 568 "ael.y" + { + (yyval.pval)= npval(PV_WORD,0/*@1.first_line*/,0/*@1.last_line*/,0/* @1.first_column*/, 0/*@1.last_column*/); + (yyval.pval)->u1.str = strdup(""); ;} + break; + + case 119: +#line 571 "ael.y" + { (yyval.pval) = linku1((yyvsp[(1) - (3)].pval), nword((yyvsp[(3) - (3)].str), &(yylsp[(3) - (3)]))); ;} + break; + + case 120: +#line 574 "ael.y" + { (yyval.pval) = NULL; ;} + break; + + case 121: +#line 575 "ael.y" + { (yyval.pval) = linku1((yyvsp[(1) - (2)].pval), (yyvsp[(2) - (2)].pval)); ;} + break; + + case 122: +#line 578 "ael.y" + { + (yyval.pval) = npval2(PV_CASE, &(yylsp[(1) - (4)]), &(yylsp[(3) - (4)])); /* XXX 3 or 4 ? */ + (yyval.pval)->u1.str = (yyvsp[(2) - (4)].str); + (yyval.pval)->u2.statements = (yyvsp[(4) - (4)].pval); set_dads((yyval.pval),(yyvsp[(4) - (4)].pval));;} + break; + + case 123: +#line 582 "ael.y" + { + (yyval.pval) = npval2(PV_DEFAULT, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)])); + (yyval.pval)->u1.str = NULL; + (yyval.pval)->u2.statements = (yyvsp[(3) - (3)].pval);set_dads((yyval.pval),(yyvsp[(3) - (3)].pval));;} + break; + + case 124: +#line 586 "ael.y" + { + (yyval.pval) = npval2(PV_PATTERN, &(yylsp[(1) - (4)]), &(yylsp[(4) - (4)])); /* XXX@3 or @4 ? */ + (yyval.pval)->u1.str = (yyvsp[(2) - (4)].str); + (yyval.pval)->u2.statements = (yyvsp[(4) - (4)].pval);set_dads((yyval.pval),(yyvsp[(4) - (4)].pval));;} + break; + + case 125: +#line 592 "ael.y" + { (yyval.pval) = NULL; ;} + break; + + case 126: +#line 593 "ael.y" + { (yyval.pval) = linku1((yyvsp[(1) - (2)].pval), (yyvsp[(2) - (2)].pval)); ;} + break; + + case 127: +#line 596 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (1)].pval);;} + break; + + case 128: +#line 597 "ael.y" + { (yyval.pval)=(yyvsp[(1) - (1)].pval);;} + break; + + case 129: +#line 598 "ael.y" + { + (yyval.pval) = npval2(PV_CATCH, &(yylsp[(1) - (5)]), &(yylsp[(5) - (5)])); + (yyval.pval)->u1.str = (yyvsp[(2) - (5)].str); + (yyval.pval)->u2.statements = (yyvsp[(4) - (5)].pval); set_dads((yyval.pval),(yyvsp[(4) - (5)].pval));;} + break; + + case 130: +#line 604 "ael.y" + { + (yyval.pval) = npval2(PV_SWITCHES, &(yylsp[(1) - (4)]), &(yylsp[(2) - (4)])); + (yyval.pval)->u1.list = (yyvsp[(3) - (4)].pval); set_dads((yyval.pval),(yyvsp[(3) - (4)].pval));;} + break; + + case 131: +#line 609 "ael.y" + { + (yyval.pval) = npval2(PV_ESWITCHES, &(yylsp[(1) - (4)]), &(yylsp[(2) - (4)])); + (yyval.pval)->u1.list = (yyvsp[(3) - (4)].pval); set_dads((yyval.pval),(yyvsp[(3) - (4)].pval));;} + break; + + case 132: +#line 614 "ael.y" + { (yyval.pval) = NULL; ;} + break; + + case 133: +#line 615 "ael.y" + { (yyval.pval) = linku1(nword((yyvsp[(1) - (3)].str), &(yylsp[(1) - (3)])), (yyvsp[(3) - (3)].pval)); ;} + break; + + case 134: +#line 616 "ael.y" + { char *x; asprintf(&x,"%s@%s", (yyvsp[(1) - (5)].str),(yyvsp[(3) - (5)].str)); free((yyvsp[(1) - (5)].str)); free((yyvsp[(3) - (5)].str)); + (yyval.pval) = linku1(nword(x, &(yylsp[(1) - (5)])), (yyvsp[(5) - (5)].pval));;} + break; + + case 135: +#line 618 "ael.y" + {(yyval.pval)=(yyvsp[(2) - (2)].pval);;} + break; + + case 136: +#line 621 "ael.y" + { (yyval.pval) = nword((yyvsp[(1) - (1)].str), &(yylsp[(1) - (1)])); ;} + break; + + case 137: +#line 622 "ael.y" + { + (yyval.pval) = nword((yyvsp[(1) - (3)].str), &(yylsp[(1) - (3)])); + (yyval.pval)->u2.arglist = (yyvsp[(3) - (3)].pval); + prev_word=0; /* XXX sure ? */ ;} + break; + + case 138: +#line 629 "ael.y" + { (yyval.pval) = (yyvsp[(1) - (2)].pval); ;} + break; + + case 139: +#line 630 "ael.y" + { (yyval.pval) = linku1((yyvsp[(1) - (3)].pval), (yyvsp[(2) - (3)].pval)); ;} + break; + + case 140: +#line 631 "ael.y" + {(yyval.pval)=(yyvsp[(1) - (2)].pval);;} + break; + + case 141: +#line 634 "ael.y" + { + (yyval.pval) = npval2(PV_INCLUDES, &(yylsp[(1) - (4)]), &(yylsp[(4) - (4)])); + (yyval.pval)->u1.list = (yyvsp[(3) - (4)].pval);set_dads((yyval.pval),(yyvsp[(3) - (4)].pval));;} + break; + + case 142: +#line 637 "ael.y" + { + (yyval.pval) = npval2(PV_INCLUDES, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)]));;} + break; + + +/* Line 1270 of yacc.c. */ +#line 3012 "ael.tab.c" + default: break; + } + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + *++yylsp = yyloc; + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (&yylloc, parseio, YY_("syntax error")); +#else + { + YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); + if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) + { + YYSIZE_T yyalloc = 2 * yysize; + if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) + yyalloc = YYSTACK_ALLOC_MAXIMUM; + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yyalloc); + if (yymsg) + yymsg_alloc = yyalloc; + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + } + } + + if (0 < yysize && yysize <= yymsg_alloc) + { + (void) yysyntax_error (yymsg, yystate, yychar); + yyerror (&yylloc, parseio, yymsg); + } + else + { + yyerror (&yylloc, parseio, YY_("syntax error")); + if (yysize != 0) + goto yyexhaustedlab; + } + } +#endif + } + + yyerror_range[0] = yylloc; + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse look-ahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, &yylloc, parseio); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse look-ahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + yyerror_range[0] = yylsp[1-yylen]; + /* Do not reclaim the symbols of the rule which action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + yyerror_range[0] = *yylsp; + yydestruct ("Error: popping", + yystos[yystate], yyvsp, yylsp, parseio); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + if (yyn == YYFINAL) + YYACCEPT; + + *++yyvsp = yylval; + + yyerror_range[1] = yylloc; + /* Using YYLLOC is tempting, but would change the location of + the look-ahead. YYLOC is available though. */ + YYLLOC_DEFAULT (yyloc, (yyerror_range - 1), 2); + *++yylsp = yyloc; + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#ifndef yyoverflow +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (&yylloc, parseio, YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEOF && yychar != YYEMPTY) + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, &yylloc, parseio); + /* Do not reclaim the symbols of the rule which action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp, yylsp, parseio); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + return yyresult; +} + + +#line 642 "ael.y" + + +static char *token_equivs1[] = +{ + "AMPER", + "AT", + "BAR", + "COLON", + "COMMA", + "EQ", + "EXTENMARK", + "KW_BREAK", + "KW_CASE", + "KW_CATCH", + "KW_CONTEXT", + "KW_CONTINUE", + "KW_DEFAULT", + "KW_ELSE", + "KW_ESWITCHES", + "KW_FOR", + "KW_GLOBALS", + "KW_GOTO", + "KW_HINT", + "KW_IFTIME", + "KW_IF", + "KW_IGNOREPAT", + "KW_INCLUDES" + "KW_JUMP", + "KW_MACRO", + "KW_PATTERN", + "KW_REGEXTEN", + "KW_RETURN", + "KW_SWITCHES", + "KW_SWITCH", + "KW_WHILE", + "LC", + "LP", + "RC", + "RP", + "SEMI", +}; + +static char *token_equivs2[] = +{ + "&", + "@", + "|", + ":", + ",", + "=", + "=>", + "break", + "case", + "catch", + "context", + "continue", + "default", + "else", + "eswitches", + "for", + "globals", + "goto", + "hint", + "ifTime", + "if", + "ignorepat", + "includes" + "jump", + "macro", + "pattern", + "regexten", + "return", + "switches", + "switch", + "while", + "{", + "(", + "}", + ")", + ";", +}; + + +static char *ael_token_subst(const char *mess) +{ + /* calc a length, malloc, fill, and return; yyerror had better free it! */ + int len=0,i; + const char *p; + char *res, *s,*t; + int token_equivs_entries = sizeof(token_equivs1)/sizeof(char*); + + for (p=mess; *p; p++) { + for (i=0; i<token_equivs_entries; i++) { + if ( strncmp(p,token_equivs1[i],strlen(token_equivs1[i])) == 0 ) + { + len+=strlen(token_equivs2[i])+2; + p += strlen(token_equivs1[i])-1; + break; + } + } + len++; + } + res = calloc(1, len+1); + res[0] = 0; + s = res; + for (p=mess; *p;) { + int found = 0; + for (i=0; i<token_equivs_entries; i++) { + if ( strncmp(p,token_equivs1[i],strlen(token_equivs1[i])) == 0 ) { + *s++ = '\''; + for (t=token_equivs2[i]; *t;) { + *s++ = *t++; + } + *s++ = '\''; + p += strlen(token_equivs1[i]); + found = 1; + break; + } + } + if( !found ) + *s++ = *p++; + } + *s++ = 0; + return res; +} + +void yyerror(YYLTYPE *locp, struct parse_io *parseio, char const *s) +{ + char *s2 = ael_token_subst((char *)s); + if (locp->first_line == locp->last_line) { + ast_log(LOG_ERROR, "==== File: %s, Line %d, Cols: %d-%d: Error: %s\n", my_file, locp->first_line, locp->first_column, locp->last_column, s2); + } else { + ast_log(LOG_ERROR, "==== File: %s, Line %d Col %d to Line %d Col %d: Error: %s\n", my_file, locp->first_line, locp->first_column, locp->last_line, locp->last_column, s2); + } + free(s2); + parseio->syntax_error_count++; +} + +struct pval *npval(pvaltype type, int first_line, int last_line, + int first_column, int last_column) +{ + pval *z = calloc(1, sizeof(struct pval)); + z->type = type; + z->startline = first_line; + z->endline = last_line; + z->startcol = first_column; + z->endcol = last_column; + z->filename = strdup(my_file); + return z; +} + +static struct pval *npval2(pvaltype type, YYLTYPE *first, YYLTYPE *last) +{ + return npval(type, first->first_line, last->last_line, + first->first_column, last->last_column); +} + +static struct pval *update_last(pval *obj, YYLTYPE *last) +{ + obj->endline = last->last_line; + obj->endcol = last->last_column; + return obj; +} + +/* frontend for npval to create a PV_WORD string from the given token */ +static pval *nword(char *string, YYLTYPE *pos) +{ + pval *p = npval2(PV_WORD, pos, pos); + if (p) + p->u1.str = string; + return p; +} + +/* this routine adds a dad ptr to each element in the list */ +static void set_dads(struct pval *dad, struct pval *child_list) +{ + struct pval *t; + + for(t=child_list;t;t=t->next) /* simple stuff */ + t->dad = dad; +} + + diff --git a/trunk/res/ael/ael.tab.h b/trunk/res/ael/ael.tab.h new file mode 100644 index 000000000..95b011852 --- /dev/null +++ b/trunk/res/ael/ael.tab.h @@ -0,0 +1,154 @@ +/* A Bison parser, made by GNU Bison 2.1a. */ + +/* Skeleton parser for Yacc-like parsing with Bison, + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +/* As a special exception, when this file is copied by Bison into a + Bison output file, you may use that output file without restriction. + This special exception was added by the Free Software Foundation + in version 1.24 of Bison. */ + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + KW_CONTEXT = 258, + LC = 259, + RC = 260, + LP = 261, + RP = 262, + SEMI = 263, + EQ = 264, + COMMA = 265, + COLON = 266, + AMPER = 267, + BAR = 268, + AT = 269, + KW_MACRO = 270, + KW_GLOBALS = 271, + KW_IGNOREPAT = 272, + KW_SWITCH = 273, + KW_IF = 274, + KW_IFTIME = 275, + KW_ELSE = 276, + KW_RANDOM = 277, + KW_ABSTRACT = 278, + KW_EXTEND = 279, + EXTENMARK = 280, + KW_GOTO = 281, + KW_JUMP = 282, + KW_RETURN = 283, + KW_BREAK = 284, + KW_CONTINUE = 285, + KW_REGEXTEN = 286, + KW_HINT = 287, + KW_FOR = 288, + KW_WHILE = 289, + KW_CASE = 290, + KW_PATTERN = 291, + KW_DEFAULT = 292, + KW_CATCH = 293, + KW_SWITCHES = 294, + KW_ESWITCHES = 295, + KW_INCLUDES = 296, + KW_LOCAL = 297, + word = 298 + }; +#endif +/* Tokens. */ +#define KW_CONTEXT 258 +#define LC 259 +#define RC 260 +#define LP 261 +#define RP 262 +#define SEMI 263 +#define EQ 264 +#define COMMA 265 +#define COLON 266 +#define AMPER 267 +#define BAR 268 +#define AT 269 +#define KW_MACRO 270 +#define KW_GLOBALS 271 +#define KW_IGNOREPAT 272 +#define KW_SWITCH 273 +#define KW_IF 274 +#define KW_IFTIME 275 +#define KW_ELSE 276 +#define KW_RANDOM 277 +#define KW_ABSTRACT 278 +#define KW_EXTEND 279 +#define EXTENMARK 280 +#define KW_GOTO 281 +#define KW_JUMP 282 +#define KW_RETURN 283 +#define KW_BREAK 284 +#define KW_CONTINUE 285 +#define KW_REGEXTEN 286 +#define KW_HINT 287 +#define KW_FOR 288 +#define KW_WHILE 289 +#define KW_CASE 290 +#define KW_PATTERN 291 +#define KW_DEFAULT 292 +#define KW_CATCH 293 +#define KW_SWITCHES 294 +#define KW_ESWITCHES 295 +#define KW_INCLUDES 296 +#define KW_LOCAL 297 +#define word 298 + + + + +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef union YYSTYPE +#line 54 "ael.y" +{ + int intval; /* integer value, typically flags */ + char *str; /* strings */ + struct pval *pval; /* full objects */ +} +/* Line 1536 of yacc.c. */ +#line 131 "ael.tab.h" + YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + + + +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +} YYLTYPE; +# define yyltype YYLTYPE /* obsolescent; will be withdrawn */ +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + + + diff --git a/trunk/res/ael/ael.y b/trunk/res/ael/ael.y new file mode 100644 index 000000000..a8df1cb51 --- /dev/null +++ b/trunk/res/ael/ael.y @@ -0,0 +1,823 @@ +%{ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2006, Digium, Inc. + * + * Steve Murphy <murf@parsetree.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ +/*! \file + * + * \brief Bison Grammar description of AEL2. + * + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "asterisk/logger.h" +#include "asterisk/ael_structs.h" + +pval * linku1(pval *head, pval *tail); +static void set_dads(pval *dad, pval *child_list); +void reset_parencount(yyscan_t yyscanner); +void reset_semicount(yyscan_t yyscanner); +void reset_argcount(yyscan_t yyscanner ); + +#define YYLEX_PARAM ((struct parse_io *)parseio)->scanner +#define YYERROR_VERBOSE 1 + +extern char *my_file; +#ifdef AAL_ARGCHECK +int ael_is_funcname(char *name); +#endif +static char *ael_token_subst(const char *mess); + +%} + + +%union { + int intval; /* integer value, typically flags */ + char *str; /* strings */ + struct pval *pval; /* full objects */ +} + +%{ + /* declaring these AFTER the union makes things a lot simpler! */ +void yyerror(YYLTYPE *locp, struct parse_io *parseio, char const *s); +int ael_yylex (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , void * yyscanner); + +/* create a new object with start-end marker */ +pval *npval(pvaltype type, int first_line, int last_line, + int first_column, int last_column); + +/* create a new object with start-end marker, simplified interface. + * Must be declared here because YYLTYPE is not known before + */ +static pval *npval2(pvaltype type, YYLTYPE *first, YYLTYPE *last); + +/* another frontend for npval, this time for a string */ +static pval *nword(char *string, YYLTYPE *pos); + +/* update end position of an object, return the object */ +static pval *update_last(pval *, YYLTYPE *); +%} + + +%token KW_CONTEXT LC RC LP RP SEMI EQ COMMA COLON AMPER BAR AT +%token KW_MACRO KW_GLOBALS KW_IGNOREPAT KW_SWITCH KW_IF KW_IFTIME KW_ELSE KW_RANDOM KW_ABSTRACT KW_EXTEND +%token EXTENMARK KW_GOTO KW_JUMP KW_RETURN KW_BREAK KW_CONTINUE KW_REGEXTEN KW_HINT +%token KW_FOR KW_WHILE KW_CASE KW_PATTERN KW_DEFAULT KW_CATCH KW_SWITCHES KW_ESWITCHES +%token KW_INCLUDES KW_LOCAL + +%right BAR COMMA + +%token <str> word + +%type <pval>includes +%type <pval>includeslist +%type <pval>switchlist +%type <pval>eswitches +%type <pval>switches +%type <pval>macro_statement +%type <pval>macro_statements +%type <pval>case_statement +%type <pval>case_statements +%type <pval>eval_arglist +%type <pval>application_call +%type <pval>application_call_head +%type <pval>macro_call +%type <pval>target jumptarget +%type <pval>statement +%type <pval>switch_statement + +%type <pval>if_like_head +%type <pval>statements +%type <pval>extension +%type <pval>ignorepat +%type <pval>element +%type <pval>elements +%type <pval>arglist +%type <pval>assignment +%type <pval>local_assignment +%type <pval>global_statements +%type <pval>globals +%type <pval>macro +%type <pval>context +%type <pval>object +%type <pval>objects +%type <pval>file +/* XXX lr changes */ +%type <pval>opt_else +%type <pval>timespec +%type <pval>included_entry + +%type <str>opt_word +%type <str>context_name +%type <str>timerange + +%type <str>goto_word +%type <str>word_list +%type <str>word3_list hint_word +%type <str>test_expr +%type <str>opt_pri + +%type <intval>opt_abstract + +/* + * OPTIONS + */ + +%locations /* track source location using @n variables (yylloc in flex) */ +%pure-parser /* pass yylval and yylloc as arguments to yylex(). */ +%name-prefix="ael_yy" +/* + * add an additional argument, parseio, to yyparse(), + * which is then accessible in the grammar actions + */ +%parse-param {struct parse_io *parseio} + +/* there will be two shift/reduce conflicts, they involve the if statement, where a single statement occurs not wrapped in curlies in the "true" section + the default action to shift will attach the else to the preceeding if. */ +%expect 3 +%error-verbose + +/* + * declare destructors for objects. + * The former is for pval, the latter for strings. + * NOTE: we must not have a destructor for a 'file' object. + */ +%destructor { + destroy_pval($$); + prev_word=0; + } includes includeslist switchlist eswitches switches + macro_statement macro_statements case_statement case_statements + eval_arglist application_call application_call_head + macro_call target jumptarget statement switch_statement + if_like_head statements extension + ignorepat element elements arglist assignment local_assignment + global_statements globals macro context object objects + opt_else + timespec included_entry + +%destructor { free($$);} word word_list goto_word word3_list opt_word context_name + timerange + test_expr + opt_pri + + +%% + +file : objects { $$ = parseio->pval = $1; } + ; + +objects : object {$$=$1;} + | objects object { $$ = linku1($1, $2); } + | objects error {$$=$1;} + ; + +object : context {$$=$1;} + | macro {$$=$1;} + | globals {$$=$1;} + | SEMI {$$=0;/* allow older docs to be read */} + ; + +context_name : word { $$ = $1; } + | KW_DEFAULT { $$ = strdup("default"); } + ; + +context : opt_abstract KW_CONTEXT context_name LC elements RC { + $$ = npval2(PV_CONTEXT, &@1, &@6); + $$->u1.str = $3; + $$->u2.statements = $5; + set_dads($$,$5); + $$->u3.abstract = $1;} + ; + +/* optional "abstract" keyword XXX there is no regression test for this */ +opt_abstract: KW_ABSTRACT { $$ = 1; } + | /* nothing */ { $$ = 0; } + | KW_EXTEND { $$ = 2; } + | KW_EXTEND KW_ABSTRACT { $$=3; } + | KW_ABSTRACT KW_EXTEND { $$=3; } + ; + +macro : KW_MACRO word LP arglist RP LC macro_statements RC { + $$ = npval2(PV_MACRO, &@1, &@8); + $$->u1.str = $2; $$->u2.arglist = $4; $$->u3.macro_statements = $7; + set_dads($$,$7);} + ; + +globals : KW_GLOBALS LC global_statements RC { + $$ = npval2(PV_GLOBALS, &@1, &@4); + $$->u1.statements = $3; + set_dads($$,$3);} + ; + +global_statements : { $$ = NULL; } + | assignment global_statements {$$ = linku1($1, $2); } + | error global_statements {$$=$2;} + ; + +assignment : word EQ { reset_semicount(parseio->scanner); } word SEMI { + $$ = npval2(PV_VARDEC, &@1, &@5); + $$->u1.str = $1; + $$->u2.val = $4; } + ; + +local_assignment : KW_LOCAL word EQ { reset_semicount(parseio->scanner); } word SEMI { + $$ = npval2(PV_LOCALVARDEC, &@1, &@6); + $$->u1.str = $2; + $$->u2.val = $5; } + ; + +/* XXX this matches missing arguments, is this desired ? */ +arglist : /* empty */ { $$ = NULL; } + | word { $$ = nword($1, &@1); } + | arglist COMMA word { $$ = linku1($1, nword($3, &@3)); } + | arglist error {$$=$1;} + ; + +elements : {$$=0;} + | element elements { $$ = linku1($1, $2); } + | error elements { $$=$2;} + ; + +element : extension {$$=$1;} + | includes {$$=$1;} + | switches {$$=$1;} + | eswitches {$$=$1;} + | ignorepat {$$=$1;} + | assignment {$$=$1;} + | local_assignment {$$=$1;} + | word error {free($1); $$=0;} + | SEMI {$$=0;/* allow older docs to be read */} + ; + +ignorepat : KW_IGNOREPAT EXTENMARK word SEMI { + $$ = npval2(PV_IGNOREPAT, &@1, &@4); + $$->u1.str = $3;} + ; + +extension : word EXTENMARK statement { + $$ = npval2(PV_EXTENSION, &@1, &@3); + $$->u1.str = $1; + $$->u2.statements = $3; set_dads($$,$3);} + | word AT word EXTENMARK statement { + $$ = npval2(PV_EXTENSION, &@1, &@3); + $$->u1.str = malloc(strlen($1)+strlen($3)+2); + strcpy($$->u1.str,$1); + strcat($$->u1.str,"@"); + strcat($$->u1.str,$3); + free($1); + $$->u2.statements = $5; set_dads($$,$5);} + | KW_REGEXTEN word EXTENMARK statement { + $$ = npval2(PV_EXTENSION, &@1, &@4); + $$->u1.str = $2; + $$->u2.statements = $4; set_dads($$,$4); + $$->u4.regexten=1;} + | KW_HINT LP hint_word RP word EXTENMARK statement { + $$ = npval2(PV_EXTENSION, &@1, &@7); + $$->u1.str = $5; + $$->u2.statements = $7; set_dads($$,$7); + $$->u3.hints = $3;} + | KW_REGEXTEN KW_HINT LP hint_word RP word EXTENMARK statement { + $$ = npval2(PV_EXTENSION, &@1, &@8); + $$->u1.str = $6; + $$->u2.statements = $8; set_dads($$,$8); + $$->u4.regexten=1; + $$->u3.hints = $4;} + ; + +/* list of statements in a block or after a case label - can be empty */ +statements : /* empty */ { $$ = NULL; } + | statement statements { $$ = linku1($1, $2); } + | error statements {$$=$2;} + ; + +/* hh:mm-hh:mm, due to the way the parser works we do not + * detect the '-' but only the ':' as separator + */ +timerange: word3_list COLON word3_list COLON word3_list { + asprintf(&$$, "%s:%s:%s", $1, $3, $5); + free($1); + free($3); + free($5); } + | word { $$ = $1; } + ; + +/* full time specification range|dow|*|* */ +timespec : timerange BAR word3_list BAR word3_list BAR word3_list { + $$ = nword($1, &@1); + $$->next = nword($3, &@3); + $$->next->next = nword($5, &@5); + $$->next->next->next = nword($7, &@7); } + ; + +/* expression used in if, random, while, switch */ +test_expr : LP { reset_parencount(parseio->scanner); } word_list RP { $$ = $3; } + ; + +/* 'if' like statements: if, iftime, random */ +if_like_head : KW_IF test_expr { + $$= npval2(PV_IF, &@1, &@2); + $$->u1.str = $2; } + | KW_RANDOM test_expr { + $$ = npval2(PV_RANDOM, &@1, &@2); + $$->u1.str=$2;} + | KW_IFTIME LP timespec RP { + $$ = npval2(PV_IFTIME, &@1, &@4); + $$->u1.list = $3; + prev_word = 0; } + ; + +/* word_list is a hack to fix a problem with context switching between bison and flex; + by the time you register a new context with flex, you've already got a look-ahead token + from the old context, with no way to put it back and start afresh. So, we kludge this + and merge the words back together. */ + +word_list : word { $$ = $1;} + | word word { + asprintf(&($$), "%s%s", $1, $2); + free($1); + free($2); + prev_word = $$;} + ; + +hint_word : word { $$ = $1; } + | hint_word word { + asprintf(&($$), "%s %s", $1, $2); + free($1); + free($2); } + | hint_word COLON word { + asprintf(&($$), "%s:%s", $1, $3); + free($1); + free($3); } + | hint_word AMPER word { /* there are often '&' in hints */ + asprintf(&($$), "%s&%s", $1, $3); + free($1); + free($3);} + + +word3_list : word { $$ = $1;} + | word word { + asprintf(&($$), "%s%s", $1, $2); + free($1); + free($2); + prev_word = $$;} + | word word word { + asprintf(&($$), "%s%s%s", $1, $2, $3); + free($1); + free($2); + free($3); + prev_word=$$;} + ; + +goto_word : word { $$ = $1;} + | word word { + asprintf(&($$), "%s%s", $1, $2); + free($1); + free($2);} + | goto_word COLON word { + asprintf(&($$), "%s:%s", $1, $3); + free($1); + free($3);} + ; + +switch_statement : KW_SWITCH test_expr LC case_statements RC { + $$ = npval2(PV_SWITCH, &@1, &@5); + $$->u1.str = $2; + $$->u2.statements = $4; set_dads($$,$4);} + ; + +/* + * Definition of a statememt in our language + */ +statement : LC statements RC { + $$ = npval2(PV_STATEMENTBLOCK, &@1, &@3); + $$->u1.list = $2; set_dads($$,$2);} + | assignment { $$ = $1; } + | local_assignment { $$ = $1; } + | KW_GOTO target SEMI { + $$ = npval2(PV_GOTO, &@1, &@3); + $$->u1.list = $2;} + | KW_JUMP jumptarget SEMI { + $$ = npval2(PV_GOTO, &@1, &@3); + $$->u1.list = $2;} + | word COLON { + $$ = npval2(PV_LABEL, &@1, &@2); + $$->u1.str = $1; } + | KW_FOR LP {reset_semicount(parseio->scanner);} word SEMI + {reset_semicount(parseio->scanner);} word SEMI + {reset_parencount(parseio->scanner);} word RP statement { /* XXX word_list maybe ? */ + $$ = npval2(PV_FOR, &@1, &@12); + $$->u1.for_init = $4; + $$->u2.for_test=$7; + $$->u3.for_inc = $10; + $$->u4.for_statements = $12; set_dads($$,$12);} + | KW_WHILE test_expr statement { + $$ = npval2(PV_WHILE, &@1, &@3); + $$->u1.str = $2; + $$->u2.statements = $3; set_dads($$,$3);} + | switch_statement { $$ = $1; } + | AMPER macro_call SEMI { $$ = update_last($2, &@2); } + | application_call SEMI { $$ = update_last($1, &@2); } + | word SEMI { + $$= npval2(PV_APPLICATION_CALL, &@1, &@2); + $$->u1.str = $1;} + | application_call EQ {reset_semicount(parseio->scanner);} word SEMI { + char *bufx; + int tot=0; + pval *pptr; + $$ = npval2(PV_VARDEC, &@1, &@5); + $$->u2.val=$4; + /* rebuild the original string-- this is not an app call, it's an unwrapped vardec, with a func call on the LHS */ + /* string to big to fit in the buffer? */ + tot+=strlen($1->u1.str); + for(pptr=$1->u2.arglist;pptr;pptr=pptr->next) { + tot+=strlen(pptr->u1.str); + tot++; /* for a sep like a comma */ + } + tot+=4; /* for safety */ + bufx = calloc(1, tot); + strcpy(bufx,$1->u1.str); + strcat(bufx,"("); + /* XXX need to advance the pointer or the loop is very inefficient */ + for (pptr=$1->u2.arglist;pptr;pptr=pptr->next) { + if ( pptr != $1->u2.arglist ) + strcat(bufx,","); + strcat(bufx,pptr->u1.str); + } + strcat(bufx,")"); +#ifdef AAL_ARGCHECK + if ( !ael_is_funcname($1->u1.str) ) + ast_log(LOG_WARNING, "==== File: %s, Line %d, Cols: %d-%d: Function call? The name %s is not in my internal list of function names\n", + my_file, @1.first_line, @1.first_column, @1.last_column, $1->u1.str); +#endif + $$->u1.str = bufx; + destroy_pval($1); /* the app call it is not, get rid of that chain */ + prev_word = 0; + } + | KW_BREAK SEMI { $$ = npval2(PV_BREAK, &@1, &@2); } + | KW_RETURN SEMI { $$ = npval2(PV_RETURN, &@1, &@2); } + | KW_CONTINUE SEMI { $$ = npval2(PV_CONTINUE, &@1, &@2); } + | if_like_head statement opt_else { + $$ = update_last($1, &@2); + $$->u2.statements = $2; set_dads($$,$2); + $$->u3.else_statements = $3;set_dads($$,$3);} + | SEMI { $$=0; } + ; + +opt_else : KW_ELSE statement { $$ = $2; } + | { $$ = NULL ; } + + +target : goto_word { $$ = nword($1, &@1); } + | goto_word BAR goto_word { + $$ = nword($1, &@1); + $$->next = nword($3, &@3); } + | goto_word COMMA goto_word { + $$ = nword($1, &@1); + $$->next = nword($3, &@3); } + | goto_word BAR goto_word BAR goto_word { + $$ = nword($1, &@1); + $$->next = nword($3, &@3); + $$->next->next = nword($5, &@5); } + | goto_word COMMA goto_word COMMA goto_word { + $$ = nword($1, &@1); + $$->next = nword($3, &@3); + $$->next->next = nword($5, &@5); } + | KW_DEFAULT BAR goto_word BAR goto_word { + $$ = nword(strdup("default"), &@1); + $$->next = nword($3, &@3); + $$->next->next = nword($5, &@5); } + | KW_DEFAULT COMMA goto_word COMMA goto_word { + $$ = nword(strdup("default"), &@1); + $$->next = nword($3, &@3); + $$->next->next = nword($5, &@5); } + ; + +opt_pri : /* empty */ { $$ = strdup("1"); } + | COMMA word { $$ = $2; } + ; + +/* XXX please document the form of jumptarget */ +jumptarget : goto_word opt_pri { /* ext[, pri] default 1 */ + $$ = nword($1, &@1); + $$->next = nword($2, &@2); } /* jump extension[,priority][@context] */ + | goto_word opt_pri AT context_name { /* context, ext, pri */ + $$ = nword($4, &@4); + $$->next = nword($1, &@1); + $$->next->next = nword($2, &@2); } + ; + +macro_call : word LP {reset_argcount(parseio->scanner);} eval_arglist RP { + /* XXX original code had @2 but i think we need @5 */ + $$ = npval2(PV_MACRO_CALL, &@1, &@5); + $$->u1.str = $1; + $$->u2.arglist = $4;} + | word LP RP { + $$= npval2(PV_MACRO_CALL, &@1, &@3); + $$->u1.str = $1; } + ; + +/* XXX application_call_head must be revised. Having 'word LP { ...' + * just as above should work fine, however it gives a different result. + */ +application_call_head: word LP {reset_argcount(parseio->scanner);} { + if (strcasecmp($1,"goto") == 0) { + $$ = npval2(PV_GOTO, &@1, &@2); + free($1); /* won't be using this */ + ast_log(LOG_WARNING, "==== File: %s, Line %d, Cols: %d-%d: Suggestion: Use the goto statement instead of the Goto() application call in AEL.\n", my_file, @1.first_line, @1.first_column, @1.last_column ); + } else { + $$= npval2(PV_APPLICATION_CALL, &@1, &@2); + $$->u1.str = $1; + } } + ; + +application_call : application_call_head eval_arglist RP { + $$ = update_last($1, &@3); + if( $$->type == PV_GOTO ) + $$->u1.list = $2; + else + $$->u2.arglist = $2; + } + | application_call_head RP { $$ = update_last($1, &@2); } + ; + +opt_word : word { $$ = $1 } + | { $$ = strdup(""); } + ; + +eval_arglist : word_list { $$ = nword($1, &@1); } + | /*nothing! */ { + $$= npval(PV_WORD,0/*@1.first_line*/,0/*@1.last_line*/,0/* @1.first_column*/, 0/*@1.last_column*/); + $$->u1.str = strdup(""); } + | eval_arglist COMMA opt_word { $$ = linku1($1, nword($3, &@3)); } + ; + +case_statements: /* empty */ { $$ = NULL; } + | case_statement case_statements { $$ = linku1($1, $2); } + ; + +case_statement: KW_CASE word COLON statements { + $$ = npval2(PV_CASE, &@1, &@3); /* XXX 3 or 4 ? */ + $$->u1.str = $2; + $$->u2.statements = $4; set_dads($$,$4);} + | KW_DEFAULT COLON statements { + $$ = npval2(PV_DEFAULT, &@1, &@3); + $$->u1.str = NULL; + $$->u2.statements = $3;set_dads($$,$3);} + | KW_PATTERN word COLON statements { + $$ = npval2(PV_PATTERN, &@1, &@4); /* XXX@3 or @4 ? */ + $$->u1.str = $2; + $$->u2.statements = $4;set_dads($$,$4);} + ; + +macro_statements: /* empty */ { $$ = NULL; } + | macro_statement macro_statements { $$ = linku1($1, $2); } + ; + +macro_statement : statement {$$=$1;} + | includes { $$=$1;} + | KW_CATCH word LC statements RC { + $$ = npval2(PV_CATCH, &@1, &@5); + $$->u1.str = $2; + $$->u2.statements = $4; set_dads($$,$4);} + ; + +switches : KW_SWITCHES LC switchlist RC { + $$ = npval2(PV_SWITCHES, &@1, &@2); + $$->u1.list = $3; set_dads($$,$3);} + ; + +eswitches : KW_ESWITCHES LC switchlist RC { + $$ = npval2(PV_ESWITCHES, &@1, &@2); + $$->u1.list = $3; set_dads($$,$3);} + ; + +switchlist : /* empty */ { $$ = NULL; } + | word SEMI switchlist { $$ = linku1(nword($1, &@1), $3); } + | word AT word SEMI switchlist { char *x; asprintf(&x,"%s@%s", $1,$3); free($1); free($3); + $$ = linku1(nword(x, &@1), $5);} + | error switchlist {$$=$2;} + ; + +included_entry : context_name { $$ = nword($1, &@1); } + | context_name BAR timespec { + $$ = nword($1, &@1); + $$->u2.arglist = $3; + prev_word=0; /* XXX sure ? */ } + ; + +/* list of ';' separated context names followed by optional timespec */ +includeslist : included_entry SEMI { $$ = $1; } + | includeslist included_entry SEMI { $$ = linku1($1, $2); } + | includeslist error {$$=$1;} + ; + +includes : KW_INCLUDES LC includeslist RC { + $$ = npval2(PV_INCLUDES, &@1, &@4); + $$->u1.list = $3;set_dads($$,$3);} + | KW_INCLUDES LC RC { + $$ = npval2(PV_INCLUDES, &@1, &@3);} + ; + + +%% + +static char *token_equivs1[] = +{ + "AMPER", + "AT", + "BAR", + "COLON", + "COMMA", + "EQ", + "EXTENMARK", + "KW_BREAK", + "KW_CASE", + "KW_CATCH", + "KW_CONTEXT", + "KW_CONTINUE", + "KW_DEFAULT", + "KW_ELSE", + "KW_ESWITCHES", + "KW_FOR", + "KW_GLOBALS", + "KW_GOTO", + "KW_HINT", + "KW_IFTIME", + "KW_IF", + "KW_IGNOREPAT", + "KW_INCLUDES" + "KW_JUMP", + "KW_MACRO", + "KW_PATTERN", + "KW_REGEXTEN", + "KW_RETURN", + "KW_SWITCHES", + "KW_SWITCH", + "KW_WHILE", + "LC", + "LP", + "RC", + "RP", + "SEMI", +}; + +static char *token_equivs2[] = +{ + "&", + "@", + "|", + ":", + ",", + "=", + "=>", + "break", + "case", + "catch", + "context", + "continue", + "default", + "else", + "eswitches", + "for", + "globals", + "goto", + "hint", + "ifTime", + "if", + "ignorepat", + "includes" + "jump", + "macro", + "pattern", + "regexten", + "return", + "switches", + "switch", + "while", + "{", + "(", + "}", + ")", + ";", +}; + + +static char *ael_token_subst(const char *mess) +{ + /* calc a length, malloc, fill, and return; yyerror had better free it! */ + int len=0,i; + const char *p; + char *res, *s,*t; + int token_equivs_entries = sizeof(token_equivs1)/sizeof(char*); + + for (p=mess; *p; p++) { + for (i=0; i<token_equivs_entries; i++) { + if ( strncmp(p,token_equivs1[i],strlen(token_equivs1[i])) == 0 ) + { + len+=strlen(token_equivs2[i])+2; + p += strlen(token_equivs1[i])-1; + break; + } + } + len++; + } + res = calloc(1, len+1); + res[0] = 0; + s = res; + for (p=mess; *p;) { + int found = 0; + for (i=0; i<token_equivs_entries; i++) { + if ( strncmp(p,token_equivs1[i],strlen(token_equivs1[i])) == 0 ) { + *s++ = '\''; + for (t=token_equivs2[i]; *t;) { + *s++ = *t++; + } + *s++ = '\''; + p += strlen(token_equivs1[i]); + found = 1; + break; + } + } + if( !found ) + *s++ = *p++; + } + *s++ = 0; + return res; +} + +void yyerror(YYLTYPE *locp, struct parse_io *parseio, char const *s) +{ + char *s2 = ael_token_subst((char *)s); + if (locp->first_line == locp->last_line) { + ast_log(LOG_ERROR, "==== File: %s, Line %d, Cols: %d-%d: Error: %s\n", my_file, locp->first_line, locp->first_column, locp->last_column, s2); + } else { + ast_log(LOG_ERROR, "==== File: %s, Line %d Col %d to Line %d Col %d: Error: %s\n", my_file, locp->first_line, locp->first_column, locp->last_line, locp->last_column, s2); + } + free(s2); + parseio->syntax_error_count++; +} + +struct pval *npval(pvaltype type, int first_line, int last_line, + int first_column, int last_column) +{ + pval *z = calloc(1, sizeof(struct pval)); + z->type = type; + z->startline = first_line; + z->endline = last_line; + z->startcol = first_column; + z->endcol = last_column; + z->filename = strdup(my_file); + return z; +} + +static struct pval *npval2(pvaltype type, YYLTYPE *first, YYLTYPE *last) +{ + return npval(type, first->first_line, last->last_line, + first->first_column, last->last_column); +} + +static struct pval *update_last(pval *obj, YYLTYPE *last) +{ + obj->endline = last->last_line; + obj->endcol = last->last_column; + return obj; +} + +/* frontend for npval to create a PV_WORD string from the given token */ +static pval *nword(char *string, YYLTYPE *pos) +{ + pval *p = npval2(PV_WORD, pos, pos); + if (p) + p->u1.str = string; + return p; +} + +/* this routine adds a dad ptr to each element in the list */ +static void set_dads(struct pval *dad, struct pval *child_list) +{ + struct pval *t; + + for(t=child_list;t;t=t->next) /* simple stuff */ + t->dad = dad; +} + diff --git a/trunk/res/ael/ael_lex.c b/trunk/res/ael/ael_lex.c new file mode 100644 index 000000000..26206af5d --- /dev/null +++ b/trunk/res/ael/ael_lex.c @@ -0,0 +1,3127 @@ +#line 2 "ael_lex.c" + +#line 4 "ael_lex.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 33 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +#include "asterisk.h" +/* begin standard C headers. */ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */ + +#if !defined __STDC_VERSION__ || __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include <inttypes.h> +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; +#endif /* ! C99 */ + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +#if __STDC__ + +#define YY_USE_CONST + +#endif /* __STDC__ */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +int ael_yylex_init (yyscan_t* scanner); + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yyg->yy_start = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yyg->yy_start - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE ael_yyrestart(yyin ,yyscanner ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#define YY_BUF_SIZE 16384 +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = yyg->yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + +/* The following is because we cannot portably get our hands on size_t + * (without autoconf's help, which isn't available because we want + * flex-generated scanners to compile on their own). + */ + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef unsigned int yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via ael_yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ + ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ + : NULL) + +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] + +void ael_yyrestart (FILE *input_file ,yyscan_t yyscanner ); +void ael_yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); +YY_BUFFER_STATE ael_yy_create_buffer (FILE *file,int size ,yyscan_t yyscanner ); +void ael_yy_delete_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); +void ael_yy_flush_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner ); +void ael_yypush_buffer_state (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner ); +void ael_yypop_buffer_state (yyscan_t yyscanner ); + +static void ael_yyensure_buffer_stack (yyscan_t yyscanner ); +static void ael_yy_load_buffer_state (yyscan_t yyscanner ); +static void ael_yy_init_buffer (YY_BUFFER_STATE b,FILE *file ,yyscan_t yyscanner ); + +#define YY_FLUSH_BUFFER ael_yy_flush_buffer(YY_CURRENT_BUFFER ,yyscanner) + +YY_BUFFER_STATE ael_yy_scan_buffer (char *base,yy_size_t size ,yyscan_t yyscanner ); +YY_BUFFER_STATE ael_yy_scan_string (yyconst char *yy_str ,yyscan_t yyscanner ); +YY_BUFFER_STATE ael_yy_scan_bytes (yyconst char *bytes,int len ,yyscan_t yyscanner ); + +void *ael_yyalloc (yy_size_t ,yyscan_t yyscanner ); +void *ael_yyrealloc (void *,yy_size_t ,yyscan_t yyscanner ); +void ael_yyfree (void * ,yyscan_t yyscanner ); + +#define yy_new_buffer ael_yy_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + ael_yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + ael_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + ael_yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + ael_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define ael_yywrap(n) 1 +#define YY_SKIP_YYWRAP + +typedef unsigned char YY_CHAR; + +typedef int yy_state_type; + +#define yytext_ptr yytext_r + +static yy_state_type yy_get_previous_state (yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans (yy_state_type current_state ,yyscan_t yyscanner); +static int yy_get_next_buffer (yyscan_t yyscanner ); +static void yy_fatal_error (yyconst char msg[] ,yyscan_t yyscanner ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yyg->yytext_ptr = yy_bp; \ + yyg->yytext_ptr -= yyg->yy_more_len; \ + yyleng = (size_t) (yy_cp - yyg->yytext_ptr); \ + yyg->yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yyg->yy_c_buf_p = yy_cp; + +#define YY_NUM_RULES 63 +#define YY_END_OF_BUFFER 64 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static yyconst flex_int16_t yy_accept[244] = + { 0, + 0, 0, 0, 0, 0, 0, 0, 0, 43, 43, + 64, 63, 50, 48, 49, 51, 51, 9, 3, 4, + 7, 51, 8, 5, 6, 12, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 1, 10, 2, 63, 53, 52, 63, 54, + 63, 59, 60, 61, 63, 63, 55, 56, 57, 63, + 58, 43, 44, 45, 50, 49, 51, 51, 42, 13, + 11, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 22, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 0, 53, 52, 0, 54, 53, + + 52, 54, 0, 59, 60, 61, 0, 59, 60, 61, + 0, 55, 56, 57, 0, 58, 55, 56, 57, 58, + 43, 44, 45, 46, 45, 47, 51, 13, 13, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 33, 51, + 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51, 35, 51, 51, + 51, 27, 51, 51, 51, 28, 26, 51, 51, 51, + 29, 51, 51, 51, 51, 51, 51, 51, 51, 51, + 51, 31, 38, 51, 51, 51, 51, 51, 51, 51, + 51, 51, 19, 17, 51, 51, 51, 51, 51, 34, + + 51, 51, 51, 51, 51, 51, 16, 51, 23, 51, + 51, 51, 24, 51, 30, 21, 51, 51, 14, 51, + 36, 51, 18, 51, 51, 37, 51, 51, 51, 15, + 32, 51, 51, 41, 25, 39, 0, 40, 20, 0, + 0, 62, 0 + } ; + +static yyconst flex_int32_t yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 4, 5, 6, 7, 5, 1, 8, 5, 9, + 10, 11, 5, 12, 5, 5, 13, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 14, 15, 5, + 16, 17, 1, 18, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 19, 5, 5, 5, 5, 5, 5, + 20, 21, 22, 1, 5, 1, 23, 24, 25, 26, + + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 5, 39, 40, 41, 42, 5, 43, 44, + 5, 5, 45, 46, 47, 1, 1, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48 + } ; + +static yyconst flex_int32_t yy_meta[49] = + { 0, + 1, 1, 2, 1, 3, 4, 3, 1, 1, 1, + 5, 1, 3, 1, 1, 1, 3, 1, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 1, 3, 3 + } ; + +static yyconst flex_int16_t yy_base[257] = + { 0, + 0, 0, 40, 43, 82, 121, 160, 199, 48, 55, + 320, 986, 317, 986, 314, 0, 286, 986, 986, 986, + 986, 43, 986, 986, 299, 986, 291, 275, 32, 286, + 33, 275, 34, 280, 46, 268, 272, 285, 284, 49, + 263, 275, 986, 986, 986, 74, 986, 986, 90, 986, + 238, 986, 986, 986, 277, 316, 986, 986, 986, 355, + 986, 301, 986, 67, 301, 298, 0, 265, 0, 402, + 986, 260, 269, 65, 259, 266, 253, 248, 249, 250, + 251, 243, 246, 262, 244, 254, 243, 252, 251, 234, + 238, 52, 242, 241, 104, 986, 986, 138, 986, 143, + + 177, 182, 440, 986, 986, 986, 479, 518, 557, 596, + 635, 986, 986, 986, 674, 986, 713, 752, 791, 830, + 268, 986, 104, 986, 105, 986, 245, 0, 877, 228, + 245, 240, 241, 224, 241, 236, 231, 234, 0, 233, + 219, 214, 223, 215, 217, 212, 226, 206, 202, 216, + 214, 198, 198, 204, 203, 197, 202, 0, 204, 101, + 191, 0, 191, 195, 207, 0, 0, 193, 187, 183, + 0, 189, 181, 190, 179, 171, 175, 188, 185, 168, + 183, 0, 0, 157, 164, 162, 170, 168, 159, 162, + 157, 153, 0, 0, 139, 142, 135, 138, 137, 0, + + 136, 136, 116, 114, 114, 124, 0, 110, 0, 108, + 118, 108, 0, 113, 0, 112, 111, 93, 0, 106, + 0, 96, 0, 86, 61, 0, 62, 49, 118, 0, + 0, 46, 38, 0, 0, 0, 169, 0, 0, 0, + 51, 986, 986, 923, 928, 933, 938, 941, 946, 951, + 956, 961, 965, 970, 975, 980 + } ; + +static yyconst flex_int16_t yy_def[257] = + { 0, + 243, 1, 244, 244, 245, 245, 246, 246, 247, 247, + 243, 243, 243, 243, 243, 248, 248, 243, 243, 243, + 243, 248, 243, 243, 243, 243, 248, 248, 248, 248, + 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, + 248, 248, 243, 243, 243, 249, 243, 243, 249, 243, + 250, 243, 243, 243, 250, 251, 243, 243, 243, 251, + 243, 252, 243, 253, 243, 243, 248, 248, 248, 254, + 243, 248, 248, 248, 248, 248, 248, 248, 248, 248, + 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, + 248, 248, 248, 248, 249, 243, 243, 249, 243, 249, + + 249, 249, 250, 243, 243, 243, 250, 250, 250, 250, + 251, 243, 243, 243, 251, 243, 251, 251, 251, 251, + 252, 243, 253, 243, 253, 243, 248, 255, 254, 248, + 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, + 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, + 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, + 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, + 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, + 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, + 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, + + 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, + 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, + 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, + 248, 248, 248, 248, 248, 248, 243, 248, 248, 256, + 256, 243, 0, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243 + } ; + +static yyconst flex_int16_t yy_nxt[1035] = + { 0, + 12, 13, 14, 15, 16, 16, 17, 18, 19, 20, + 16, 21, 22, 23, 24, 25, 16, 26, 16, 16, + 16, 16, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 16, 37, 38, 16, 16, 39, 40, 41, + 16, 16, 42, 16, 43, 44, 45, 16, 47, 48, + 63, 47, 48, 69, 74, 70, 242, 63, 64, 47, + 49, 50, 47, 49, 50, 64, 77, 81, 75, 124, + 82, 91, 78, 84, 85, 92, 79, 125, 239, 126, + 151, 86, 96, 97, 47, 238, 50, 47, 236, 50, + 52, 53, 152, 96, 98, 99, 54, 235, 100, 101, + + 234, 52, 55, 53, 132, 133, 124, 124, 233, 100, + 98, 102, 96, 97, 243, 125, 243, 243, 96, 237, + 99, 237, 232, 96, 98, 99, 52, 184, 53, 52, + 53, 185, 231, 230, 100, 54, 102, 229, 228, 227, + 52, 55, 53, 226, 225, 224, 100, 101, 96, 223, + 99, 96, 97, 222, 221, 220, 219, 100, 98, 102, + 218, 217, 96, 98, 99, 52, 216, 53, 57, 58, + 237, 59, 237, 215, 240, 214, 213, 212, 211, 57, + 60, 61, 100, 210, 102, 96, 97, 96, 209, 99, + 96, 97, 208, 207, 206, 205, 96, 98, 99, 204, + + 203, 96, 98, 99, 57, 202, 61, 57, 58, 201, + 59, 200, 199, 198, 197, 196, 195, 194, 57, 60, + 61, 96, 193, 99, 192, 191, 96, 190, 99, 189, + 188, 187, 186, 183, 182, 181, 180, 179, 178, 177, + 176, 175, 174, 57, 173, 61, 104, 105, 172, 171, + 170, 169, 106, 168, 167, 166, 165, 104, 107, 105, + 164, 163, 162, 161, 160, 159, 158, 157, 156, 155, + 122, 154, 153, 150, 149, 148, 147, 146, 145, 144, + 143, 142, 104, 141, 105, 108, 109, 140, 139, 138, + 137, 110, 136, 135, 134, 131, 108, 107, 109, 130, + + 127, 66, 65, 122, 94, 93, 90, 89, 88, 87, + 83, 80, 76, 73, 72, 71, 68, 66, 65, 243, + 243, 108, 243, 109, 112, 113, 243, 114, 243, 243, + 243, 243, 243, 243, 243, 112, 115, 116, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 112, 243, 116, 117, 118, 243, 119, 243, 243, 243, + 243, 243, 243, 243, 117, 115, 120, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 117, + + 243, 120, 128, 128, 243, 128, 243, 243, 243, 128, + 128, 128, 243, 128, 243, 128, 128, 128, 243, 128, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 128, 104, 105, + 243, 243, 243, 243, 106, 243, 243, 243, 243, 104, + 107, 105, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 104, 243, 105, 108, 109, 243, + 243, 243, 243, 110, 243, 243, 243, 243, 108, 107, + + 109, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 108, 243, 109, 104, 105, 243, 243, + 243, 243, 106, 243, 243, 243, 243, 104, 107, 105, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 104, 243, 105, 104, 105, 243, 243, 243, + 243, 106, 243, 243, 243, 243, 104, 107, 105, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + + 243, 104, 243, 105, 104, 105, 243, 243, 243, 243, + 106, 243, 243, 243, 243, 104, 107, 105, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 104, 243, 105, 112, 113, 243, 114, 243, 243, 243, + 243, 243, 243, 243, 112, 115, 116, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 112, + 243, 116, 117, 118, 243, 119, 243, 243, 243, 243, + 243, 243, 243, 117, 115, 120, 243, 243, 243, 243, + + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 117, 243, + 120, 112, 113, 243, 114, 243, 243, 243, 243, 243, + 243, 243, 112, 115, 116, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 112, 243, 116, + 112, 113, 243, 114, 243, 243, 243, 243, 243, 243, + 243, 112, 115, 116, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 112, 243, 116, 112, + + 113, 243, 114, 243, 243, 243, 243, 243, 243, 243, + 112, 115, 116, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 112, 243, 116, 112, 113, + 243, 114, 243, 243, 243, 243, 243, 243, 243, 112, + 115, 116, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 112, 243, 116, 128, 128, 243, + 128, 243, 243, 243, 128, 128, 128, 243, 128, 243, + 128, 128, 128, 243, 128, 243, 243, 243, 243, 243, + + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 128, 46, 46, 46, 46, 46, 51, 51, + 51, 51, 51, 56, 56, 56, 56, 56, 62, 62, + 62, 62, 62, 67, 67, 67, 95, 95, 95, 95, + 95, 103, 103, 103, 103, 103, 111, 111, 111, 111, + 111, 121, 121, 121, 121, 123, 123, 123, 123, 123, + 129, 243, 129, 129, 129, 128, 243, 128, 128, 128, + 241, 241, 241, 243, 241, 11, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243 + } ; + +static yyconst flex_int16_t yy_chk[1035] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, + 9, 4, 4, 22, 29, 22, 241, 10, 9, 3, + 3, 3, 4, 4, 4, 10, 31, 33, 29, 64, + 33, 40, 31, 35, 35, 40, 31, 64, 233, 64, + 92, 35, 46, 46, 3, 232, 3, 4, 228, 4, + 5, 5, 92, 46, 46, 46, 5, 227, 49, 49, + + 225, 5, 5, 5, 74, 74, 123, 125, 224, 49, + 49, 49, 95, 95, 123, 125, 123, 125, 46, 229, + 46, 229, 222, 95, 95, 95, 5, 160, 5, 6, + 6, 160, 220, 218, 49, 6, 49, 217, 216, 214, + 6, 6, 6, 212, 211, 210, 98, 98, 95, 208, + 95, 100, 100, 206, 205, 204, 203, 98, 98, 98, + 202, 201, 100, 100, 100, 6, 199, 6, 7, 7, + 237, 7, 237, 198, 237, 197, 196, 195, 192, 7, + 7, 7, 98, 191, 98, 101, 101, 100, 190, 100, + 102, 102, 189, 188, 187, 186, 101, 101, 101, 185, + + 184, 102, 102, 102, 7, 181, 7, 8, 8, 180, + 8, 179, 178, 177, 176, 175, 174, 173, 8, 8, + 8, 101, 172, 101, 170, 169, 102, 168, 102, 165, + 164, 163, 161, 159, 157, 156, 155, 154, 153, 152, + 151, 150, 149, 8, 148, 8, 51, 51, 147, 146, + 145, 144, 51, 143, 142, 141, 140, 51, 51, 51, + 138, 137, 136, 135, 134, 133, 132, 131, 130, 127, + 121, 94, 93, 91, 90, 89, 88, 87, 86, 85, + 84, 83, 51, 82, 51, 55, 55, 81, 80, 79, + 78, 55, 77, 76, 75, 73, 55, 55, 55, 72, + + 68, 66, 65, 62, 42, 41, 39, 38, 37, 36, + 34, 32, 30, 28, 27, 25, 17, 15, 13, 11, + 0, 55, 0, 55, 56, 56, 0, 56, 0, 0, + 0, 0, 0, 0, 0, 56, 56, 56, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 56, 0, 56, 60, 60, 0, 60, 0, 0, 0, + 0, 0, 0, 0, 60, 60, 60, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, + + 0, 60, 70, 70, 0, 70, 0, 0, 0, 70, + 70, 70, 0, 70, 0, 70, 70, 70, 0, 70, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 70, 103, 103, + 0, 0, 0, 0, 103, 0, 0, 0, 0, 103, + 103, 103, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 103, 0, 103, 107, 107, 0, + 0, 0, 0, 107, 0, 0, 0, 0, 107, 107, + + 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 107, 0, 107, 108, 108, 0, 0, + 0, 0, 108, 0, 0, 0, 0, 108, 108, 108, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 108, 0, 108, 109, 109, 0, 0, 0, + 0, 109, 0, 0, 0, 0, 109, 109, 109, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 109, 0, 109, 110, 110, 0, 0, 0, 0, + 110, 0, 0, 0, 0, 110, 110, 110, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 110, 0, 110, 111, 111, 0, 111, 0, 0, 0, + 0, 0, 0, 0, 111, 111, 111, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 111, + 0, 111, 115, 115, 0, 115, 0, 0, 0, 0, + 0, 0, 0, 115, 115, 115, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 115, 0, + 115, 117, 117, 0, 117, 0, 0, 0, 0, 0, + 0, 0, 117, 117, 117, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 117, 0, 117, + 118, 118, 0, 118, 0, 0, 0, 0, 0, 0, + 0, 118, 118, 118, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 118, 0, 118, 119, + + 119, 0, 119, 0, 0, 0, 0, 0, 0, 0, + 119, 119, 119, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 119, 0, 119, 120, 120, + 0, 120, 0, 0, 0, 0, 0, 0, 0, 120, + 120, 120, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 120, 0, 120, 129, 129, 0, + 129, 0, 0, 0, 129, 129, 129, 0, 129, 0, + 129, 129, 129, 0, 129, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 129, 244, 244, 244, 244, 244, 245, 245, + 245, 245, 245, 246, 246, 246, 246, 246, 247, 247, + 247, 247, 247, 248, 248, 248, 249, 249, 249, 249, + 249, 250, 250, 250, 250, 250, 251, 251, 251, 251, + 251, 252, 252, 252, 252, 253, 253, 253, 253, 253, + 254, 0, 254, 254, 254, 255, 0, 255, 255, 255, + 256, 256, 256, 0, 256, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 243, 243, 243 + } ; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() (yyg->yy_more_flag = 1) +#define YY_MORE_ADJ yyg->yy_more_len +#define YY_RESTORE_YY_MORE_OFFSET +#line 1 "ael.flex" +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2006, Digium, Inc. + * + * Steve Murphy <murf@parsetree.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ +/*! \file + * + * \brief Flex scanner description of tokens used in AEL2 . + * + */ +/* + * Start with flex options: + * + * %x describes the contexts we have: paren, semic and argg, plus INITIAL + */ + +/* prefix used for various globally-visible functions and variables. + * This renames also ael_yywrap, but since we do not use it, we just + * add option noyywrap to remove it. + */ +/* ael_yyfree normally just frees its arg. It can be null sometimes, + which some systems will complain about, so, we'll define our own version */ +/* batch gives a bit more performance if we are using it in + * a non-interactive mode. We probably don't care much. + */ +/* outfile is the filename to be used instead of lex.yy.c */ +/* + * These are not supported in flex 2.5.4, but we need them + * at the moment: + * reentrant produces a thread-safe parser. Not 100% sure that + * we require it, though. + * bison-bridge passes an additional yylval argument to ael_yylex(). + * bison-locations is probably not needed. + */ +#line 63 "ael.flex" +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#if defined(__Darwin__) || defined(__CYGWIN__) +#define GLOB_ABORTED GLOB_ABEND +#endif +# include <glob.h> + +#include "asterisk/logger.h" +#include "asterisk/utils.h" +#include "ael/ael.tab.h" +#include "asterisk/ael_structs.h" + +/* + * A stack to keep track of matching brackets ( [ { } ] ) + */ +static char pbcstack[400]; /* XXX missing size checks */ +static int pbcpos = 0; +static void pbcpush(char x); +static int pbcpop(char x); + +static int parencount = 0; + +/* + * current line, column and filename, updated as we read the input. + */ +static int my_lineno = 1; /* current line in the source */ +static int my_col = 1; /* current column in the source */ +char *my_file = 0; /* used also in the bison code */ +char *prev_word; /* XXX document it */ + +#define MAX_INCLUDE_DEPTH 50 + +/* + * flex is not too smart, and generates global functions + * without prototypes so the compiler may complain. + * To avoid that, we declare the prototypes here, + * even though these functions are not used. + */ +int ael_yyget_column (yyscan_t yyscanner); +void ael_yyset_column (int column_no , yyscan_t yyscanner); + +int ael_yyparse (struct parse_io *); + +/* + * A stack to process include files. + * As we switch into the new file we need to store the previous + * state to restore it later. + */ +struct stackelement { + char *fname; + int lineno; + int colno; + glob_t globbuf; /* the current globbuf */ + int globbuf_pos; /* where we are in the current globbuf */ + YY_BUFFER_STATE bufstate; +}; + +static struct stackelement include_stack[MAX_INCLUDE_DEPTH]; +static int include_stack_index = 0; +static void setup_filestack(char *fnamebuf, int fnamebuf_siz, glob_t *globbuf, int globpos, yyscan_t xscan, int create); + +/* + * if we use the @n feature of bison, we must supply the start/end + * location of tokens in the structure pointed by yylloc. + * Simple tokens are just assumed to be on the same line, so + * the line number is constant, and the column is incremented + * by the length of the token. + */ +#ifdef FLEX_BETA /* set for 2.5.33 */ + +/* compute the total number of lines and columns in the text + * passed as argument. + */ +static void pbcwhere(const char *text, int *line, int *col ) +{ + int loc_line = *line; + int loc_col = *col; + char c; + while ( (c = *text++) ) { + if ( c == '\t' ) { + loc_col += 8 - (loc_col % 8); + } else if ( c == '\n' ) { + loc_line++; + loc_col = 1; + } else + loc_col++; + } + *line = loc_line; + *col = loc_col; +} + +#define STORE_POS do { \ + yylloc->first_line = yylloc->last_line = my_lineno; \ + yylloc->first_column=my_col; \ + yylloc->last_column=my_col+yyleng-1; \ + my_col+=yyleng; \ + } while (0) + +#define STORE_LOC do { \ + yylloc->first_line = my_lineno; \ + yylloc->first_column=my_col; \ + pbcwhere(yytext, &my_lineno, &my_col); \ + yylloc->last_line = my_lineno; \ + yylloc->last_column = my_col - 1; \ + } while (0) +#else +#define STORE_POS +#define STORE_LOC +#endif +#line 909 "ael_lex.c" + +#define INITIAL 0 +#define paren 1 +#define semic 2 +#define argg 3 +#define comment 4 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include <unistd.h> +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Holds the entire state of the reentrant scanner. */ +struct yyguts_t + { + + /* User-defined. Not touched by flex. */ + YY_EXTRA_TYPE yyextra_r; + + /* The rest are the same as the globals declared in the non-reentrant scanner. */ + FILE *yyin_r, *yyout_r; + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ + char yy_hold_char; + int yy_n_chars; + int yyleng_r; + char *yy_c_buf_p; + int yy_init; + int yy_start; + int yy_did_buffer_switch_on_eof; + int yy_start_stack_ptr; + int yy_start_stack_depth; + int *yy_start_stack; + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + int yylineno_r; + int yy_flex_debug_r; + + char *yytext_r; + int yy_more_flag; + int yy_more_len; + + YYSTYPE * yylval_r; + + YYLTYPE * yylloc_r; + + }; /* end struct yyguts_t */ + +static int yy_init_globals (yyscan_t yyscanner ); + + /* This must go here because YYSTYPE and YYLTYPE are included + * from bison output in section 1.*/ + # define yylval yyg->yylval_r + + # define yylloc yyg->yylloc_r + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int ael_yylex_destroy (yyscan_t yyscanner ); + +int ael_yyget_debug (yyscan_t yyscanner ); + +void ael_yyset_debug (int debug_flag ,yyscan_t yyscanner ); + +YY_EXTRA_TYPE ael_yyget_extra (yyscan_t yyscanner ); + +void ael_yyset_extra (YY_EXTRA_TYPE user_defined ,yyscan_t yyscanner ); + +FILE *ael_yyget_in (yyscan_t yyscanner ); + +void ael_yyset_in (FILE * in_str ,yyscan_t yyscanner ); + +FILE *ael_yyget_out (yyscan_t yyscanner ); + +void ael_yyset_out (FILE * out_str ,yyscan_t yyscanner ); + +int ael_yyget_leng (yyscan_t yyscanner ); + +char *ael_yyget_text (yyscan_t yyscanner ); + +int ael_yyget_lineno (yyscan_t yyscanner ); + +void ael_yyset_lineno (int line_number ,yyscan_t yyscanner ); + +YYSTYPE * ael_yyget_lval (yyscan_t yyscanner ); + +void ael_yyset_lval (YYSTYPE * yylval_param ,yyscan_t yyscanner ); + + YYLTYPE *ael_yyget_lloc (yyscan_t yyscanner ); + + void ael_yyset_lloc (YYLTYPE * yylloc_param ,yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int ael_yywrap (yyscan_t yyscanner ); +#else +extern int ael_yywrap (yyscan_t yyscanner ); +#endif +#endif + + static void yyunput (int c,char *buf_ptr ,yyscan_t yyscanner); + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ,yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT + +#ifdef __cplusplus +static int yyinput (yyscan_t yyscanner ); +#else +static int input (yyscan_t yyscanner ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO (void) fwrite( yytext, yyleng, 1, yyout ) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + size_t n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int ael_yylex (YYSTYPE * yylval_param,YYLTYPE * yylloc_param ,yyscan_t yyscanner); + +#define YY_DECL int ael_yylex (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + +#line 187 "ael.flex" + + +#line 1151 "ael_lex.c" + + yylval = yylval_param; + + yylloc = yylloc_param; + + if ( !yyg->yy_init ) + { + yyg->yy_init = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yyg->yy_start ) + yyg->yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + ael_yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + ael_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); + } + + ael_yy_load_buffer_state(yyscanner ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yyg->yy_more_len = 0; + if ( yyg->yy_more_flag ) + { + yyg->yy_more_len = yyg->yy_c_buf_p - yyg->yytext_ptr; + yyg->yy_more_flag = 0; + } + yy_cp = yyg->yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yyg->yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yyg->yy_start; +yy_match: + do + { + register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)]; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 244 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + ++yy_cp; + } + while ( yy_current_state != 243 ); + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yyg->yy_hold_char; + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + +case 1: +YY_RULE_SETUP +#line 189 "ael.flex" +{ STORE_POS; return LC;} + YY_BREAK +case 2: +YY_RULE_SETUP +#line 190 "ael.flex" +{ STORE_POS; return RC;} + YY_BREAK +case 3: +YY_RULE_SETUP +#line 191 "ael.flex" +{ STORE_POS; return LP;} + YY_BREAK +case 4: +YY_RULE_SETUP +#line 192 "ael.flex" +{ STORE_POS; return RP;} + YY_BREAK +case 5: +YY_RULE_SETUP +#line 193 "ael.flex" +{ STORE_POS; return SEMI;} + YY_BREAK +case 6: +YY_RULE_SETUP +#line 194 "ael.flex" +{ STORE_POS; return EQ;} + YY_BREAK +case 7: +YY_RULE_SETUP +#line 195 "ael.flex" +{ STORE_POS; return COMMA;} + YY_BREAK +case 8: +YY_RULE_SETUP +#line 196 "ael.flex" +{ STORE_POS; return COLON;} + YY_BREAK +case 9: +YY_RULE_SETUP +#line 197 "ael.flex" +{ STORE_POS; return AMPER;} + YY_BREAK +case 10: +YY_RULE_SETUP +#line 198 "ael.flex" +{ STORE_POS; return BAR;} + YY_BREAK +case 11: +YY_RULE_SETUP +#line 199 "ael.flex" +{ STORE_POS; return EXTENMARK;} + YY_BREAK +case 12: +YY_RULE_SETUP +#line 200 "ael.flex" +{ STORE_POS; return AT;} + YY_BREAK +case 13: +YY_RULE_SETUP +#line 201 "ael.flex" +{/*comment*/} + YY_BREAK +case 14: +YY_RULE_SETUP +#line 202 "ael.flex" +{ STORE_POS; return KW_CONTEXT;} + YY_BREAK +case 15: +YY_RULE_SETUP +#line 203 "ael.flex" +{ STORE_POS; return KW_ABSTRACT;} + YY_BREAK +case 16: +YY_RULE_SETUP +#line 204 "ael.flex" +{ STORE_POS; return KW_EXTEND;} + YY_BREAK +case 17: +YY_RULE_SETUP +#line 205 "ael.flex" +{ STORE_POS; return KW_MACRO;}; + YY_BREAK +case 18: +YY_RULE_SETUP +#line 206 "ael.flex" +{ STORE_POS; return KW_GLOBALS;} + YY_BREAK +case 19: +YY_RULE_SETUP +#line 207 "ael.flex" +{ STORE_POS; return KW_LOCAL;} + YY_BREAK +case 20: +YY_RULE_SETUP +#line 208 "ael.flex" +{ STORE_POS; return KW_IGNOREPAT;} + YY_BREAK +case 21: +YY_RULE_SETUP +#line 209 "ael.flex" +{ STORE_POS; return KW_SWITCH;} + YY_BREAK +case 22: +YY_RULE_SETUP +#line 210 "ael.flex" +{ STORE_POS; return KW_IF;} + YY_BREAK +case 23: +YY_RULE_SETUP +#line 211 "ael.flex" +{ STORE_POS; return KW_IFTIME;} + YY_BREAK +case 24: +YY_RULE_SETUP +#line 212 "ael.flex" +{ STORE_POS; return KW_RANDOM;} + YY_BREAK +case 25: +YY_RULE_SETUP +#line 213 "ael.flex" +{ STORE_POS; return KW_REGEXTEN;} + YY_BREAK +case 26: +YY_RULE_SETUP +#line 214 "ael.flex" +{ STORE_POS; return KW_HINT;} + YY_BREAK +case 27: +YY_RULE_SETUP +#line 215 "ael.flex" +{ STORE_POS; return KW_ELSE;} + YY_BREAK +case 28: +YY_RULE_SETUP +#line 216 "ael.flex" +{ STORE_POS; return KW_GOTO;} + YY_BREAK +case 29: +YY_RULE_SETUP +#line 217 "ael.flex" +{ STORE_POS; return KW_JUMP;} + YY_BREAK +case 30: +YY_RULE_SETUP +#line 218 "ael.flex" +{ STORE_POS; return KW_RETURN;} + YY_BREAK +case 31: +YY_RULE_SETUP +#line 219 "ael.flex" +{ STORE_POS; return KW_BREAK;} + YY_BREAK +case 32: +YY_RULE_SETUP +#line 220 "ael.flex" +{ STORE_POS; return KW_CONTINUE;} + YY_BREAK +case 33: +YY_RULE_SETUP +#line 221 "ael.flex" +{ STORE_POS; return KW_FOR;} + YY_BREAK +case 34: +YY_RULE_SETUP +#line 222 "ael.flex" +{ STORE_POS; return KW_WHILE;} + YY_BREAK +case 35: +YY_RULE_SETUP +#line 223 "ael.flex" +{ STORE_POS; return KW_CASE;} + YY_BREAK +case 36: +YY_RULE_SETUP +#line 224 "ael.flex" +{ STORE_POS; return KW_DEFAULT;} + YY_BREAK +case 37: +YY_RULE_SETUP +#line 225 "ael.flex" +{ STORE_POS; return KW_PATTERN;} + YY_BREAK +case 38: +YY_RULE_SETUP +#line 226 "ael.flex" +{ STORE_POS; return KW_CATCH;} + YY_BREAK +case 39: +YY_RULE_SETUP +#line 227 "ael.flex" +{ STORE_POS; return KW_SWITCHES;} + YY_BREAK +case 40: +YY_RULE_SETUP +#line 228 "ael.flex" +{ STORE_POS; return KW_ESWITCHES;} + YY_BREAK +case 41: +YY_RULE_SETUP +#line 229 "ael.flex" +{ STORE_POS; return KW_INCLUDES;} + YY_BREAK +case 42: +YY_RULE_SETUP +#line 230 "ael.flex" +{ BEGIN(comment); my_col += 2; } + YY_BREAK +case 43: +YY_RULE_SETUP +#line 232 "ael.flex" +{ my_col += yyleng; } + YY_BREAK +case 44: +/* rule 44 can match eol */ +YY_RULE_SETUP +#line 233 "ael.flex" +{ ++my_lineno; my_col=1;} + YY_BREAK +case 45: +YY_RULE_SETUP +#line 234 "ael.flex" +{ my_col += yyleng; } + YY_BREAK +case 46: +/* rule 46 can match eol */ +YY_RULE_SETUP +#line 235 "ael.flex" +{ ++my_lineno; my_col=1;} + YY_BREAK +case 47: +YY_RULE_SETUP +#line 236 "ael.flex" +{ my_col += 2; BEGIN(INITIAL); } + YY_BREAK +case 48: +/* rule 48 can match eol */ +YY_RULE_SETUP +#line 238 "ael.flex" +{ my_lineno++; my_col = 1; } + YY_BREAK +case 49: +YY_RULE_SETUP +#line 239 "ael.flex" +{ my_col += yyleng; } + YY_BREAK +case 50: +YY_RULE_SETUP +#line 240 "ael.flex" +{ my_col += (yyleng*8)-(my_col%8); } + YY_BREAK +case 51: +YY_RULE_SETUP +#line 242 "ael.flex" +{ + STORE_POS; + yylval->str = strdup(yytext); + prev_word = yylval->str; + return word; + } + YY_BREAK +/* + * context used for arguments of if_head, random_head, switch_head, + * for (last statement), while (XXX why not iftime_head ?). + * End with the matching parentheses. + * A comma at the top level is valid here, unlike in argg where it + * is an argument separator so it must be returned as a token. + */ +case 52: +/* rule 52 can match eol */ +YY_RULE_SETUP +#line 258 "ael.flex" +{ + if ( pbcpop(')') ) { /* error */ + STORE_LOC; + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched ')' in expression: %s !\n", my_file, my_lineno, my_col, yytext); + BEGIN(0); + yylval->str = strdup(yytext); + prev_word = 0; + return word; + } + parencount--; + if ( parencount >= 0) { + yymore(); + } else { + STORE_LOC; + yylval->str = strdup(yytext); + yylval->str[yyleng-1] = '\0'; /* trim trailing ')' */ + unput(')'); + BEGIN(0); + return word; + } + } + YY_BREAK +case 53: +/* rule 53 can match eol */ +YY_RULE_SETUP +#line 280 "ael.flex" +{ + char c = yytext[yyleng-1]; + if (c == '(') + parencount++; + pbcpush(c); + yymore(); + } + YY_BREAK +case 54: +/* rule 54 can match eol */ +YY_RULE_SETUP +#line 288 "ael.flex" +{ + char c = yytext[yyleng-1]; + if ( pbcpop(c)) { /* error */ + STORE_LOC; + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched '%c' in expression!\n", + my_file, my_lineno, my_col, c); + BEGIN(0); + yylval->str = strdup(yytext); + return word; + } + yymore(); + } + YY_BREAK +/* + * handlers for arguments to a macro or application calls. + * We enter this context when we find the initial '(' and + * stay here until we close all matching parentheses, + * and find the comma (argument separator) or the closing ')' + * of the (external) call, which happens when parencount == 0 + * before the decrement. + */ +case 55: +/* rule 55 can match eol */ +YY_RULE_SETUP +#line 310 "ael.flex" +{ + char c = yytext[yyleng-1]; + if (c == '(') + parencount++; + pbcpush(c); + yymore(); + } + YY_BREAK +case 56: +/* rule 56 can match eol */ +YY_RULE_SETUP +#line 318 "ael.flex" +{ + if ( pbcpop(')') ) { /* error */ + STORE_LOC; + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched ')' in expression!\n", my_file, my_lineno, my_col); + BEGIN(0); + yylval->str = strdup(yytext); + return word; + } + + parencount--; + if( parencount >= 0){ + yymore(); + } else { + STORE_LOC; + BEGIN(0); + if ( !strcmp(yytext, ")") ) + return RP; + yylval->str = strdup(yytext); + yylval->str[yyleng-1] = '\0'; /* trim trailing ')' */ + unput(')'); + return word; + } + } + YY_BREAK +case 57: +/* rule 57 can match eol */ +YY_RULE_SETUP +#line 342 "ael.flex" +{ + if( parencount != 0) { /* printf("Folding in a comma!\n"); */ + yymore(); + } else { + STORE_LOC; + if( !strcmp(yytext,"," ) ) + return COMMA; + yylval->str = strdup(yytext); + yylval->str[yyleng-1] = '\0'; + unput(','); + return word; + } + } + YY_BREAK +case 58: +/* rule 58 can match eol */ +YY_RULE_SETUP +#line 356 "ael.flex" +{ + char c = yytext[yyleng-1]; + if ( pbcpop(c) ) { /* error */ + STORE_LOC; + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched '%c' in expression!\n", my_file, my_lineno, my_col, c); + BEGIN(0); + yylval->str = strdup(yytext); + return word; + } + yymore(); + } + YY_BREAK +/* + * context used to find tokens in the right hand side of assignments, + * or in the first and second operand of a 'for'. As above, match + * commas and use ';' as a separator (hence return it as a separate token). + */ +case 59: +/* rule 59 can match eol */ +YY_RULE_SETUP +#line 373 "ael.flex" +{ + char c = yytext[yyleng-1]; + yymore(); + pbcpush(c); + } + YY_BREAK +case 60: +/* rule 60 can match eol */ +YY_RULE_SETUP +#line 379 "ael.flex" +{ + char c = yytext[yyleng-1]; + if ( pbcpop(c) ) { /* error */ + STORE_LOC; + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched '%c' in expression!\n", my_file, my_lineno, my_col, c); + BEGIN(0); + yylval->str = strdup(yytext); + return word; + } + yymore(); + } + YY_BREAK +case 61: +/* rule 61 can match eol */ +YY_RULE_SETUP +#line 391 "ael.flex" +{ + STORE_LOC; + yylval->str = strdup(yytext); + yylval->str[yyleng-1] = '\0'; + unput(';'); + BEGIN(0); + return word; + } + YY_BREAK +case 62: +/* rule 62 can match eol */ +YY_RULE_SETUP +#line 400 "ael.flex" +{ + char fnamebuf[1024],*p1,*p2; + int glob_ret; + glob_t globbuf; /* the current globbuf */ + int globbuf_pos = -1; /* where we are in the current globbuf */ + globbuf.gl_offs = 0; /* initialize it to silence gcc */ + + p1 = strchr(yytext,'"'); + p2 = strrchr(yytext,'"'); + if ( include_stack_index >= MAX_INCLUDE_DEPTH ) { + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Includes nested too deeply! Wow!!! How did you do that?\n", my_file, my_lineno, my_col); + } else if ( (int)(p2-p1) > sizeof(fnamebuf) - 1 ) { + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Filename is incredibly way too long (%d chars!). Inclusion ignored!\n", my_file, my_lineno, my_col, yyleng - 10); + } else { + strncpy(fnamebuf, p1+1, p2-p1-1); + fnamebuf[p2-p1-1] = 0; + if (fnamebuf[0] != '/') { + char fnamebuf2[1024]; + snprintf(fnamebuf2,sizeof(fnamebuf2), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, fnamebuf); + ast_copy_string(fnamebuf,fnamebuf2,sizeof(fnamebuf)); + } +#ifdef SOLARIS + glob_ret = glob(fnamebuf, GLOB_NOCHECK, NULL, &globbuf); +#else + glob_ret = glob(fnamebuf, 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", fnamebuf); + } else if (glob_ret == GLOB_ABORTED) { + ast_log(LOG_WARNING, + "Glob Expansion of pattern '%s' failed: Read error\n", fnamebuf); + } else if (glob_ret == GLOB_NOMATCH) { + ast_log(LOG_WARNING, + "Glob Expansion of pattern '%s' failed: No matches!\n", fnamebuf); + } else { + globbuf_pos = 0; + } + } + if (globbuf_pos > -1) { + setup_filestack(fnamebuf, sizeof(fnamebuf), &globbuf, 0, yyscanner, 1); + } + } + YY_BREAK +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(paren): +case YY_STATE_EOF(semic): +case YY_STATE_EOF(argg): +case YY_STATE_EOF(comment): +#line 445 "ael.flex" +{ + char fnamebuf[2048]; + if (include_stack_index > 0 && include_stack[include_stack_index-1].globbuf_pos < include_stack[include_stack_index-1].globbuf.gl_pathc-1) { + free(my_file); + my_file = 0; + ael_yy_delete_buffer(YY_CURRENT_BUFFER,yyscanner ); + include_stack[include_stack_index-1].globbuf_pos++; + setup_filestack(fnamebuf, sizeof(fnamebuf), &include_stack[include_stack_index-1].globbuf, include_stack[include_stack_index-1].globbuf_pos, yyscanner, 0); + /* finish this */ + + } else { + if (include_stack[include_stack_index].fname) { + free(include_stack[include_stack_index].fname); + include_stack[include_stack_index].fname = 0; + } + if ( --include_stack_index < 0 ) { + yyterminate(); + } else { + if (my_file) { + free(my_file); + my_file = 0; + } + globfree(&include_stack[include_stack_index].globbuf); + include_stack[include_stack_index].globbuf_pos = -1; + + ael_yy_delete_buffer(YY_CURRENT_BUFFER,yyscanner ); + ael_yy_switch_to_buffer(include_stack[include_stack_index].bufstate,yyscanner ); + my_lineno = include_stack[include_stack_index].lineno; + my_col = include_stack[include_stack_index].colno; + my_file = strdup(include_stack[include_stack_index].fname); + } + } + } + YY_BREAK +case 63: +YY_RULE_SETUP +#line 479 "ael.flex" +ECHO; + YY_BREAK +#line 1784 "ael_lex.c" + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yyg->yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * ael_yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); + + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yyg->yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_END_OF_FILE: + { + yyg->yy_did_buffer_switch_on_eof = 0; + + if ( ael_yywrap(yyscanner ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = + yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yyg->yy_c_buf_p = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ +} /* end of ael_yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = yyg->yytext_ptr; + register int number_to_move, i; + int ret_val; + + if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER; + + int yy_c_buf_p_offset = + (int) (yyg->yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + ael_yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ,yyscanner ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = 0; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + yyg->yy_n_chars, num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + if ( yyg->yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + ael_yyrestart(yyin ,yyscanner); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + yyg->yy_n_chars += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (yyscan_t yyscanner) +{ + register yy_state_type yy_current_state; + register char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_current_state = yyg->yy_start; + + for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) + { + register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 244 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) +{ + register int yy_is_jam; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ + register char *yy_cp = yyg->yy_c_buf_p; + + register YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 244 ) + yy_c = yy_meta[(unsigned int) yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; + yy_is_jam = (yy_current_state == 243); + + return yy_is_jam ? 0 : yy_current_state; +} + + static void yyunput (int c, register char * yy_bp , yyscan_t yyscanner) +{ + register char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_cp = yyg->yy_c_buf_p; + + /* undo effects of setting up yytext */ + *yy_cp = yyg->yy_hold_char; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + { /* need to shift things up to make room */ + /* +2 for EOB chars. */ + register int number_to_move = yyg->yy_n_chars + 2; + register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; + register char *source = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; + + while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_buf_size; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + YY_FATAL_ERROR( "flex scanner push-back overflow" ); + } + + *--yy_cp = (char) c; + + yyg->yytext_ptr = yy_bp; + yyg->yy_hold_char = *yy_cp; + yyg->yy_c_buf_p = yy_cp; +} + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (yyscan_t yyscanner) +#else + static int input (yyscan_t yyscanner) +#endif + +{ + int c; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + *yyg->yy_c_buf_p = yyg->yy_hold_char; + + if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + /* This was really a NUL. */ + *yyg->yy_c_buf_p = '\0'; + + else + { /* need more input */ + int offset = yyg->yy_c_buf_p - yyg->yytext_ptr; + ++yyg->yy_c_buf_p; + + switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + ael_yyrestart(yyin ,yyscanner); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( ael_yywrap(yyscanner ) ) + return EOF; + + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(yyscanner); +#else + return input(yyscanner); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = yyg->yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ + *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ + yyg->yy_hold_char = *++yyg->yy_c_buf_p; + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * @param yyscanner The scanner object. + * @note This function does not reset the start condition to @c INITIAL . + */ + void ael_yyrestart (FILE * input_file , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! YY_CURRENT_BUFFER ){ + ael_yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + ael_yy_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); + } + + ael_yy_init_buffer(YY_CURRENT_BUFFER,input_file ,yyscanner); + ael_yy_load_buffer_state(yyscanner ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * @param yyscanner The scanner object. + */ + void ael_yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* TODO. We should be able to replace this entire function body + * with + * ael_yypop_buffer_state(); + * ael_yypush_buffer_state(new_buffer); + */ + ael_yyensure_buffer_stack (yyscanner); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + ael_yy_load_buffer_state(yyscanner ); + + /* We don't actually know whether we did this switch during + * EOF (ael_yywrap()) processing, but the only time this flag + * is looked at is after ael_yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yyg->yy_did_buffer_switch_on_eof = 1; +} + +static void ael_yy_load_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + yyg->yy_hold_char = *yyg->yy_c_buf_p; +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * @param yyscanner The scanner object. + * @return the allocated buffer state. + */ + YY_BUFFER_STATE ael_yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) ael_yyalloc(sizeof( struct yy_buffer_state ) ,yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in ael_yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) ael_yyalloc(b->yy_buf_size + 2 ,yyscanner ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in ael_yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + ael_yy_init_buffer(b,file ,yyscanner); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with ael_yy_create_buffer() + * @param yyscanner The scanner object. + */ + void ael_yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + ael_yyfree((void *) b->yy_ch_buf ,yyscanner ); + + ael_yyfree((void *) b ,yyscanner ); +} + +#ifndef __cplusplus +extern int isatty (int ); +#endif /* __cplusplus */ + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a ael_yyrestart() or at EOF. + */ + static void ael_yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + +{ + int oerrno = errno; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + ael_yy_flush_buffer(b ,yyscanner); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then ael_yy_init_buffer was _probably_ + * called from ael_yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * @param yyscanner The scanner object. + */ + void ael_yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + ael_yy_load_buffer_state(yyscanner ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * @param yyscanner The scanner object. + */ +void ael_yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (new_buffer == NULL) + return; + + ael_yyensure_buffer_stack(yyscanner); + + /* This block is copied from ael_yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + yyg->yy_buffer_stack_top++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from ael_yy_switch_to_buffer. */ + ael_yy_load_buffer_state(yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * @param yyscanner The scanner object. + */ +void ael_yypop_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (!YY_CURRENT_BUFFER) + return; + + ael_yy_delete_buffer(YY_CURRENT_BUFFER ,yyscanner); + YY_CURRENT_BUFFER_LVALUE = NULL; + if (yyg->yy_buffer_stack_top > 0) + --yyg->yy_buffer_stack_top; + + if (YY_CURRENT_BUFFER) { + ael_yy_load_buffer_state(yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void ael_yyensure_buffer_stack (yyscan_t yyscanner) +{ + int num_to_alloc; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (!yyg->yy_buffer_stack) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; + yyg->yy_buffer_stack = (struct yy_buffer_state**)ael_yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + yyg->yy_buffer_stack_max = num_to_alloc; + yyg->yy_buffer_stack_top = 0; + return; + } + + if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = yyg->yy_buffer_stack_max + grow_size; + yyg->yy_buffer_stack = (struct yy_buffer_state**)ael_yyrealloc + (yyg->yy_buffer_stack, + num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + + /* zero only the new slots.*/ + memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); + yyg->yy_buffer_stack_max = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE ael_yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) ael_yyalloc(sizeof( struct yy_buffer_state ) ,yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in ael_yy_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + ael_yy_switch_to_buffer(b ,yyscanner ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to ael_yylex() will + * scan from a @e copy of @a str. + * @param str a NUL-terminated string to scan + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * ael_yy_scan_bytes() instead. + */ +YY_BUFFER_STATE ael_yy_scan_string (yyconst char * yystr , yyscan_t yyscanner) +{ + + return ael_yy_scan_bytes(yystr,strlen(yystr) ,yyscanner); +} + +/** Setup the input buffer state to scan the given bytes. The next call to ael_yylex() will + * scan from a @e copy of @a bytes. + * @param bytes the byte buffer to scan + * @param len the number of bytes in the buffer pointed to by @a bytes. + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE ael_yy_scan_bytes (yyconst char * yybytes, int _yybytes_len , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = _yybytes_len + 2; + buf = (char *) ael_yyalloc(n ,yyscanner ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in ael_yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = ael_yy_scan_buffer(buf,n ,yyscanner); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in ael_yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yy_fatal_error (yyconst char* msg , yyscan_t yyscanner) +{ + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = yyg->yy_hold_char; \ + yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ + yyg->yy_hold_char = *yyg->yy_c_buf_p; \ + *yyg->yy_c_buf_p = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the user-defined data for this scanner. + * @param yyscanner The scanner object. + */ +YY_EXTRA_TYPE ael_yyget_extra (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyextra; +} + +/** Get the current line number. + * @param yyscanner The scanner object. + */ +int ael_yyget_lineno (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yylineno; +} + +/** Get the current column number. + * @param yyscanner The scanner object. + */ +int ael_yyget_column (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yycolumn; +} + +/** Get the input stream. + * @param yyscanner The scanner object. + */ +FILE *ael_yyget_in (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyin; +} + +/** Get the output stream. + * @param yyscanner The scanner object. + */ +FILE *ael_yyget_out (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyout; +} + +/** Get the length of the current token. + * @param yyscanner The scanner object. + */ +int ael_yyget_leng (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyleng; +} + +/** Get the current token. + * @param yyscanner The scanner object. + */ + +char *ael_yyget_text (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yytext; +} + +/** Set the user-defined data. This data is never touched by the scanner. + * @param user_defined The data to be associated with this scanner. + * @param yyscanner The scanner object. + */ +void ael_yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyextra = user_defined ; +} + +/** Set the current line number. + * @param line_number + * @param yyscanner The scanner object. + */ +void ael_yyset_lineno (int line_number , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* lineno is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + yy_fatal_error( "ael_yyset_lineno called with no buffer" , yyscanner); + + yylineno = line_number; +} + +/** Set the current column. + * @param line_number + * @param yyscanner The scanner object. + */ +void ael_yyset_column (int column_no , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* column is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + yy_fatal_error( "ael_yyset_column called with no buffer" , yyscanner); + + yycolumn = column_no; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param in_str A readable stream. + * @param yyscanner The scanner object. + * @see ael_yy_switch_to_buffer + */ +void ael_yyset_in (FILE * in_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyin = in_str ; +} + +void ael_yyset_out (FILE * out_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyout = out_str ; +} + +int ael_yyget_debug (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yy_flex_debug; +} + +void ael_yyset_debug (int bdebug , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yy_flex_debug = bdebug ; +} + +/* Accessor methods for yylval and yylloc */ + +YYSTYPE * ael_yyget_lval (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylval; +} + +void ael_yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylval = yylval_param; +} + +YYLTYPE *ael_yyget_lloc (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylloc; +} + +void ael_yyset_lloc (YYLTYPE * yylloc_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylloc = yylloc_param; +} + +/* User-visible API */ + +/* ael_yylex_init is special because it creates the scanner itself, so it is + * the ONLY reentrant function that doesn't take the scanner as the last argument. + * That's why we explicitly handle the declaration, instead of using our macros. + */ + +int ael_yylex_init(yyscan_t* ptr_yy_globals) + +{ + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) ael_yyalloc ( sizeof( struct yyguts_t ), NULL ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + return yy_init_globals ( *ptr_yy_globals ); +} + +static int yy_init_globals (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from ael_yylex_destroy(), so don't allocate here. + */ + + yyg->yy_buffer_stack = 0; + yyg->yy_buffer_stack_top = 0; + yyg->yy_buffer_stack_max = 0; + yyg->yy_c_buf_p = (char *) 0; + yyg->yy_init = 0; + yyg->yy_start = 0; + + yyg->yy_start_stack_ptr = 0; + yyg->yy_start_stack_depth = 0; + yyg->yy_start_stack = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = (FILE *) 0; + yyout = (FILE *) 0; +#endif + + /* For future reference: Set errno on error, since we are called by + * ael_yylex_init() + */ + return 0; +} + +/* ael_yylex_destroy is for both reentrant and non-reentrant scanners. */ +int ael_yylex_destroy (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + ael_yy_delete_buffer(YY_CURRENT_BUFFER ,yyscanner ); + YY_CURRENT_BUFFER_LVALUE = NULL; + ael_yypop_buffer_state(yyscanner); + } + + /* Destroy the stack itself. */ + ael_yyfree(yyg->yy_buffer_stack ,yyscanner); + yyg->yy_buffer_stack = NULL; + + /* Destroy the start condition stack. */ + ael_yyfree(yyg->yy_start_stack ,yyscanner ); + yyg->yy_start_stack = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * ael_yylex() is called, initialization will occur. */ + yy_init_globals( yyscanner); + + /* Destroy the main struct (reentrant only). */ + ael_yyfree ( yyscanner , yyscanner ); + yyscanner = NULL; + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n , yyscan_t yyscanner) +{ + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * s , yyscan_t yyscanner) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *ael_yyalloc (yy_size_t size , yyscan_t yyscanner) +{ + return (void *) malloc( size ); +} + +void *ael_yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) +{ + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); +} + +#define YYTABLES_NAME "yytables" + +#line 479 "ael.flex" + + + +static void pbcpush(char x) +{ + pbcstack[pbcpos++] = x; +} + +void ael_yyfree(void *ptr, yyscan_t yyscanner) +{ + if (ptr) + free( (char*) ptr ); +} + +static int pbcpop(char x) +{ + if ( ( x == ')' && pbcstack[pbcpos-1] == '(' ) + || ( x == ']' && pbcstack[pbcpos-1] == '[' ) + || ( x == '}' && pbcstack[pbcpos-1] == '{' )) { + pbcpos--; + return 0; + } + return 1; /* error */ +} + +static int c_prevword(void) +{ + char *c = prev_word; + if (c == NULL) + return 0; + while ( *c ) { + switch (*c) { + case '{': + case '[': + case '(': + pbcpush(*c); + break; + case '}': + case ']': + case ')': + if (pbcpop(*c)) + return 1; + break; + } + c++; + } + return 0; +} + + +/* + * The following three functions, reset_*, are used in the bison + * code to switch context. As a consequence, we need to + * declare them global and add a prototype so that the + * compiler does not complain. + * + * NOTE: yyg is declared because it is used in the BEGIN macros, + * though that should be hidden as the macro changes + * depending on the flex options that we use - in particular, + * %reentrant changes the way the macro is declared; + * without %reentrant, BEGIN uses yystart instead of yyg + */ + +void reset_parencount(yyscan_t yyscanner ); +void reset_parencount(yyscan_t yyscanner ) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + parencount = 0; + pbcpos = 0; + pbcpush('('); /* push '(' so the last pcbpop (parencount= -1) will succeed */ + c_prevword(); + BEGIN(paren); +} + +void reset_semicount(yyscan_t yyscanner ); +void reset_semicount(yyscan_t yyscanner ) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + pbcpos = 0; + BEGIN(semic); +} + +void reset_argcount(yyscan_t yyscanner ); +void reset_argcount(yyscan_t yyscanner ) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + parencount = 0; + pbcpos = 0; + pbcpush('('); /* push '(' so the last pcbpop (parencount= -1) will succeed */ + c_prevword(); + BEGIN(argg); +} + +/* used elsewhere, but some local vars */ +struct pval *ael2_parse(char *filename, int *errors) +{ + struct pval *pval; + struct parse_io *io; + char *buffer; + struct stat stats; + FILE *fin; + + /* extern int ael_yydebug; */ + + io = calloc(sizeof(struct parse_io),1); + /* reset the global counters */ + prev_word = 0; + my_lineno = 1; + include_stack_index=0; + my_col = 0; + /* ael_yydebug = 1; */ + ael_yylex_init(&io->scanner); + fin = fopen(filename,"r"); + if ( !fin ) { + ast_log(LOG_ERROR,"File %s could not be opened\n", filename); + *errors = 1; + return 0; + } + if (my_file) + free(my_file); + my_file = strdup(filename); + stat(filename, &stats); + buffer = (char*)malloc(stats.st_size+2); + fread(buffer, 1, stats.st_size, fin); + buffer[stats.st_size]=0; + fclose(fin); + + ael_yy_scan_string (buffer ,io->scanner); + ael_yyset_lineno(1 , io->scanner); + + /* ael_yyset_in (fin , io->scanner); OLD WAY */ + + ael_yyparse(io); + + + pval = io->pval; + *errors = io->syntax_error_count; + + ael_yylex_destroy(io->scanner); + free(buffer); + free(io); + + return pval; +} + +static void setup_filestack(char *fnamebuf2, int fnamebuf_siz, glob_t *globbuf, int globpos, yyscan_t yyscanner, int create) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + int error, i; + FILE *in1; + char fnamebuf[2048]; + + if (globbuf && globbuf->gl_pathv && globbuf->gl_pathc > 0) +#if defined(STANDALONE) || defined(LOW_MEMORY) || defined(STANDALONE_AEL) + strncpy(fnamebuf, globbuf->gl_pathv[globpos], fnamebuf_siz); +#else + ast_copy_string(fnamebuf, globbuf->gl_pathv[globpos], fnamebuf_siz); +#endif + else { + ast_log(LOG_ERROR,"Include file name not present!\n"); + return; + } + for (i=0; i<include_stack_index; i++) { + if ( !strcmp(fnamebuf,include_stack[i].fname )) { + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Nice Try!!! But %s has already been included (perhaps by another file), and would cause an infinite loop of file inclusions!!! Include directive ignored\n", + my_file, my_lineno, my_col, fnamebuf); + break; + } + } + error = 1; + if (i == include_stack_index) + error = 0; /* we can use this file */ + if ( !error ) { /* valid file name */ + /* relative vs. absolute */ + if (fnamebuf[0] != '/') + snprintf(fnamebuf2, fnamebuf_siz, "%s/%s", ast_config_AST_CONFIG_DIR, fnamebuf); + else +#if defined(STANDALONE) || defined(LOW_MEMORY) || defined(STANDALONE_AEL) + strncpy(fnamebuf2, fnamebuf, fnamebuf_siz); +#else + ast_copy_string(fnamebuf2, fnamebuf, fnamebuf_siz); +#endif + in1 = fopen( fnamebuf2, "r" ); + + if ( ! in1 ) { + ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Couldn't find the include file: %s; ignoring the Include directive!\n", my_file, my_lineno, my_col, fnamebuf2); + } else { + char *buffer; + struct stat stats; + stat(fnamebuf2, &stats); + buffer = (char*)malloc(stats.st_size+1); + fread(buffer, 1, stats.st_size, in1); + buffer[stats.st_size] = 0; + ast_log(LOG_NOTICE," --Read in included file %s, %d chars\n",fnamebuf2, (int)stats.st_size); + fclose(in1); + if (my_file) + free(my_file); + my_file = strdup(fnamebuf2); + include_stack[include_stack_index].fname = strdup(my_file); + include_stack[include_stack_index].lineno = my_lineno; + include_stack[include_stack_index].colno = my_col+yyleng; + if (create) + include_stack[include_stack_index].globbuf = *globbuf; + + include_stack[include_stack_index].globbuf_pos = 0; + + include_stack[include_stack_index].bufstate = YY_CURRENT_BUFFER; + if (create) + include_stack_index++; + ael_yy_switch_to_buffer(ael_yy_scan_string (buffer ,yyscanner),yyscanner); + free(buffer); + my_lineno = 1; + my_col = 1; + BEGIN(INITIAL); + } + } +} + diff --git a/trunk/res/ael/pval.c b/trunk/res/ael/pval.c new file mode 100644 index 000000000..56794041d --- /dev/null +++ b/trunk/res/ael/pval.c @@ -0,0 +1,5435 @@ + +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2006, Digium, Inc. + * + * Steve Murphy <murf@parsetree.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Compile symbolic Asterisk Extension Logic into Asterisk extensions, version 2. + * + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <sys/types.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <regex.h> +#include <sys/stat.h> + +#include "asterisk/pbx.h" +#include "asterisk/config.h" +#include "asterisk/module.h" +#include "asterisk/logger.h" +#include "asterisk/cli.h" +#include "asterisk/app.h" +#include "asterisk/channel.h" +#include "asterisk/callerid.h" +#include "asterisk/pval.h" +#include "asterisk/ael_structs.h" +#ifdef AAL_ARGCHECK +#include "asterisk/argdesc.h" +#endif +#include "asterisk/utils.h" + +extern int localized_pbx_load_module(void); + +static char expr_output[2096]; +#define AST_PBX_MAX_STACK 128 + +/* these functions are in ../ast_expr2.fl */ + +static int errs, warns; +static int notes; +#ifdef STANDALONE +static int extensions_dot_conf_loaded = 0; +#endif +static char *registrar = "pbx_ael"; + +static pval *current_db; +static pval *current_context; +static pval *current_extension; + +static const char *match_context; +static const char *match_exten; +static const char *match_label; +static int in_abstract_context; +static int count_labels; /* true, put matcher in label counting mode */ +static int label_count; /* labels are only meant to be counted in a context or exten */ +static int return_on_context_match; +static pval *last_matched_label; +struct pval *match_pval(pval *item); +static void check_timerange(pval *p); +static void check_dow(pval *DOW); +static void check_day(pval *DAY); +static void check_month(pval *MON); +static void check_expr2_input(pval *expr, char *str); +static int extension_matches(pval *here, const char *exten, const char *pattern); +static void check_goto(pval *item); +static void find_pval_goto_item(pval *item, int lev); +static void find_pval_gotos(pval *item, int lev); +static int check_break(pval *item); +static int check_continue(pval *item); +static void check_label(pval *item); +static void check_macro_returns(pval *macro); + +static struct pval *find_label_in_current_context(char *exten, char *label, pval *curr_cont); +static struct pval *find_first_label_in_current_context(char *label, pval *curr_cont); +static void print_pval_list(FILE *fin, pval *item, int depth); + +static struct pval *find_label_in_current_extension(const char *label, pval *curr_ext); +static struct pval *find_label_in_current_db(const char *context, const char *exten, const char *label); +static pval *get_goto_target(pval *item); +static int label_inside_case(pval *label); +static void attach_exten(struct ael_extension **list, struct ael_extension *newmem); +static void fix_gotos_in_extensions(struct ael_extension *exten); +static pval *get_extension_or_contxt(pval *p); +static pval *get_contxt(pval *p); +static void remove_spaces_before_equals(char *str); + +/* PRETTY PRINTER FOR AEL: ============================================================================= */ + +static void print_pval(FILE *fin, pval *item, int depth) +{ + int i; + pval *lp; + + for (i=0; i<depth; i++) { + fprintf(fin, "\t"); /* depth == indentation */ + } + + switch ( item->type ) { + case PV_WORD: + fprintf(fin,"%s;\n", item->u1.str); /* usually, words are encapsulated in something else */ + break; + + case PV_MACRO: + fprintf(fin,"macro %s(", item->u1.str); + for (lp=item->u2.arglist; lp; lp=lp->next) { + if (lp != item->u2.arglist ) + fprintf(fin,", "); + fprintf(fin,"%s", lp->u1.str); + } + fprintf(fin,") {\n"); + print_pval_list(fin,item->u3.macro_statements,depth+1); + for (i=0; i<depth; i++) { + fprintf(fin,"\t"); /* depth == indentation */ + } + fprintf(fin,"};\n\n"); + break; + + case PV_CONTEXT: + if ( item->u3.abstract ) + fprintf(fin,"abstract context %s {\n", item->u1.str); + else + fprintf(fin,"context %s {\n", item->u1.str); + print_pval_list(fin,item->u2.statements,depth+1); + for (i=0; i<depth; i++) { + fprintf(fin,"\t"); /* depth == indentation */ + } + fprintf(fin,"};\n\n"); + break; + + case PV_MACRO_CALL: + fprintf(fin,"&%s(", item->u1.str); + for (lp=item->u2.arglist; lp; lp=lp->next) { + if ( lp != item->u2.arglist ) + fprintf(fin,", "); + fprintf(fin,"%s", lp->u1.str); + } + fprintf(fin,");\n"); + break; + + case PV_APPLICATION_CALL: + fprintf(fin,"%s(", item->u1.str); + for (lp=item->u2.arglist; lp; lp=lp->next) { + if ( lp != item->u2.arglist ) + fprintf(fin,","); + fprintf(fin,"%s", lp->u1.str); + } + fprintf(fin,");\n"); + break; + + case PV_CASE: + fprintf(fin,"case %s:\n", item->u1.str); + print_pval_list(fin,item->u2.statements, depth+1); + break; + + case PV_PATTERN: + fprintf(fin,"pattern %s:\n", item->u1.str); + print_pval_list(fin,item->u2.statements, depth+1); + break; + + case PV_DEFAULT: + fprintf(fin,"default:\n"); + print_pval_list(fin,item->u2.statements, depth+1); + break; + + case PV_CATCH: + fprintf(fin,"catch %s {\n", item->u1.str); + print_pval_list(fin,item->u2.statements, depth+1); + for (i=0; i<depth; i++) { + fprintf(fin,"\t"); /* depth == indentation */ + } + fprintf(fin,"};\n"); + break; + + case PV_SWITCHES: + fprintf(fin,"switches {\n"); + print_pval_list(fin,item->u1.list,depth+1); + for (i=0; i<depth; i++) { + fprintf(fin,"\t"); /* depth == indentation */ + } + fprintf(fin,"};\n"); + break; + + case PV_ESWITCHES: + fprintf(fin,"eswitches {\n"); + print_pval_list(fin,item->u1.list,depth+1); + for (i=0; i<depth; i++) { + fprintf(fin,"\t"); /* depth == indentation */ + } + fprintf(fin,"};\n"); + break; + + case PV_INCLUDES: + fprintf(fin,"includes {\n"); + for (lp=item->u1.list; lp; lp=lp->next) { + for (i=0; i<depth+1; i++) { + fprintf(fin,"\t"); /* depth == indentation */ + } + fprintf(fin,"%s", lp->u1.str); /* usually, words are encapsulated in something else */ + if (lp->u2.arglist) + fprintf(fin,"|%s|%s|%s|%s", + lp->u2.arglist->u1.str, + lp->u2.arglist->next->u1.str, + lp->u2.arglist->next->next->u1.str, + lp->u2.arglist->next->next->next->u1.str + ); + fprintf(fin,";\n"); /* usually, words are encapsulated in something else */ + } + + for (i=0; i<depth; i++) { + fprintf(fin,"\t"); /* depth == indentation */ + } + fprintf(fin,"};\n"); + break; + + case PV_STATEMENTBLOCK: + fprintf(fin,"{\n"); + print_pval_list(fin,item->u1.list, depth+1); + for (i=0; i<depth; i++) { + fprintf(fin,"\t"); /* depth == indentation */ + } + fprintf(fin,"}\n"); + break; + + case PV_VARDEC: + fprintf(fin,"%s=%s;\n", item->u1.str, item->u2.val); + break; + + case PV_LOCALVARDEC: + fprintf(fin,"local %s=%s;\n", item->u1.str, item->u2.val); + break; + + case PV_GOTO: + fprintf(fin,"goto %s", item->u1.list->u1.str); + if ( item->u1.list->next ) + fprintf(fin,",%s", item->u1.list->next->u1.str); + if ( item->u1.list->next && item->u1.list->next->next ) + fprintf(fin,",%s", item->u1.list->next->next->u1.str); + fprintf(fin,"\n"); + break; + + case PV_LABEL: + fprintf(fin,"%s:\n", item->u1.str); + break; + + case PV_FOR: + fprintf(fin,"for (%s; %s; %s)\n", item->u1.for_init, item->u2.for_test, item->u3.for_inc); + print_pval_list(fin,item->u4.for_statements,depth+1); + break; + + case PV_WHILE: + fprintf(fin,"while (%s)\n", item->u1.str); + print_pval_list(fin,item->u2.statements,depth+1); + break; + + case PV_BREAK: + fprintf(fin,"break;\n"); + break; + + case PV_RETURN: + fprintf(fin,"return;\n"); + break; + + case PV_CONTINUE: + fprintf(fin,"continue;\n"); + break; + + case PV_RANDOM: + case PV_IFTIME: + case PV_IF: + if ( item->type == PV_IFTIME ) { + + fprintf(fin,"ifTime ( %s|%s|%s|%s )\n", + item->u1.list->u1.str, + item->u1.list->next->u1.str, + item->u1.list->next->next->u1.str, + item->u1.list->next->next->next->u1.str + ); + } else if ( item->type == PV_RANDOM ) { + fprintf(fin,"random ( %s )\n", item->u1.str ); + } else + fprintf(fin,"if ( %s )\n", item->u1.str); + if ( item->u2.statements && item->u2.statements->next ) { + for (i=0; i<depth; i++) { + fprintf(fin,"\t"); /* depth == indentation */ + } + fprintf(fin,"{\n"); + print_pval_list(fin,item->u2.statements,depth+1); + for (i=0; i<depth; i++) { + fprintf(fin,"\t"); /* depth == indentation */ + } + if ( item->u3.else_statements ) + fprintf(fin,"}\n"); + else + fprintf(fin,"};\n"); + } else if (item->u2.statements ) { + print_pval_list(fin,item->u2.statements,depth+1); + } else { + if (item->u3.else_statements ) + fprintf(fin, " {} "); + else + fprintf(fin, " {}; "); + } + if ( item->u3.else_statements ) { + for (i=0; i<depth; i++) { + fprintf(fin,"\t"); /* depth == indentation */ + } + fprintf(fin,"else\n"); + print_pval_list(fin,item->u3.else_statements, depth); + } + break; + + case PV_SWITCH: + fprintf(fin,"switch( %s ) {\n", item->u1.str); + print_pval_list(fin,item->u2.statements,depth+1); + for (i=0; i<depth; i++) { + fprintf(fin,"\t"); /* depth == indentation */ + } + fprintf(fin,"}\n"); + break; + + case PV_EXTENSION: + if ( item->u4.regexten ) + fprintf(fin, "regexten "); + if ( item->u3.hints ) + fprintf(fin,"hints(%s) ", item->u3.hints); + + fprintf(fin,"%s => ", item->u1.str); + print_pval_list(fin,item->u2.statements,depth+1); + fprintf(fin,"\n"); + break; + + case PV_IGNOREPAT: + fprintf(fin,"ignorepat => %s;\n", item->u1.str); + break; + + case PV_GLOBALS: + fprintf(fin,"globals {\n"); + print_pval_list(fin,item->u1.statements,depth+1); + for (i=0; i<depth; i++) { + fprintf(fin,"\t"); /* depth == indentation */ + } + fprintf(fin,"}\n"); + break; + } +} + +static void print_pval_list(FILE *fin, pval *item, int depth) +{ + pval *i; + + for (i=item; i; i=i->next) { + print_pval(fin, i, depth); + } +} + +void ael2_print(char *fname, pval *tree) +{ + FILE *fin = fopen(fname,"w"); + if ( !fin ) { + ast_log(LOG_ERROR, "Couldn't open %s for writing.\n", fname); + return; + } + print_pval_list(fin, tree, 0); + fclose(fin); +} + + +/* EMPTY TEMPLATE FUNCS FOR AEL TRAVERSAL: ============================================================================= */ + +void traverse_pval_template(pval *item, int depth); +void traverse_pval_item_template(pval *item, int depth); + + +void traverse_pval_item_template(pval *item, int depth)/* depth comes in handy for a pretty print (indentation), + but you may not need it */ +{ + pval *lp; + + switch ( item->type ) { + case PV_WORD: + /* fields: item->u1.str == string associated with this (word). */ + break; + + case PV_MACRO: + /* fields: item->u1.str == name of macro + item->u2.arglist == pval list of PV_WORD arguments of macro, as given by user + item->u2.arglist->u1.str == argument + item->u2.arglist->next == next arg + + item->u3.macro_statements == pval list of statements in macro body. + */ + for (lp=item->u2.arglist; lp; lp=lp->next) { + + } + traverse_pval_item_template(item->u3.macro_statements,depth+1); + break; + + case PV_CONTEXT: + /* fields: item->u1.str == name of context + item->u2.statements == pval list of statements in context body + item->u3.abstract == int 1 if an abstract keyword were present + */ + traverse_pval_item_template(item->u2.statements,depth+1); + break; + + case PV_MACRO_CALL: + /* fields: item->u1.str == name of macro to call + item->u2.arglist == pval list of PV_WORD arguments of macro call, as given by user + item->u2.arglist->u1.str == argument + item->u2.arglist->next == next arg + */ + for (lp=item->u2.arglist; lp; lp=lp->next) { + } + break; + + case PV_APPLICATION_CALL: + /* fields: item->u1.str == name of application to call + item->u2.arglist == pval list of PV_WORD arguments of macro call, as given by user + item->u2.arglist->u1.str == argument + item->u2.arglist->next == next arg + */ + for (lp=item->u2.arglist; lp; lp=lp->next) { + } + break; + + case PV_CASE: + /* fields: item->u1.str == value of case + item->u2.statements == pval list of statements under the case + */ + traverse_pval_item_template(item->u2.statements,depth+1); + break; + + case PV_PATTERN: + /* fields: item->u1.str == value of case + item->u2.statements == pval list of statements under the case + */ + traverse_pval_item_template(item->u2.statements,depth+1); + break; + + case PV_DEFAULT: + /* fields: + item->u2.statements == pval list of statements under the case + */ + traverse_pval_item_template(item->u2.statements,depth+1); + break; + + case PV_CATCH: + /* fields: item->u1.str == name of extension to catch + item->u2.statements == pval list of statements in context body + */ + traverse_pval_item_template(item->u2.statements,depth+1); + break; + + case PV_SWITCHES: + /* fields: item->u1.list == pval list of PV_WORD elements, one per entry in the list + */ + traverse_pval_item_template(item->u1.list,depth+1); + break; + + case PV_ESWITCHES: + /* fields: item->u1.list == pval list of PV_WORD elements, one per entry in the list + */ + traverse_pval_item_template(item->u1.list,depth+1); + break; + + case PV_INCLUDES: + /* fields: item->u1.list == pval list of PV_WORD elements, one per entry in the list + item->u2.arglist == pval list of 4 PV_WORD elements for time values + */ + traverse_pval_item_template(item->u1.list,depth+1); + traverse_pval_item_template(item->u2.arglist,depth+1); + break; + + case PV_STATEMENTBLOCK: + /* fields: item->u1.list == pval list of statements in block, one per entry in the list + */ + traverse_pval_item_template(item->u1.list,depth+1); + break; + + case PV_LOCALVARDEC: + case PV_VARDEC: + /* fields: item->u1.str == variable name + item->u2.val == variable value to assign + */ + break; + + case PV_GOTO: + /* fields: item->u1.list == pval list of PV_WORD target names, up to 3, in order as given by user. + item->u1.list->u1.str == where the data on a PV_WORD will always be. + */ + + if ( item->u1.list->next ) + ; + if ( item->u1.list->next && item->u1.list->next->next ) + ; + + break; + + case PV_LABEL: + /* fields: item->u1.str == label name + */ + break; + + case PV_FOR: + /* fields: item->u1.for_init == a string containing the initalizer + item->u2.for_test == a string containing the loop test + item->u3.for_inc == a string containing the loop increment + + item->u4.for_statements == a pval list of statements in the for () + */ + traverse_pval_item_template(item->u4.for_statements,depth+1); + break; + + case PV_WHILE: + /* fields: item->u1.str == the while conditional, as supplied by user + + item->u2.statements == a pval list of statements in the while () + */ + traverse_pval_item_template(item->u2.statements,depth+1); + break; + + case PV_BREAK: + /* fields: none + */ + break; + + case PV_RETURN: + /* fields: none + */ + break; + + case PV_CONTINUE: + /* fields: none + */ + break; + + case PV_IFTIME: + /* fields: item->u1.list == there are 4 linked PV_WORDs here. + + item->u2.statements == a pval list of statements in the if () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + */ + traverse_pval_item_template(item->u2.statements,depth+1); + if ( item->u3.else_statements ) { + traverse_pval_item_template(item->u3.else_statements,depth+1); + } + break; + + case PV_RANDOM: + /* fields: item->u1.str == the random number expression, as supplied by user + + item->u2.statements == a pval list of statements in the if () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + */ + traverse_pval_item_template(item->u2.statements,depth+1); + if ( item->u3.else_statements ) { + traverse_pval_item_template(item->u3.else_statements,depth+1); + } + break; + + case PV_IF: + /* fields: item->u1.str == the if conditional, as supplied by user + + item->u2.statements == a pval list of statements in the if () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + */ + traverse_pval_item_template(item->u2.statements,depth+1); + if ( item->u3.else_statements ) { + traverse_pval_item_template(item->u3.else_statements,depth+1); + } + break; + + case PV_SWITCH: + /* fields: item->u1.str == the switch expression + + item->u2.statements == a pval list of statements in the switch, + (will be case statements, most likely!) + */ + traverse_pval_item_template(item->u2.statements,depth+1); + break; + + case PV_EXTENSION: + /* fields: item->u1.str == the extension name, label, whatever it's called + + item->u2.statements == a pval list of statements in the extension + item->u3.hints == a char * hint argument + item->u4.regexten == an int boolean. non-zero says that regexten was specified + */ + traverse_pval_item_template(item->u2.statements,depth+1); + break; + + case PV_IGNOREPAT: + /* fields: item->u1.str == the ignorepat data + */ + break; + + case PV_GLOBALS: + /* fields: item->u1.statements == pval list of statements, usually vardecs + */ + traverse_pval_item_template(item->u1.statements,depth+1); + break; + } +} + +void traverse_pval_template(pval *item, int depth) /* depth comes in handy for a pretty print (indentation), + but you may not need it */ +{ + pval *i; + + for (i=item; i; i=i->next) { + traverse_pval_item_template(i, depth); + } +} + + +/* SEMANTIC CHECKING FOR AEL: ============================================================================= */ + +/* (not all that is syntactically legal is good! */ + + +static void check_macro_returns(pval *macro) +{ + pval *i; + if (!macro->u3.macro_statements) + { + pval *z = calloc(1, sizeof(struct pval)); + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The macro %s is empty! I will insert a return.\n", + macro->filename, macro->startline, macro->endline, macro->u1.str); + + z->type = PV_RETURN; + z->startline = macro->startline; + z->endline = macro->endline; + z->startcol = macro->startcol; + z->endcol = macro->endcol; + z->filename = strdup(macro->filename); + + macro->u3.macro_statements = z; + return; + } + for (i=macro->u3.macro_statements; i; i=i->next) { + /* if the last statement in the list is not return, then insert a return there */ + if (i->next == NULL) { + if (i->type != PV_RETURN) { + pval *z = calloc(1, sizeof(struct pval)); + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The macro %s does not end with a return; I will insert one.\n", + macro->filename, macro->startline, macro->endline, macro->u1.str); + + z->type = PV_RETURN; + z->startline = macro->startline; + z->endline = macro->endline; + z->startcol = macro->startcol; + z->endcol = macro->endcol; + z->filename = strdup(macro->filename); + + i->next = z; + return; + } + } + } + return; +} + + + +static int extension_matches(pval *here, const char *exten, const char *pattern) +{ + int err1; + regex_t preg; + + /* simple case, they match exactly, the pattern and exten name */ + if (!strcmp(pattern,exten) == 0) + return 1; + + if (pattern[0] == '_') { + char reg1[2000]; + const char *p; + char *r = reg1; + + if ( strlen(pattern)*5 >= 2000 ) /* safety valve */ { + ast_log(LOG_ERROR,"Error: The pattern %s is way too big. Pattern matching cancelled.\n", + pattern); + return 0; + } + /* form a regular expression from the pattern, and then match it against exten */ + *r++ = '^'; /* what if the extension is a pattern ?? */ + *r++ = '_'; /* what if the extension is a pattern ?? */ + *r++ = '?'; + for (p=pattern+1; *p; p++) { + switch ( *p ) { + case 'X': + *r++ = '['; + *r++ = '0'; + *r++ = '-'; + *r++ = '9'; + *r++ = 'X'; + *r++ = ']'; + break; + + case 'Z': + *r++ = '['; + *r++ = '1'; + *r++ = '-'; + *r++ = '9'; + *r++ = 'Z'; + *r++ = ']'; + break; + + case 'N': + *r++ = '['; + *r++ = '2'; + *r++ = '-'; + *r++ = '9'; + *r++ = 'N'; + *r++ = ']'; + break; + + case '[': + while ( *p && *p != ']' ) { + *r++ = *p++; + } + if ( *p != ']') { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The extension pattern '%s' is missing a closing bracket \n", + here->filename, here->startline, here->endline, pattern); + } + break; + + case '.': + case '!': + *r++ = '.'; + *r++ = '*'; + break; + case '*': + *r++ = '\\'; + *r++ = '*'; + break; + default: + *r++ = *p; + break; + + } + } + *r++ = '$'; /* what if the extension is a pattern ?? */ + *r++ = *p++; /* put in the closing null */ + err1 = regcomp(&preg, reg1, REG_NOSUB|REG_EXTENDED); + if ( err1 ) { + char errmess[500]; + regerror(err1,&preg,errmess,sizeof(errmess)); + regfree(&preg); + ast_log(LOG_WARNING, "Regcomp of %s failed, error code %d\n", + reg1, err1); + return 0; + } + err1 = regexec(&preg, exten, 0, 0, 0); + regfree(&preg); + + if ( err1 ) { + /* ast_log(LOG_NOTICE,"*****************************[%d]Extension %s did not match %s(%s)\n", + err1,exten, pattern, reg1); */ + return 0; /* no match */ + } else { + /* ast_log(LOG_NOTICE,"*****************************Extension %s matched %s\n", + exten, pattern); */ + return 1; + } + + + } else { + if ( strcmp(exten,pattern) == 0 ) { + return 1; + } else + return 0; + } +} + + +static void check_expr2_input(pval *expr, char *str) +{ + int spaces = strspn(str,"\t \n"); + if ( !strncmp(str+spaces,"$[",2) ) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The expression '%s' is redundantly wrapped in '$[ ]'. \n", + expr->filename, expr->startline, expr->endline, str); + warns++; + } +} + +static void check_includes(pval *includes) +{ + struct pval *p4; + for (p4=includes->u1.list; p4; p4=p4->next) { + /* for each context pointed to, find it, then find a context/label that matches the + target here! */ + char *incl_context = p4->u1.str; + /* find a matching context name */ + struct pval *that_other_context = find_context(incl_context); + if (!that_other_context && strcmp(incl_context, "parkedcalls") != 0) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The included context '%s' cannot be found.\n\ + (You may ignore this warning if '%s' exists in extensions.conf, or is created by another module. I cannot check for those.)\n", + includes->filename, includes->startline, includes->endline, incl_context, incl_context); + warns++; + } + } +} + + +static void check_timerange(pval *p) +{ + char *times; + char *e; + int s1, s2; + int e1, e2; + + times = ast_strdupa(p->u1.str); + + /* Star is all times */ + if (ast_strlen_zero(times) || !strcmp(times, "*")) { + return; + } + /* Otherwise expect a range */ + e = strchr(times, '-'); + if (!e) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The time range format (%s) requires a '-' surrounded by two 24-hour times of day!\n", + p->filename, p->startline, p->endline, times); + warns++; + return; + } + *e = '\0'; + e++; + while (*e && !isdigit(*e)) + e++; + if (!*e) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The time range format (%s) is missing the end time!\n", + p->filename, p->startline, p->endline, p->u1.str); + warns++; + } + if (sscanf(times, "%d:%d", &s1, &s2) != 2) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The start time (%s) isn't quite right!\n", + p->filename, p->startline, p->endline, times); + warns++; + } + if (sscanf(e, "%d:%d", &e1, &e2) != 2) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The end time (%s) isn't quite right!\n", + p->filename, p->startline, p->endline, times); + warns++; + } + + s1 = s1 * 30 + s2/2; + if ((s1 < 0) || (s1 >= 24*30)) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The start time (%s) is out of range!\n", + p->filename, p->startline, p->endline, times); + warns++; + } + e1 = e1 * 30 + e2/2; + if ((e1 < 0) || (e1 >= 24*30)) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The end time (%s) is out of range!\n", + p->filename, p->startline, p->endline, e); + warns++; + } + return; +} + +static char *days[] = +{ + "sun", + "mon", + "tue", + "wed", + "thu", + "fri", + "sat", +}; + +/*! \brief get_dow: Get day of week */ +static void check_dow(pval *DOW) +{ + char *dow; + char *c; + /* The following line is coincidence, really! */ + int s, e; + + dow = ast_strdupa(DOW->u1.str); + + /* Check for all days */ + if (ast_strlen_zero(dow) || !strcmp(dow, "*")) + return; + /* Get start and ending days */ + c = strchr(dow, '-'); + if (c) { + *c = '\0'; + c++; + } else + c = NULL; + /* Find the start */ + s = 0; + while ((s < 7) && strcasecmp(dow, days[s])) s++; + if (s >= 7) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The day (%s) must be one of 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', or 'sat'!\n", + DOW->filename, DOW->startline, DOW->endline, dow); + warns++; + } + if (c) { + e = 0; + while ((e < 7) && strcasecmp(c, days[e])) e++; + if (e >= 7) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The end day (%s) must be one of 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', or 'sat'!\n", + DOW->filename, DOW->startline, DOW->endline, c); + warns++; + } + } else + e = s; +} + +static void check_day(pval *DAY) +{ + char *day; + char *c; + /* The following line is coincidence, really! */ + int s, e; + + day = ast_strdupa(DAY->u1.str); + + /* Check for all days */ + if (ast_strlen_zero(day) || !strcmp(day, "*")) { + return; + } + /* Get start and ending days */ + c = strchr(day, '-'); + if (c) { + *c = '\0'; + c++; + } + /* Find the start */ + if (sscanf(day, "%d", &s) != 1) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The start day of month (%s) must be a number!\n", + DAY->filename, DAY->startline, DAY->endline, day); + warns++; + } + else if ((s < 1) || (s > 31)) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The start day of month (%s) must be a number in the range [1-31]!\n", + DAY->filename, DAY->startline, DAY->endline, day); + warns++; + } + s--; + if (c) { + if (sscanf(c, "%d", &e) != 1) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The end day of month (%s) must be a number!\n", + DAY->filename, DAY->startline, DAY->endline, c); + warns++; + } + else if ((e < 1) || (e > 31)) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The end day of month (%s) must be a number in the range [1-31]!\n", + DAY->filename, DAY->startline, DAY->endline, day); + warns++; + } + e--; + } else + e = s; +} + +static char *months[] = +{ + "jan", + "feb", + "mar", + "apr", + "may", + "jun", + "jul", + "aug", + "sep", + "oct", + "nov", + "dec", +}; + +static void check_month(pval *MON) +{ + char *mon; + char *c; + /* The following line is coincidence, really! */ + int s, e; + + mon = ast_strdupa(MON->u1.str); + + /* Check for all days */ + if (ast_strlen_zero(mon) || !strcmp(mon, "*")) + return ; + /* Get start and ending days */ + c = strchr(mon, '-'); + if (c) { + *c = '\0'; + c++; + } + /* Find the start */ + s = 0; + while ((s < 12) && strcasecmp(mon, months[s])) s++; + if (s >= 12) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The start month (%s) must be a one of: 'jan', 'feb', ..., 'dec'!\n", + MON->filename, MON->startline, MON->endline, mon); + warns++; + } + if (c) { + e = 0; + while ((e < 12) && strcasecmp(mon, months[e])) e++; + if (e >= 12) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The end month (%s) must be a one of: 'jan', 'feb', ..., 'dec'!\n", + MON->filename, MON->startline, MON->endline, c); + warns++; + } + } else + e = s; +} + +static int check_break(pval *item) +{ + pval *p = item; + + while( p && p->type != PV_MACRO && p->type != PV_CONTEXT ) /* early cutout, sort of */ { + /* a break is allowed in WHILE, FOR, CASE, DEFAULT, PATTERN; otherwise, it don't make + no sense */ + if( p->type == PV_CASE || p->type == PV_DEFAULT || p->type == PV_PATTERN + || p->type == PV_WHILE || p->type == PV_FOR ) { + return 1; + } + p = p->dad; + } + ast_log(LOG_ERROR,"Error: file %s, line %d-%d: 'break' not in switch, for, or while statement!\n", + item->filename, item->startline, item->endline); + errs++; + + return 0; +} + +static int check_continue(pval *item) +{ + pval *p = item; + + while( p && p->type != PV_MACRO && p->type != PV_CONTEXT ) /* early cutout, sort of */ { + /* a break is allowed in WHILE, FOR, CASE, DEFAULT, PATTERN; otherwise, it don't make + no sense */ + if( p->type == PV_WHILE || p->type == PV_FOR ) { + return 1; + } + p = p->dad; + } + ast_log(LOG_ERROR,"Error: file %s, line %d-%d: 'continue' not in 'for' or 'while' statement!\n", + item->filename, item->startline, item->endline); + errs++; + + return 0; +} + +static struct pval *in_macro(pval *item) +{ + struct pval *curr; + curr = item; + while( curr ) { + if( curr->type == PV_MACRO ) { + return curr; + } + curr = curr->dad; + } + return 0; +} + +static struct pval *in_context(pval *item) +{ + struct pval *curr; + curr = item; + while( curr ) { + if( curr->type == PV_MACRO || curr->type == PV_CONTEXT ) { + return curr; + } + curr = curr->dad; + } + return 0; +} + + +/* general purpose goto finder */ + +static void check_label(pval *item) +{ + struct pval *curr; + struct pval *x; + int alright = 0; + + /* A label outside an extension just plain does not make sense! */ + + curr = item; + + while( curr ) { + if( curr->type == PV_MACRO || curr->type == PV_EXTENSION ) { + alright = 1; + break; + } + curr = curr->dad; + } + if( !alright ) + { + ast_log(LOG_ERROR,"Error: file %s, line %d-%d: Label %s is not within an extension or macro!\n", + item->filename, item->startline, item->endline, item->u1.str); + errs++; + } + + + /* basically, ensure that a label is not repeated in a context. Period. + The method: well, for each label, find the first label in the context + with the same name. If it's not the current label, then throw an error. */ + + + /* printf("==== check_label: ====\n"); */ + if( !current_extension ) + curr = current_context; + else + curr = current_extension; + + x = find_first_label_in_current_context((char *)item->u1.str, curr); + /* printf("Hey, check_label found with item = %x, and x is %x, and currcont is %x, label name is %s\n", item,x, current_context, (char *)item->u1.str); */ + if( x && x != item ) + { + ast_log(LOG_ERROR,"Error: file %s, line %d-%d: Duplicate label %s! Previously defined at file %s, line %d.\n", + item->filename, item->startline, item->endline, item->u1.str, x->filename, x->startline); + errs++; + } + /* printf("<<<<< check_label: ====\n"); */ +} + +static pval *get_goto_target(pval *item) +{ + /* just one item-- the label should be in the current extension */ + pval *curr_ext = get_extension_or_contxt(item); /* containing exten, or macro */ + pval *curr_cont; + + if (item->u1.list && !item->u1.list->next && !strstr((item->u1.list)->u1.str,"${")) { + struct pval *x = find_label_in_current_extension((char*)((item->u1.list)->u1.str), curr_ext); + return x; + } + + curr_cont = get_contxt(item); + + /* TWO items */ + if (item->u1.list->next && !item->u1.list->next->next) { + if (!strstr((item->u1.list)->u1.str,"${") + && !strstr(item->u1.list->next->u1.str,"${") ) /* Don't try to match variables */ { + struct pval *x = find_label_in_current_context((char *)item->u1.list->u1.str, (char *)item->u1.list->next->u1.str, curr_cont); + return x; + } + } + + /* All 3 items! */ + if (item->u1.list->next && item->u1.list->next->next) { + /* all three */ + pval *first = item->u1.list; + pval *second = item->u1.list->next; + pval *third = item->u1.list->next->next; + + if (!strstr((item->u1.list)->u1.str,"${") + && !strstr(item->u1.list->next->u1.str,"${") + && !strstr(item->u1.list->next->next->u1.str,"${")) /* Don't try to match variables */ { + struct pval *x = find_label_in_current_db((char*)first->u1.str, (char*)second->u1.str, (char*)third->u1.str); + if (!x) { + + struct pval *p3; + struct pval *that_context = find_context(item->u1.list->u1.str); + + /* the target of the goto could be in an included context!! Fancy that!! */ + /* look for includes in the current context */ + if (that_context) { + for (p3=that_context->u2.statements; p3; p3=p3->next) { + if (p3->type == PV_INCLUDES) { + struct pval *p4; + for (p4=p3->u1.list; p4; p4=p4->next) { + /* for each context pointed to, find it, then find a context/label that matches the + target here! */ + char *incl_context = p4->u1.str; + /* find a matching context name */ + struct pval *that_other_context = find_context(incl_context); + if (that_other_context) { + struct pval *x3; + x3 = find_label_in_current_context((char *)item->u1.list->next->u1.str, (char *)item->u1.list->next->next->u1.str, that_other_context); + if (x3) { + return x3; + } + } + } + } + } + } + } + return x; + } + } + return 0; +} + +static void check_goto(pval *item) +{ + /* check for the target of the goto-- does it exist? */ + if ( !(item->u1.list)->next && !(item->u1.list)->u1.str ) { + ast_log(LOG_ERROR,"Error: file %s, line %d-%d: goto: empty label reference found!\n", + item->filename, item->startline, item->endline); + errs++; + } + + /* just one item-- the label should be in the current extension */ + + if (item->u1.list && !item->u1.list->next && !strstr((item->u1.list)->u1.str,"${")) { + struct pval *z = get_extension_or_contxt(item); + struct pval *x = 0; + if (z) + x = find_label_in_current_extension((char*)((item->u1.list)->u1.str), z); /* if in macro, use current context instead */ + /* printf("Called find_label_in_current_extension with arg %s; current_extension is %x: %d\n", + (char*)((item->u1.list)->u1.str), current_extension?current_extension:current_context, current_extension?current_extension->type:current_context->type); */ + if (!x) { + ast_log(LOG_ERROR,"Error: file %s, line %d-%d: goto: no label %s exists in the current extension!\n", + item->filename, item->startline, item->endline, item->u1.list->u1.str); + errs++; + } + else + return; + } + + /* TWO items */ + if (item->u1.list->next && !item->u1.list->next->next) { + /* two items */ + /* printf("Calling find_label_in_current_context with args %s, %s\n", + (char*)((item->u1.list)->u1.str), (char *)item->u1.list->next->u1.str); */ + if (!strstr((item->u1.list)->u1.str,"${") + && !strstr(item->u1.list->next->u1.str,"${") ) /* Don't try to match variables */ { + struct pval *z = get_contxt(item); + struct pval *x = 0; + + if (z) + x = find_label_in_current_context((char *)item->u1.list->u1.str, (char *)item->u1.list->next->u1.str, z); + + if (!x) { + ast_log(LOG_ERROR,"Error: file %s, line %d-%d: goto: no label '%s,%s' exists in the current context, or any of its inclusions!\n", + item->filename, item->startline, item->endline, item->u1.list->u1.str, item->u1.list->next->u1.str ); + errs++; + } + else + return; + } + } + + /* All 3 items! */ + if (item->u1.list->next && item->u1.list->next->next) { + /* all three */ + pval *first = item->u1.list; + pval *second = item->u1.list->next; + pval *third = item->u1.list->next->next; + + /* printf("Calling find_label_in_current_db with args %s, %s, %s\n", + (char*)first->u1.str, (char*)second->u1.str, (char*)third->u1.str); */ + if (!strstr((item->u1.list)->u1.str,"${") + && !strstr(item->u1.list->next->u1.str,"${") + && !strstr(item->u1.list->next->next->u1.str,"${")) /* Don't try to match variables */ { + struct pval *x = find_label_in_current_db((char*)first->u1.str, (char*)second->u1.str, (char*)third->u1.str); + if (!x) { + struct pval *p3; + struct pval *found = 0; + struct pval *that_context = find_context(item->u1.list->u1.str); + + /* the target of the goto could be in an included context!! Fancy that!! */ + /* look for includes in the current context */ + if (that_context) { + for (p3=that_context->u2.statements; p3; p3=p3->next) { + if (p3->type == PV_INCLUDES) { + struct pval *p4; + for (p4=p3->u1.list; p4; p4=p4->next) { + /* for each context pointed to, find it, then find a context/label that matches the + target here! */ + char *incl_context = p4->u1.str; + /* find a matching context name */ + struct pval *that_other_context = find_context(incl_context); + if (that_other_context) { + struct pval *x3; + x3 = find_label_in_current_context((char *)item->u1.list->next->u1.str, (char *)item->u1.list->next->next->u1.str, that_other_context); + if (x3) { + found = x3; + break; + } + } + } + } + } + if (!found) { + ast_log(LOG_ERROR,"Error: file %s, line %d-%d: goto: no label %s|%s exists in the context %s or its inclusions!\n", + item->filename, item->startline, item->endline, item->u1.list->next->u1.str, item->u1.list->next->next->u1.str, item->u1.list->u1.str ); + errs++; + } else { + struct pval *mac = in_macro(item); /* is this goto inside a macro? */ + if( mac ) { /* yes! */ + struct pval *targ = in_context(found); + if( mac != targ ) + { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: It's bad form to have a goto in a macro to a target outside the macro!\n", + item->filename, item->startline, item->endline); + warns++; + } + } + } + } else { + /* here is where code would go to check for target existence in extensions.conf files */ +#ifdef STANDALONE + struct pbx_find_info pfiq = {.stacklen = 0 }; + extern int localized_pbx_load_module(void); + /* if this is a standalone, we will need to make sure the + localized load of extensions.conf is done */ + if (!extensions_dot_conf_loaded) { + localized_pbx_load_module(); + extensions_dot_conf_loaded++; + } + + pbx_find_extension(NULL, NULL, &pfiq, first->u1.str, second->u1.str, atoi(third->u1.str), + atoi(third->u1.str) ? NULL : third->u1.str, NULL, + atoi(third->u1.str) ? E_MATCH : E_FINDLABEL); + + if (pfiq.status != STATUS_SUCCESS) { + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: goto: Couldn't find goto target %s|%s|%s, not even in extensions.conf!\n", + item->filename, item->startline, item->endline, first->u1.str, second->u1.str, third->u1.str); + warns++; + } +#else + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: goto: Couldn't find goto target %s|%s|%s in the AEL code!\n", + item->filename, item->startline, item->endline, first->u1.str, second->u1.str, third->u1.str); + warns++; +#endif + } + } else { + struct pval *mac = in_macro(item); /* is this goto inside a macro? */ + if( mac ) { /* yes! */ + struct pval *targ = in_context(x); + if( mac != targ ) + { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: It's bad form to have a goto in a macro to a target outside the macro!\n", + item->filename, item->startline, item->endline); + warns++; + } + } + } + } + } +} + + +static void find_pval_goto_item(pval *item, int lev) +{ + struct pval *p4; + if (lev>100) { + ast_log(LOG_ERROR,"find_pval_goto in infinite loop!\n\n"); + return; + } + + switch ( item->type ) { + case PV_MACRO: + /* fields: item->u1.str == name of macro + item->u2.arglist == pval list of PV_WORD arguments of macro, as given by user + item->u2.arglist->u1.str == argument + item->u2.arglist->next == next arg + + item->u3.macro_statements == pval list of statements in macro body. + */ + + /* printf("Descending into matching macro %s\n", match_context); */ + find_pval_gotos(item->u3.macro_statements,lev+1); /* if we're just searching for a context, don't bother descending into them */ + + break; + + case PV_CONTEXT: + /* fields: item->u1.str == name of context + item->u2.statements == pval list of statements in context body + item->u3.abstract == int 1 if an abstract keyword were present + */ + break; + + case PV_CASE: + /* fields: item->u1.str == value of case + item->u2.statements == pval list of statements under the case + */ + find_pval_gotos(item->u2.statements,lev+1); + break; + + case PV_PATTERN: + /* fields: item->u1.str == value of case + item->u2.statements == pval list of statements under the case + */ + find_pval_gotos(item->u2.statements,lev+1); + break; + + case PV_DEFAULT: + /* fields: + item->u2.statements == pval list of statements under the case + */ + find_pval_gotos(item->u2.statements,lev+1); + break; + + case PV_CATCH: + /* fields: item->u1.str == name of extension to catch + item->u2.statements == pval list of statements in context body + */ + find_pval_gotos(item->u2.statements,lev+1); + break; + + case PV_STATEMENTBLOCK: + /* fields: item->u1.list == pval list of statements in block, one per entry in the list + */ + find_pval_gotos(item->u1.list,lev+1); + break; + + case PV_GOTO: + /* fields: item->u1.list == pval list of PV_WORD target names, up to 3, in order as given by user. + item->u1.list->u1.str == where the data on a PV_WORD will always be. + */ + check_goto(item); /* THE WHOLE FUNCTION OF THIS ENTIRE ROUTINE!!!! */ + break; + + case PV_INCLUDES: + /* fields: item->u1.list == pval list of PV_WORD elements, one per entry in the list + */ + for (p4=item->u1.list; p4; p4=p4->next) { + /* for each context pointed to, find it, then find a context/label that matches the + target here! */ + char *incl_context = p4->u1.str; + /* find a matching context name */ + struct pval *that_context = find_context(incl_context); + if (that_context) { + find_pval_gotos(that_context,lev+1); /* keep working up the includes */ + } + } + break; + + case PV_FOR: + /* fields: item->u1.for_init == a string containing the initalizer + item->u2.for_test == a string containing the loop test + item->u3.for_inc == a string containing the loop increment + + item->u4.for_statements == a pval list of statements in the for () + */ + find_pval_gotos(item->u4.for_statements,lev+1); + break; + + case PV_WHILE: + /* fields: item->u1.str == the while conditional, as supplied by user + + item->u2.statements == a pval list of statements in the while () + */ + find_pval_gotos(item->u2.statements,lev+1); + break; + + case PV_RANDOM: + /* fields: item->u1.str == the random number expression, as supplied by user + + item->u2.statements == a pval list of statements in the if () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + fall thru to PV_IF */ + + case PV_IFTIME: + /* fields: item->u1.list == the time values, 4 of them, as PV_WORD structs in a list + + item->u2.statements == a pval list of statements in the if () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + fall thru to PV_IF*/ + case PV_IF: + /* fields: item->u1.str == the if conditional, as supplied by user + + item->u2.statements == a pval list of statements in the if () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + */ + find_pval_gotos(item->u2.statements,lev+1); + + if (item->u3.else_statements) { + find_pval_gotos(item->u3.else_statements,lev+1); + } + break; + + case PV_SWITCH: + /* fields: item->u1.str == the switch expression + + item->u2.statements == a pval list of statements in the switch, + (will be case statements, most likely!) + */ + find_pval_gotos(item->u3.else_statements,lev+1); + break; + + case PV_EXTENSION: + /* fields: item->u1.str == the extension name, label, whatever it's called + + item->u2.statements == a pval list of statements in the extension + item->u3.hints == a char * hint argument + item->u4.regexten == an int boolean. non-zero says that regexten was specified + */ + + find_pval_gotos(item->u2.statements,lev+1); + break; + + default: + break; + } +} + +static void find_pval_gotos(pval *item,int lev) +{ + pval *i; + + for (i=item; i; i=i->next) { + + find_pval_goto_item(i, lev); + } +} + + + +/* general purpose label finder */ +static struct pval *match_pval_item(pval *item) +{ + pval *x; + + switch ( item->type ) { + case PV_MACRO: + /* fields: item->u1.str == name of macro + item->u2.arglist == pval list of PV_WORD arguments of macro, as given by user + item->u2.arglist->u1.str == argument + item->u2.arglist->next == next arg + + item->u3.macro_statements == pval list of statements in macro body. + */ + /* printf(" matching in MACRO %s, match_context=%s; retoncontmtch=%d; \n", item->u1.str, match_context, return_on_context_match); */ + if (!strcmp(match_context,"*") || !strcmp(item->u1.str, match_context)) { + + /* printf("MACRO: match context is: %s\n", match_context); */ + + if (return_on_context_match && !strcmp(item->u1.str, match_context)) /* if we're just searching for a context, don't bother descending into them */ { + /* printf("Returning on matching macro %s\n", match_context); */ + return item; + } + + + if (!return_on_context_match) { + /* printf("Descending into matching macro %s/%s\n", match_context, item->u1.str); */ + if ((x=match_pval(item->u3.macro_statements))) { + /* printf("Responded with pval match %x\n", x); */ + return x; + } + } + } else { + /* printf("Skipping context/macro %s\n", item->u1.str); */ + } + + break; + + case PV_CONTEXT: + /* fields: item->u1.str == name of context + item->u2.statements == pval list of statements in context body + item->u3.abstract == int 1 if an abstract keyword were present + */ + /* printf(" matching in CONTEXT\n"); */ + if (!strcmp(match_context,"*") || !strcmp(item->u1.str, match_context)) { + if (return_on_context_match && !strcmp(item->u1.str, match_context)) { + /* printf("Returning on matching context %s\n", match_context); */ + /* printf("non-CONTEXT: Responded with pval match %x\n", x); */ + return item; + } + + if (!return_on_context_match ) { + /* printf("Descending into matching context %s\n", match_context); */ + if ((x=match_pval(item->u2.statements))) /* if we're just searching for a context, don't bother descending into them */ { + /* printf("CONTEXT: Responded with pval match %x\n", x); */ + return x; + } + } + } else { + /* printf("Skipping context/macro %s\n", item->u1.str); */ + } + break; + + case PV_CASE: + /* fields: item->u1.str == value of case + item->u2.statements == pval list of statements under the case + */ + /* printf(" matching in CASE\n"); */ + if ((x=match_pval(item->u2.statements))) { + /* printf("CASE: Responded with pval match %x\n", x); */ + return x; + } + break; + + case PV_PATTERN: + /* fields: item->u1.str == value of case + item->u2.statements == pval list of statements under the case + */ + /* printf(" matching in PATTERN\n"); */ + if ((x=match_pval(item->u2.statements))) { + /* printf("PATTERN: Responded with pval match %x\n", x); */ + return x; + } + break; + + case PV_DEFAULT: + /* fields: + item->u2.statements == pval list of statements under the case + */ + /* printf(" matching in DEFAULT\n"); */ + if ((x=match_pval(item->u2.statements))) { + /* printf("DEFAULT: Responded with pval match %x\n", x); */ + return x; + } + break; + + case PV_CATCH: + /* fields: item->u1.str == name of extension to catch + item->u2.statements == pval list of statements in context body + */ + /* printf(" matching in CATCH\n"); */ + if ((x=match_pval(item->u2.statements))) { + /* printf("CATCH: Responded with pval match %x\n", x); */ + return x; + } + break; + + case PV_STATEMENTBLOCK: + /* fields: item->u1.list == pval list of statements in block, one per entry in the list + */ + /* printf(" matching in STATEMENTBLOCK\n"); */ + if ((x=match_pval(item->u1.list))) { + /* printf("STATEMENTBLOCK: Responded with pval match %x\n", x); */ + return x; + } + break; + + case PV_LABEL: + /* fields: item->u1.str == label name + */ + /* printf("PV_LABEL %s (cont=%s, exten=%s\n", + item->u1.str, current_context->u1.str, (current_extension?current_extension->u1.str:"<macro>"));*/ + + if (count_labels) { + if (!strcmp(match_label, item->u1.str)) { + label_count++; + last_matched_label = item; + } + + } else { + if (!strcmp(match_label, item->u1.str)) { + /* printf("LABEL: Responded with pval match %x\n", x); */ + return item; + } + } + break; + + case PV_FOR: + /* fields: item->u1.for_init == a string containing the initalizer + item->u2.for_test == a string containing the loop test + item->u3.for_inc == a string containing the loop increment + + item->u4.for_statements == a pval list of statements in the for () + */ + /* printf(" matching in FOR\n"); */ + if ((x=match_pval(item->u4.for_statements))) { + /* printf("FOR: Responded with pval match %x\n", x);*/ + return x; + } + break; + + case PV_WHILE: + /* fields: item->u1.str == the while conditional, as supplied by user + + item->u2.statements == a pval list of statements in the while () + */ + /* printf(" matching in WHILE\n"); */ + if ((x=match_pval(item->u2.statements))) { + /* printf("WHILE: Responded with pval match %x\n", x); */ + return x; + } + break; + + case PV_RANDOM: + /* fields: item->u1.str == the random number expression, as supplied by user + + item->u2.statements == a pval list of statements in the if () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + fall thru to PV_IF */ + + case PV_IFTIME: + /* fields: item->u1.list == the time values, 4 of them, as PV_WORD structs in a list + + item->u2.statements == a pval list of statements in the if () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + fall thru to PV_IF*/ + case PV_IF: + /* fields: item->u1.str == the if conditional, as supplied by user + + item->u2.statements == a pval list of statements in the if () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + */ + /* printf(" matching in IF/IFTIME/RANDOM\n"); */ + if ((x=match_pval(item->u2.statements))) { + return x; + } + if (item->u3.else_statements) { + if ((x=match_pval(item->u3.else_statements))) { + /* printf("IF/IFTIME/RANDOM: Responded with pval match %x\n", x); */ + return x; + } + } + break; + + case PV_SWITCH: + /* fields: item->u1.str == the switch expression + + item->u2.statements == a pval list of statements in the switch, + (will be case statements, most likely!) + */ + /* printf(" matching in SWITCH\n"); */ + if ((x=match_pval(item->u2.statements))) { + /* printf("SWITCH: Responded with pval match %x\n", x); */ + return x; + } + break; + + case PV_EXTENSION: + /* fields: item->u1.str == the extension name, label, whatever it's called + + item->u2.statements == a pval list of statements in the extension + item->u3.hints == a char * hint argument + item->u4.regexten == an int boolean. non-zero says that regexten was specified + */ + /* printf(" matching in EXTENSION\n"); */ + if (!strcmp(match_exten,"*") || extension_matches(item, match_exten, item->u1.str) ) { + /* printf("Descending into matching exten %s => %s\n", match_exten, item->u1.str); */ + if (strcmp(match_label,"1") == 0) { + if (item->u2.statements) { + struct pval *p5 = item->u2.statements; + while (p5 && p5->type == PV_LABEL) /* find the first non-label statement in this context. If it exists, there's a "1" */ + p5 = p5->next; + if (p5) + return p5; + else + return 0; + } + else + return 0; + } + + if ((x=match_pval(item->u2.statements))) { + /* printf("EXTENSION: Responded with pval match %x\n", x); */ + return x; + } + } else { + /* printf("Skipping exten %s\n", item->u1.str); */ + } + break; + default: + /* printf(" matching in default = %d\n", item->type); */ + break; + } + return 0; +} + +struct pval *match_pval(pval *item) +{ + pval *i; + + for (i=item; i; i=i->next) { + pval *x; + /* printf(" -- match pval: item %d\n", i->type); */ + + if ((x = match_pval_item(i))) { + /* printf("match_pval: returning x=%x\n", (int)x); */ + return x; /* cut the search short */ + } + } + return 0; +} + +#if 0 +int count_labels_in_current_context(char *label) +{ + label_count = 0; + count_labels = 1; + return_on_context_match = 0; + match_pval(current_context->u2.statements); + + return label_count; +} +#endif + +struct pval *find_first_label_in_current_context(char *label, pval *curr_cont) +{ + /* printf(" --- Got args %s, %s\n", exten, label); */ + struct pval *ret; + struct pval *p3; + + count_labels = 0; + return_on_context_match = 0; + match_context = "*"; + match_exten = "*"; + match_label = label; + + ret = match_pval(curr_cont); + if (ret) + return ret; + + /* the target of the goto could be in an included context!! Fancy that!! */ + /* look for includes in the current context */ + for (p3=curr_cont->u2.statements; p3; p3=p3->next) { + if (p3->type == PV_INCLUDES) { + struct pval *p4; + for (p4=p3->u1.list; p4; p4=p4->next) { + /* for each context pointed to, find it, then find a context/label that matches the + target here! */ + char *incl_context = p4->u1.str; + /* find a matching context name */ + struct pval *that_context = find_context(incl_context); + if (that_context) { + struct pval *x3; + x3 = find_first_label_in_current_context(label, that_context); + if (x3) { + return x3; + } + } + } + } + } + return 0; +} + +struct pval *find_label_in_current_context(char *exten, char *label, pval *curr_cont) +{ + /* printf(" --- Got args %s, %s\n", exten, label); */ + struct pval *ret; + struct pval *p3; + + count_labels = 0; + return_on_context_match = 0; + match_context = "*"; + match_exten = exten; + match_label = label; + ret = match_pval(curr_cont->u2.statements); + if (ret) + return ret; + + /* the target of the goto could be in an included context!! Fancy that!! */ + /* look for includes in the current context */ + for (p3=curr_cont->u2.statements; p3; p3=p3->next) { + if (p3->type == PV_INCLUDES) { + struct pval *p4; + for (p4=p3->u1.list; p4; p4=p4->next) { + /* for each context pointed to, find it, then find a context/label that matches the + target here! */ + char *incl_context = p4->u1.str; + /* find a matching context name */ + struct pval *that_context = find_context(incl_context); + if (that_context) { + struct pval *x3; + x3 = find_label_in_current_context(exten, label, that_context); + if (x3) { + return x3; + } + } + } + } + } + return 0; +} + +static struct pval *find_label_in_current_extension(const char *label, pval *curr_ext) +{ + /* printf(" --- Got args %s\n", label); */ + count_labels = 0; + return_on_context_match = 0; + match_context = "*"; + match_exten = "*"; + match_label = label; + return match_pval(curr_ext); +} + +static struct pval *find_label_in_current_db(const char *context, const char *exten, const char *label) +{ + /* printf(" --- Got args %s, %s, %s\n", context, exten, label); */ + count_labels = 0; + return_on_context_match = 0; + + match_context = context; + match_exten = exten; + match_label = label; + + return match_pval(current_db); +} + + +struct pval *find_macro(char *name) +{ + return_on_context_match = 1; + count_labels = 0; + match_context = name; + match_exten = "*"; /* don't really need to set these, shouldn't be reached */ + match_label = "*"; + return match_pval(current_db); +} + +struct pval *find_context(char *name) +{ + return_on_context_match = 1; + count_labels = 0; + match_context = name; + match_exten = "*"; /* don't really need to set these, shouldn't be reached */ + match_label = "*"; + return match_pval(current_db); +} + +int is_float(char *arg ) +{ + char *s; + for (s=arg; *s; s++) { + if (*s != '.' && (*s < '0' || *s > '9')) + return 0; + } + return 1; +} +int is_int(char *arg ) +{ + char *s; + for (s=arg; *s; s++) { + if (*s < '0' || *s > '9') + return 0; + } + return 1; +} +int is_empty(char *arg) +{ + if (!arg) + return 1; + if (*arg == 0) + return 1; + while (*arg) { + if (*arg != ' ' && *arg != '\t') + return 0; + arg++; + } + return 1; +} + +#ifdef AAL_ARGCHECK +int option_matches_j( struct argdesc *should, pval *is, struct argapp *app) +{ + struct argchoice *ac; + char *opcop,*q,*p; + + switch (should->dtype) { + case ARGD_OPTIONSET: + if ( strstr(is->u1.str,"${") ) + return 0; /* no checking anything if there's a var reference in there! */ + + opcop = ast_strdupa(is->u1.str); + + for (q=opcop;*q;q++) { /* erase the innards of X(innard) type arguments, so we don't get confused later */ + if ( *q == '(' ) { + p = q+1; + while (*p && *p != ')' ) + *p++ = '+'; + q = p+1; + } + } + + for (ac=app->opts; ac; ac=ac->next) { + if (strlen(ac->name)>1 && strchr(ac->name,'(') == 0 && strcmp(ac->name,is->u1.str) == 0) /* multichar option, no parens, and a match? */ + return 0; + } + for (ac=app->opts; ac; ac=ac->next) { + if (strlen(ac->name)==1 || strchr(ac->name,'(')) { + char *p = strchr(opcop,ac->name[0]); /* wipe out all matched options in the user-supplied string */ + + if (p && *p == 'j') { + ast_log(LOG_ERROR, "Error: file %s, line %d-%d: The j option in the %s application call is not appropriate for AEL!\n", + is->filename, is->startline, is->endline, app->name); + errs++; + } + + if (p) { + *p = '+'; + if (ac->name[1] == '(') { + if (*(p+1) != '(') { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The %c option in the %s application call should have an (argument), but doesn't!\n", + is->filename, is->startline, is->endline, ac->name[0], app->name); + warns++; + } + } + } + } + } + for (q=opcop; *q; q++) { + if ( *q != '+' && *q != '(' && *q != ')') { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: The %c option in the %s application call is not available as an option!\n", + is->filename, is->startline, is->endline, *q, app->name); + warns++; + } + } + return 1; + break; + default: + return 0; + } + +} + +int option_matches( struct argdesc *should, pval *is, struct argapp *app) +{ + struct argchoice *ac; + char *opcop; + + switch (should->dtype) { + case ARGD_STRING: + if (is_empty(is->u1.str) && should->type == ARGD_REQUIRED) + return 0; + if (is->u1.str && strlen(is->u1.str) > 0) /* most will match */ + return 1; + break; + + case ARGD_INT: + if (is_int(is->u1.str)) + return 1; + else + return 0; + break; + + case ARGD_FLOAT: + if (is_float(is->u1.str)) + return 1; + else + return 0; + break; + + case ARGD_ENUM: + if( !is->u1.str || strlen(is->u1.str) == 0 ) + return 1; /* a null arg in the call will match an enum, I guess! */ + for (ac=should->choices; ac; ac=ac->next) { + if (strcmp(ac->name,is->u1.str) == 0) + return 1; + } + return 0; + break; + + case ARGD_OPTIONSET: + opcop = ast_strdupa(is->u1.str); + + for (ac=app->opts; ac; ac=ac->next) { + if (strlen(ac->name)>1 && strchr(ac->name,'(') == 0 && strcmp(ac->name,is->u1.str) == 0) /* multichar option, no parens, and a match? */ + return 1; + } + for (ac=app->opts; ac; ac=ac->next) { + if (strlen(ac->name)==1 || strchr(ac->name,'(')) { + char *p = strchr(opcop,ac->name[0]); /* wipe out all matched options in the user-supplied string */ + + if (p) { + *p = '+'; + if (ac->name[1] == '(') { + if (*(p+1) == '(') { + char *q = p+1; + while (*q && *q != ')') { + *q++ = '+'; + } + *q = '+'; + } + } + } + } + } + return 1; + break; + case ARGD_VARARG: + return 1; /* matches anything */ + break; + } + return 1; /* unless some for-sure match or non-match returns, then it must be close enough ... */ +} +#endif + +int check_app_args(pval* appcall, pval *arglist, struct argapp *app) +{ +#ifdef AAL_ARGCHECK + struct argdesc *ad = app->args; + pval *pa; + int z; + + for (pa = arglist; pa; pa=pa->next) { + if (!ad) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: Extra argument %s not in application call to %s !\n", + arglist->filename, arglist->startline, arglist->endline, pa->u1.str, app->name); + warns++; + return 1; + } else { + /* find the first entry in the ad list that will match */ + do { + if ( ad->dtype == ARGD_VARARG ) /* once we hit the VARARG, all bets are off. Discontinue the comparisons */ + break; + + z= option_matches( ad, pa, app); + if (!z) { + if ( !arglist ) + arglist=appcall; + + if (ad->type == ARGD_REQUIRED) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: Required argument %s not in application call to %s !\n", + arglist->filename, arglist->startline, arglist->endline, ad->dtype==ARGD_OPTIONSET?"options":ad->name, app->name); + warns++; + return 1; + } + } else if (z && ad->dtype == ARGD_OPTIONSET) { + option_matches_j( ad, pa, app); + } + ad = ad->next; + } while (ad && !z); + } + } + /* any app nodes left, that are not optional? */ + for ( ; ad; ad=ad->next) { + if (ad->type == ARGD_REQUIRED && ad->dtype != ARGD_VARARG) { + if ( !arglist ) + arglist=appcall; + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: Required argument %s not in application call to %s !\n", + arglist->filename, arglist->startline, arglist->endline, ad->dtype==ARGD_OPTIONSET?"options":ad->name, app->name); + warns++; + return 1; + } + } + return 0; +#else + return 0; +#endif +} + +void check_switch_expr(pval *item, struct argapp *apps) +{ +#ifdef AAL_ARGCHECK + /* get and clean the variable name */ + char *buff1, *p; + struct argapp *a,*a2; + struct appsetvar *v,*v2; + struct argchoice *c; + pval *t; + + p = item->u1.str; + while (p && *p && (*p == ' ' || *p == '\t' || *p == '$' || *p == '{' ) ) + p++; + + buff1 = ast_strdupa(p); + + while (strlen(buff1) > 0 && ( buff1[strlen(buff1)-1] == '}' || buff1[strlen(buff1)-1] == ' ' || buff1[strlen(buff1)-1] == '\t')) + buff1[strlen(buff1)-1] = 0; + /* buff1 now contains the variable name */ + v = 0; + for (a=apps; a; a=a->next) { + for (v=a->setvars;v;v=v->next) { + if (strcmp(v->name,buff1) == 0) { + break; + } + } + if ( v ) + break; + } + if (v && v->vals) { + /* we have a match, to a variable that has a set of determined values */ + int def= 0; + int pat = 0; + int f1 = 0; + + /* first of all, does this switch have a default case ? */ + for (t=item->u2.statements; t; t=t->next) { + if (t->type == PV_DEFAULT) { + def =1; + break; + } + if (t->type == PV_PATTERN) { + pat++; + } + } + if (def || pat) /* nothing to check. All cases accounted for! */ + return; + for (c=v->vals; c; c=c->next) { + f1 = 0; + for (t=item->u2.statements; t; t=t->next) { + if (t->type == PV_CASE || t->type == PV_PATTERN) { + if (!strcmp(t->u1.str,c->name)) { + f1 = 1; + break; + } + } + } + if (!f1) { + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: switch with expression(%s) does not handle the case of %s !\n", + item->filename, item->startline, item->endline, item->u1.str, c->name); + warns++; + } + } + /* next, is there an app call in the current exten, that would set this var? */ + f1 = 0; + t = current_extension->u2.statements; + if ( t && t->type == PV_STATEMENTBLOCK ) + t = t->u1.statements; + for (; t && t != item; t=t->next) { + if (t->type == PV_APPLICATION_CALL) { + /* find the application that matches the u1.str */ + for (a2=apps; a2; a2=a2->next) { + if (strcasecmp(a2->name, t->u1.str)==0) { + for (v2=a2->setvars; v2; v2=v2->next) { + if (strcmp(v2->name, buff1) == 0) { + /* found an app that sets the var */ + f1 = 1; + break; + } + } + } + if (f1) + break; + } + } + if (f1) + break; + } + + /* see if it sets the var */ + if (!f1) { + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: Couldn't find an application call in this extension that sets the expression (%s) value!\n", + item->filename, item->startline, item->endline, item->u1.str); + warns++; + } + } +#else + pval *t,*tl=0,*p2; + int def= 0; + + /* first of all, does this switch have a default case ? */ + for (t=item->u2.statements; t; t=t->next) { + if (t->type == PV_DEFAULT) { + def =1; + break; + } + tl = t; + } + if (def) /* nothing to check. All cases accounted for! */ + return; + /* if no default, warn and insert a default case at the end */ + p2 = tl->next = calloc(1, sizeof(struct pval)); + + p2->type = PV_DEFAULT; + p2->startline = tl->startline; + p2->endline = tl->endline; + p2->startcol = tl->startcol; + p2->endcol = tl->endcol; + p2->filename = strdup(tl->filename); + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: A default case was automatically added to the switch.\n", + p2->filename, p2->startline, p2->endline); + warns++; + +#endif +} + +static void check_context_names(void) +{ + pval *i,*j; + for (i=current_db; i; i=i->next) { + if (i->type == PV_CONTEXT || i->type == PV_MACRO) { + for (j=i->next; j; j=j->next) { + if ( j->type == PV_CONTEXT || j->type == PV_MACRO ) { + if ( !strcmp(i->u1.str, j->u1.str) && !(i->u3.abstract&2) && !(j->u3.abstract&2) ) + { + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: The context name (%s) is also declared in file %s, line %d-%d! (and neither is marked 'extend')\n", + i->filename, i->startline, i->endline, i->u1.str, j->filename, j->startline, j->endline); + warns++; + } + } + } + } + } +} + +static void check_abstract_reference(pval *abstract_context) +{ + pval *i,*j; + /* find some context includes that reference this context */ + + + /* otherwise, print out a warning */ + for (i=current_db; i; i=i->next) { + if (i->type == PV_CONTEXT) { + for (j=i->u2. statements; j; j=j->next) { + if ( j->type == PV_INCLUDES ) { + struct pval *p4; + for (p4=j->u1.list; p4; p4=p4->next) { + /* for each context pointed to, find it, then find a context/label that matches the + target here! */ + if ( !strcmp(p4->u1.str, abstract_context->u1.str) ) + return; /* found a match! */ + } + } + } + } + } + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: Couldn't find a reference to this abstract context (%s) in any other context!\n", + abstract_context->filename, abstract_context->startline, abstract_context->endline, abstract_context->u1.str); + warns++; +} + + +void check_pval_item(pval *item, struct argapp *apps, int in_globals) +{ + pval *lp; +#ifdef AAL_ARGCHECK + struct argapp *app, *found; +#endif + struct pval *macro_def; + struct pval *app_def; + + char errmsg[4096]; + char *strp; + + switch (item->type) { + case PV_WORD: + /* fields: item->u1.str == string associated with this (word). + item->u2.arglist == pval list of 4 PV_WORD elements for time values (only in PV_INCLUDES) */ + break; + + case PV_MACRO: + /* fields: item->u1.str == name of macro + item->u2.arglist == pval list of PV_WORD arguments of macro, as given by user + item->u2.arglist->u1.str == argument + item->u2.arglist->next == next arg + + item->u3.macro_statements == pval list of statements in macro body. + */ + in_abstract_context = 0; + current_context = item; + current_extension = 0; + + check_macro_returns(item); + + for (lp=item->u2.arglist; lp; lp=lp->next) { + + } + check_pval(item->u3.macro_statements, apps,in_globals); + break; + + case PV_CONTEXT: + /* fields: item->u1.str == name of context + item->u2.statements == pval list of statements in context body + item->u3.abstract == int 1 if an abstract keyword were present + */ + current_context = item; + current_extension = 0; + if ( item->u3.abstract ) { + in_abstract_context = 1; + check_abstract_reference(item); + } else + in_abstract_context = 0; + check_pval(item->u2.statements, apps,in_globals); + break; + + case PV_MACRO_CALL: + /* fields: item->u1.str == name of macro to call + item->u2.arglist == pval list of PV_WORD arguments of macro call, as given by user + item->u2.arglist->u1.str == argument + item->u2.arglist->next == next arg + */ +#ifdef STANDALONE + /* if this is a standalone, we will need to make sure the + localized load of extensions.conf is done */ + if (!extensions_dot_conf_loaded) { + localized_pbx_load_module(); + extensions_dot_conf_loaded++; + } +#endif + macro_def = find_macro(item->u1.str); + if (!macro_def) { +#ifdef STANDALONE + struct pbx_find_info pfiq = {.stacklen = 0 }; + struct pbx_find_info pfiq2 = {.stacklen = 0 }; + + /* look for the macro in the extensions.conf world */ + pbx_find_extension(NULL, NULL, &pfiq, item->u1.str, "s", 1, NULL, NULL, E_MATCH); + + if (pfiq.status != STATUS_SUCCESS) { + char namebuf2[256]; + snprintf(namebuf2, 256, "macro-%s", item->u1.str); + + /* look for the macro in the extensions.conf world */ + pbx_find_extension(NULL, NULL, &pfiq2, namebuf2, "s", 1, NULL, NULL, E_MATCH); + + if (pfiq2.status == STATUS_SUCCESS) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: macro call to non-existent %s! (macro-%s was found in the extensions.conf stuff, but we are using gosubs!)\n", + item->filename, item->startline, item->endline, item->u1.str, item->u1.str); + warns++; + } else { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: macro call to non-existent %s! (Not even in the extensions.conf stuff!)\n", + item->filename, item->startline, item->endline, item->u1.str); + warns++; + } + } +#else + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: macro call to %s cannot be found in the AEL code!\n", + item->filename, item->startline, item->endline, item->u1.str); + warns++; + +#endif +#ifdef THIS_IS_1DOT4 + char namebuf2[256]; + snprintf(namebuf2, 256, "macro-%s", item->u1.str); + + /* look for the macro in the extensions.conf world */ + pbx_find_extension(NULL, NULL, &pfiq, namebuf2, "s", 1, NULL, NULL, E_MATCH); + + if (pfiq.status != STATUS_SUCCESS) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: macro call to %s was not found in the AEL, nor the extensions.conf !\n", + item->filename, item->startline, item->endline, item->u1.str); + warns++; + } + +#endif + + } else if (macro_def->type != PV_MACRO) { + ast_log(LOG_ERROR,"Error: file %s, line %d-%d: macro call to %s references a context, not a macro!\n", + item->filename, item->startline, item->endline, item->u1.str); + errs++; + } else { + /* macro_def is a MACRO, so do the args match in number? */ + int hereargs = 0; + int thereargs = 0; + + for (lp=item->u2.arglist; lp; lp=lp->next) { + hereargs++; + } + for (lp=macro_def->u2.arglist; lp; lp=lp->next) { + thereargs++; + } + if (hereargs != thereargs ) { + ast_log(LOG_ERROR, "Error: file %s, line %d-%d: The macro call to %s has %d arguments, but the macro definition has %d arguments\n", + item->filename, item->startline, item->endline, item->u1.str, hereargs, thereargs); + errs++; + } + } + break; + + case PV_APPLICATION_CALL: + /* fields: item->u1.str == name of application to call + item->u2.arglist == pval list of PV_WORD arguments of macro call, as given by user + item->u2.arglist->u1.str == argument + item->u2.arglist->next == next arg + */ + /* Need to check to see if the application is available! */ + app_def = find_context(item->u1.str); + if (app_def && app_def->type == PV_MACRO) { + ast_log(LOG_ERROR,"Error: file %s, line %d-%d: application call to %s references an existing macro, but had no & preceding it!\n", + item->filename, item->startline, item->endline, item->u1.str); + errs++; + } + if (strcasecmp(item->u1.str,"GotoIf") == 0 + || strcasecmp(item->u1.str,"GotoIfTime") == 0 + || strcasecmp(item->u1.str,"while") == 0 + || strcasecmp(item->u1.str,"endwhile") == 0 + || strcasecmp(item->u1.str,"random") == 0 + || strcasecmp(item->u1.str,"gosub") == 0 + || strcasecmp(item->u1.str,"return") == 0 + || strcasecmp(item->u1.str,"gosubif") == 0 + || strcasecmp(item->u1.str,"continuewhile") == 0 + || strcasecmp(item->u1.str,"endwhile") == 0 + || strcasecmp(item->u1.str,"execif") == 0 + || strcasecmp(item->u1.str,"execiftime") == 0 + || strcasecmp(item->u1.str,"exitwhile") == 0 + || strcasecmp(item->u1.str,"goto") == 0 + || strcasecmp(item->u1.str,"macro") == 0 + || strcasecmp(item->u1.str,"macroexclusive") == 0 + || strcasecmp(item->u1.str,"macroif") == 0 + || strcasecmp(item->u1.str,"stackpop") == 0 + || strcasecmp(item->u1.str,"execIf") == 0 ) { + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: application call to %s affects flow of control, and needs to be re-written using AEL if, while, goto, etc. keywords instead!\n", + item->filename, item->startline, item->endline, item->u1.str); + warns++; + } + if (strcasecmp(item->u1.str,"macroexit") == 0) { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: I am converting the MacroExit call here to a return statement.\n", + item->filename, item->startline, item->endline); + item->type = PV_RETURN; + free(item->u1.str); + item->u1.str = 0; + } + +#ifdef AAL_ARGCHECK + found = 0; + for (app=apps; app; app=app->next) { + if (strcasecmp(app->name, item->u1.str) == 0) { + found =app; + break; + } + } + if (!found) { + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: application call to %s not listed in applist database!\n", + item->filename, item->startline, item->endline, item->u1.str); + warns++; + } else + check_app_args(item, item->u2.arglist, app); +#endif + break; + + case PV_CASE: + /* fields: item->u1.str == value of case + item->u2.statements == pval list of statements under the case + */ + /* Make sure sequence of statements under case is terminated with goto, return, or break */ + /* find the last statement */ + check_pval(item->u2.statements, apps,in_globals); + break; + + case PV_PATTERN: + /* fields: item->u1.str == value of case + item->u2.statements == pval list of statements under the case + */ + /* Make sure sequence of statements under case is terminated with goto, return, or break */ + /* find the last statement */ + + check_pval(item->u2.statements, apps,in_globals); + break; + + case PV_DEFAULT: + /* fields: + item->u2.statements == pval list of statements under the case + */ + + check_pval(item->u2.statements, apps,in_globals); + break; + + case PV_CATCH: + /* fields: item->u1.str == name of extension to catch + item->u2.statements == pval list of statements in context body + */ + check_pval(item->u2.statements, apps,in_globals); + break; + + case PV_SWITCHES: + /* fields: item->u1.list == pval list of PV_WORD elements, one per entry in the list + */ + check_pval(item->u1.list, apps,in_globals); + break; + + case PV_ESWITCHES: + /* fields: item->u1.list == pval list of PV_WORD elements, one per entry in the list + */ + check_pval(item->u1.list, apps,in_globals); + break; + + case PV_INCLUDES: + /* fields: item->u1.list == pval list of PV_WORD elements, one per entry in the list + */ + check_pval(item->u1.list, apps,in_globals); + check_includes(item); + for (lp=item->u1.list; lp; lp=lp->next){ + char *incl_context = lp->u1.str; + struct pval *that_context = find_context(incl_context); + + if ( lp->u2.arglist ) { + check_timerange(lp->u2.arglist); + check_dow(lp->u2.arglist->next); + check_day(lp->u2.arglist->next->next); + check_month(lp->u2.arglist->next->next->next); + } + + if (that_context) { + find_pval_gotos(that_context->u2.statements,0); + + } + } + break; + + case PV_STATEMENTBLOCK: + /* fields: item->u1.list == pval list of statements in block, one per entry in the list + */ + check_pval(item->u1.list, apps,in_globals); + break; + + case PV_VARDEC: + /* fields: item->u1.str == variable name + item->u2.val == variable value to assign + */ + /* the RHS of a vardec is encapsulated in a $[] expr. Is it legal? */ + if( !in_globals ) { /* don't check stuff inside the globals context; no wrapping in $[ ] there... */ + snprintf(errmsg,sizeof(errmsg), "file %s, line %d, columns %d-%d, variable declaration expr '%s':", item->filename, item->startline, item->startcol, item->endcol, item->u2.val); + ast_expr_register_extra_error_info(errmsg); + ast_expr(item->u2.val, expr_output, sizeof(expr_output),NULL); + ast_expr_clear_extra_error_info(); + if ( strpbrk(item->u2.val,"~!-+<>=*/&^") && !strstr(item->u2.val,"${") ) { + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: expression %s has operators, but no variables. Interesting...\n", + item->filename, item->startline, item->endline, item->u2.val); + warns++; + } + check_expr2_input(item,item->u2.val); + } + break; + + case PV_LOCALVARDEC: + /* fields: item->u1.str == variable name + item->u2.val == variable value to assign + */ + /* the RHS of a vardec is encapsulated in a $[] expr. Is it legal? */ + snprintf(errmsg,sizeof(errmsg), "file %s, line %d, columns %d-%d, variable declaration expr '%s':", item->filename, item->startline, item->startcol, item->endcol, item->u2.val); + ast_expr_register_extra_error_info(errmsg); + ast_expr(item->u2.val, expr_output, sizeof(expr_output),NULL); + ast_expr_clear_extra_error_info(); + if ( strpbrk(item->u2.val,"~!-+<>=*/&^") && !strstr(item->u2.val,"${") ) { + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: expression %s has operators, but no variables. Interesting...\n", + item->filename, item->startline, item->endline, item->u2.val); + warns++; + } + check_expr2_input(item,item->u2.val); + break; + + case PV_GOTO: + /* fields: item->u1.list == pval list of PV_WORD target names, up to 3, in order as given by user. + item->u1.list->u1.str == where the data on a PV_WORD will always be. + */ + /* don't check goto's in abstract contexts */ + if ( in_abstract_context ) + break; + + check_goto(item); + break; + + case PV_LABEL: + /* fields: item->u1.str == label name + */ + if ( strspn(item->u1.str, "0123456789") == strlen(item->u1.str) ) { + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: label '%s' is numeric, this is bad practice!\n", + item->filename, item->startline, item->endline, item->u1.str); + warns++; + } + + check_label(item); + break; + + case PV_FOR: + /* fields: item->u1.for_init == a string containing the initalizer + item->u2.for_test == a string containing the loop test + item->u3.for_inc == a string containing the loop increment + + item->u4.for_statements == a pval list of statements in the for () + */ + snprintf(errmsg,sizeof(errmsg),"file %s, line %d, columns %d-%d, for test expr '%s':", item->filename, item->startline, item->startcol, item->endcol, item->u2.for_test); + ast_expr_register_extra_error_info(errmsg); + + strp = strchr(item->u1.for_init, '='); + if (strp) { + ast_expr(strp+1, expr_output, sizeof(expr_output),NULL); + } + ast_expr(item->u2.for_test, expr_output, sizeof(expr_output),NULL); + strp = strchr(item->u3.for_inc, '='); + if (strp) { + ast_expr(strp+1, expr_output, sizeof(expr_output),NULL); + } + if ( strpbrk(item->u2.for_test,"~!-+<>=*/&^") && !strstr(item->u2.for_test,"${") ) { + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: expression %s has operators, but no variables. Interesting...\n", + item->filename, item->startline, item->endline, item->u2.for_test); + warns++; + } + if ( strpbrk(item->u3.for_inc,"~!-+<>=*/&^") && !strstr(item->u3.for_inc,"${") ) { + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: expression %s has operators, but no variables. Interesting...\n", + item->filename, item->startline, item->endline, item->u3.for_inc); + warns++; + } + check_expr2_input(item,item->u2.for_test); + check_expr2_input(item,item->u3.for_inc); + + ast_expr_clear_extra_error_info(); + check_pval(item->u4.for_statements, apps,in_globals); + break; + + case PV_WHILE: + /* fields: item->u1.str == the while conditional, as supplied by user + + item->u2.statements == a pval list of statements in the while () + */ + snprintf(errmsg,sizeof(errmsg),"file %s, line %d, columns %d-%d, while expr '%s':", item->filename, item->startline, item->startcol, item->endcol, item->u1.str); + ast_expr_register_extra_error_info(errmsg); + ast_expr(item->u1.str, expr_output, sizeof(expr_output),NULL); + ast_expr_clear_extra_error_info(); + if ( strpbrk(item->u1.str,"~!-+<>=*/&^") && !strstr(item->u1.str,"${") ) { + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: expression %s has operators, but no variables. Interesting...\n", + item->filename, item->startline, item->endline, item->u1.str); + warns++; + } + check_expr2_input(item,item->u1.str); + check_pval(item->u2.statements, apps,in_globals); + break; + + case PV_BREAK: + /* fields: none + */ + check_break(item); + break; + + case PV_RETURN: + /* fields: none + */ + break; + + case PV_CONTINUE: + /* fields: none + */ + check_continue(item); + break; + + case PV_RANDOM: + /* fields: item->u1.str == the random number expression, as supplied by user + + item->u2.statements == a pval list of statements in the if () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + */ + snprintf(errmsg,sizeof(errmsg),"file %s, line %d, columns %d-%d, random expr '%s':", item->filename, item->startline, item->startcol, item->endcol, item->u1.str); + ast_expr_register_extra_error_info(errmsg); + ast_expr(item->u1.str, expr_output, sizeof(expr_output),NULL); + ast_expr_clear_extra_error_info(); + if ( strpbrk(item->u1.str,"~!-+<>=*/&^") && !strstr(item->u1.str,"${") ) { + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: random expression '%s' has operators, but no variables. Interesting...\n", + item->filename, item->startline, item->endline, item->u1.str); + warns++; + } + check_expr2_input(item,item->u1.str); + check_pval(item->u2.statements, apps,in_globals); + if (item->u3.else_statements) { + check_pval(item->u3.else_statements, apps,in_globals); + } + break; + + case PV_IFTIME: + /* fields: item->u1.list == the if time values, 4 of them, each in PV_WORD, linked list + + item->u2.statements == a pval list of statements in the if () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + */ + if ( item->u2.arglist ) { + check_timerange(item->u1.list); + check_dow(item->u1.list->next); + check_day(item->u1.list->next->next); + check_month(item->u1.list->next->next->next); + } + + check_pval(item->u2.statements, apps,in_globals); + if (item->u3.else_statements) { + check_pval(item->u3.else_statements, apps,in_globals); + } + break; + + case PV_IF: + /* fields: item->u1.str == the if conditional, as supplied by user + + item->u2.statements == a pval list of statements in the if () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + */ + snprintf(errmsg,sizeof(errmsg),"file %s, line %d, columns %d-%d, if expr '%s':", item->filename, item->startline, item->startcol, item->endcol, item->u1.str); + ast_expr_register_extra_error_info(errmsg); + ast_expr(item->u1.str, expr_output, sizeof(expr_output),NULL); + ast_expr_clear_extra_error_info(); + if ( strpbrk(item->u1.str,"~!-+<>=*/&^") && !strstr(item->u1.str,"${") ) { + ast_log(LOG_WARNING,"Warning: file %s, line %d-%d: expression '%s' has operators, but no variables. Interesting...\n", + item->filename, item->startline, item->endline, item->u1.str); + warns++; + } + check_expr2_input(item,item->u1.str); + check_pval(item->u2.statements, apps,in_globals); + if (item->u3.else_statements) { + check_pval(item->u3.else_statements, apps,in_globals); + } + break; + + case PV_SWITCH: + /* fields: item->u1.str == the switch expression + + item->u2.statements == a pval list of statements in the switch, + (will be case statements, most likely!) + */ + /* we can check the switch expression, see if it matches any of the app variables... + if it does, then, are all the possible cases accounted for? */ + check_switch_expr(item, apps); + check_pval(item->u2.statements, apps,in_globals); + break; + + case PV_EXTENSION: + /* fields: item->u1.str == the extension name, label, whatever it's called + + item->u2.statements == a pval list of statements in the extension + item->u3.hints == a char * hint argument + item->u4.regexten == an int boolean. non-zero says that regexten was specified + */ + current_extension = item ; + + check_pval(item->u2.statements, apps,in_globals); + break; + + case PV_IGNOREPAT: + /* fields: item->u1.str == the ignorepat data + */ + break; + + case PV_GLOBALS: + /* fields: item->u1.statements == pval list of statements, usually vardecs + */ + in_abstract_context = 0; + check_pval(item->u1.statements, apps, 1); + break; + default: + break; + } +} + +void check_pval(pval *item, struct argapp *apps, int in_globals) +{ + pval *i; + + /* checks to do: + 1. Do goto's point to actual labels? + 2. Do macro calls reference a macro? + 3. Does the number of macro args match the definition? + 4. Is a macro call missing its & at the front? + 5. Application calls-- we could check syntax for existing applications, + but I need some some sort of universal description bnf for a general + sort of method for checking arguments, in number, maybe even type, at least. + Don't want to hand code checks for hundreds of applications. + */ + + for (i=item; i; i=i->next) { + check_pval_item(i,apps,in_globals); + } +} + +void ael2_semantic_check(pval *item, int *arg_errs, int *arg_warns, int *arg_notes) +{ + +#ifdef AAL_ARGCHECK + int argapp_errs =0; + char *rfilename; +#endif + struct argapp *apps=0; + + if (!item) + return; /* don't check an empty tree */ +#ifdef AAL_ARGCHECK + rfilename = alloca(10 + strlen(ast_config_AST_VAR_DIR)); + sprintf(rfilename, "%s/applist", ast_config_AST_VAR_DIR); + + apps = argdesc_parse(rfilename, &argapp_errs); /* giveth */ +#endif + current_db = item; + errs = warns = notes = 0; + + check_context_names(); + check_pval(item, apps, 0); + +#ifdef AAL_ARGCHECK + argdesc_destroy(apps); /* taketh away */ +#endif + current_db = 0; + + *arg_errs = errs; + *arg_warns = warns; + *arg_notes = notes; +} + +/* =============================================================================================== */ +/* "CODE" GENERATOR -- Convert the AEL representation to asterisk extension language */ +/* =============================================================================================== */ + +static int control_statement_count = 0; + +struct ael_priority *new_prio(void) +{ + struct ael_priority *x = (struct ael_priority *)calloc(sizeof(struct ael_priority),1); + return x; +} + +struct ael_extension *new_exten(void) +{ + struct ael_extension *x = (struct ael_extension *)calloc(sizeof(struct ael_extension),1); + return x; +} + +void linkprio(struct ael_extension *exten, struct ael_priority *prio) +{ + if (!exten->plist) { + exten->plist = prio; + exten->plist_last = prio; + } else { + exten->plist_last->next = prio; + exten->plist_last = prio; + } + if( !prio->exten ) + prio->exten = exten; /* don't override the switch value */ +} + +void destroy_extensions(struct ael_extension *exten) +{ + struct ael_extension *ne, *nen; + for (ne=exten; ne; ne=nen) { + struct ael_priority *pe, *pen; + + if (ne->name) + free(ne->name); + + /* cidmatch fields are allocated with name, and freed when + the name field is freed. Don't do a free for this field, + unless you LIKE to see a crash! */ + + if (ne->hints) + free(ne->hints); + + for (pe=ne->plist; pe; pe=pen) { + pen = pe->next; + if (pe->app) + free(pe->app); + pe->app = 0; + if (pe->appargs) + free(pe->appargs); + pe->appargs = 0; + pe->origin = 0; + pe->goto_true = 0; + pe->goto_false = 0; + free(pe); + } + nen = ne->next_exten; + ne->next_exten = 0; + ne->plist =0; + ne->plist_last = 0; + ne->next_exten = 0; + ne->loop_break = 0; + ne->loop_continue = 0; + free(ne); + } +} + +static int label_inside_case(pval *label) +{ + pval *p = label; + + while( p && p->type != PV_MACRO && p->type != PV_CONTEXT ) /* early cutout, sort of */ { + if( p->type == PV_CASE || p->type == PV_DEFAULT || p->type == PV_PATTERN ) { + return 1; + } + + p = p->dad; + } + return 0; +} + +static void linkexten(struct ael_extension *exten, struct ael_extension *add) +{ + add->next_exten = exten->next_exten; /* this will reverse the order. Big deal. */ + exten->next_exten = add; +} + +static void remove_spaces_before_equals(char *str) +{ + char *p; + while( str && *str && *str != '=' ) + { + if( *str == ' ' || *str == '\n' || *str == '\r' || *str == '\t' ) + { + p = str; + while( *p ) + { + *p = *(p+1); + p++; + } + } + else + str++; + } +} + +/* =============================================================================================== */ +/* "CODE" GENERATOR -- Convert the AEL representation to asterisk extension language */ +/* =============================================================================================== */ + +static void gen_match_to_pattern(char *pattern, char *result) +{ + /* the result will be a string that will be matched by pattern */ + char *p=pattern, *t=result; + while (*p) { + if (*p == 'x' || *p == 'n' || *p == 'z' || *p == 'X' || *p == 'N' || *p == 'Z') + *t++ = '9'; + else if (*p == '[') { + char *z = p+1; + while (*z != ']') + z++; + if (*(z+1)== ']') + z++; + *t++=*(p+1); /* use the first char in the set */ + p = z; + } else { + *t++ = *p; + } + p++; + } + *t++ = 0; /* cap it off */ +} + +static void gen_prios(struct ael_extension *exten, char *label, pval *statement, struct ael_extension *mother_exten, struct ast_context *this_context ) +{ + pval *p,*p2,*p3; + struct ael_priority *pr; + struct ael_priority *for_init, *for_test, *for_inc, *for_loop, *for_end; + struct ael_priority *while_test, *while_loop, *while_end; + struct ael_priority *switch_test, *switch_end, *fall_thru, *switch_empty; + struct ael_priority *if_test, *if_end, *if_skip, *if_false; +#ifdef OLD_RAND_ACTION + struct ael_priority *rand_test, *rand_end, *rand_skip; +#endif + char buf1[2000]; + char buf2[2000]; + char *strp, *strp2; + char new_label[2000]; + int default_exists; + int local_control_statement_count; + int first; + struct ael_priority *loop_break_save; + struct ael_priority *loop_continue_save; + struct ael_extension *switch_case,*switch_null; + + for (p=statement; p; p=p->next) { + switch (p->type) { + case PV_VARDEC: + pr = new_prio(); + pr->type = AEL_APPCALL; + snprintf(buf1,sizeof(buf1),"%s=$[%s]", p->u1.str, p->u2.val); + pr->app = strdup("Set"); + remove_spaces_before_equals(buf1); + pr->appargs = strdup(buf1); + pr->origin = p; + linkprio(exten, pr); + break; + + case PV_LOCALVARDEC: + pr = new_prio(); + pr->type = AEL_APPCALL; + snprintf(buf1,sizeof(buf1),"LOCAL(%s)=$[%s]", p->u1.str, p->u2.val); + pr->app = strdup("Set"); + remove_spaces_before_equals(buf1); + pr->appargs = strdup(buf1); + pr->origin = p; + linkprio(exten, pr); + break; + + case PV_GOTO: + pr = new_prio(); + pr->type = AEL_APPCALL; + p->u2.goto_target = get_goto_target(p); + if( p->u2.goto_target ) { + p->u3.goto_target_in_case = p->u2.goto_target->u2.label_in_case = label_inside_case(p->u2.goto_target); + } + + if (!p->u1.list->next) /* just one */ { + pr->app = strdup("Goto"); + if (!mother_exten) + pr->appargs = strdup(p->u1.list->u1.str); + else { /* for the case of simple within-extension gotos in case/pattern/default statement blocks: */ + snprintf(buf1,sizeof(buf1),"%s,%s", mother_exten->name, p->u1.list->u1.str); + pr->appargs = strdup(buf1); + } + + } else if (p->u1.list->next && !p->u1.list->next->next) /* two */ { + snprintf(buf1,sizeof(buf1),"%s,%s", p->u1.list->u1.str, p->u1.list->next->u1.str); + pr->app = strdup("Goto"); + pr->appargs = strdup(buf1); + } else if (p->u1.list->next && p->u1.list->next->next) { + snprintf(buf1,sizeof(buf1),"%s,%s,%s", p->u1.list->u1.str, + p->u1.list->next->u1.str, + p->u1.list->next->next->u1.str); + pr->app = strdup("Goto"); + pr->appargs = strdup(buf1); + } + pr->origin = p; + linkprio(exten, pr); + break; + + case PV_LABEL: + pr = new_prio(); + pr->type = AEL_LABEL; + pr->origin = p; + p->u3.compiled_label = exten; + linkprio(exten, pr); + break; + + case PV_FOR: + control_statement_count++; + loop_break_save = exten->loop_break; /* save them, then restore before leaving */ + loop_continue_save = exten->loop_continue; + snprintf(new_label,sizeof(new_label),"for-%s-%d", label, control_statement_count); + for_init = new_prio(); + for_inc = new_prio(); + for_test = new_prio(); + for_loop = new_prio(); + for_end = new_prio(); + for_init->type = AEL_APPCALL; + for_inc->type = AEL_APPCALL; + for_test->type = AEL_FOR_CONTROL; + for_test->goto_false = for_end; + for_loop->type = AEL_CONTROL1; /* simple goto */ + for_end->type = AEL_APPCALL; + for_init->app = strdup("Set"); + + strcpy(buf2,p->u1.for_init); + remove_spaces_before_equals(buf2); + strp = strchr(buf2, '='); + if (strp) { + strp2 = strchr(p->u1.for_init, '='); + *(strp+1) = 0; + strcat(buf2,"$["); + strncat(buf2,strp2+1, sizeof(buf2)-strlen(strp2+1)-2); + strcat(buf2,"]"); + for_init->appargs = strdup(buf2); + } else { + strp2 = p->u1.for_init; + while (*strp2 && isspace(*strp2)) + strp2++; + if (*strp2 == '&') { /* itsa macro call */ + char *strp3 = strp2+1; + while (*strp3 && isspace(*strp3)) + strp3++; + strcpy(buf2, strp3); + strp3 = strchr(buf2,'('); + if (strp3) { + *strp3 = '|'; + } + while ((strp3=strchr(buf2,','))) { + *strp3 = '|'; + } + strp3 = strrchr(buf2, ')'); + if (strp3) + *strp3 = 0; /* remove the closing paren */ + + for_init->appargs = strdup(buf2); + free(for_init->app); + for_init->app = strdup("Macro"); + } else { /* must be a regular app call */ + char *strp3; + strcpy(buf2, strp2); + strp3 = strchr(buf2,'('); + if (strp3) { + *strp3 = 0; + free(for_init->app); + for_init->app = strdup(buf2); + for_init->appargs = strdup(strp3+1); + strp3 = strrchr(for_init->appargs, ')'); + if (strp3) + *strp3 = 0; /* remove the closing paren */ + } + } + } + + strcpy(buf2,p->u3.for_inc); + remove_spaces_before_equals(buf2); + strp = strchr(buf2, '='); + if (strp) { /* there's an = in this part; that means an assignment. set it up */ + strp2 = strchr(p->u3.for_inc, '='); + *(strp+1) = 0; + strcat(buf2,"$["); + strncat(buf2,strp2+1, sizeof(buf2)-strlen(strp2+1)-2); + strcat(buf2,"]"); + for_inc->appargs = strdup(buf2); + for_inc->app = strdup("Set"); + } else { + strp2 = p->u3.for_inc; + while (*strp2 && isspace(*strp2)) + strp2++; + if (*strp2 == '&') { /* itsa macro call */ + char *strp3 = strp2+1; + while (*strp3 && isspace(*strp3)) + strp3++; + strcpy(buf2, strp3); + strp3 = strchr(buf2,'('); + if (strp3) { + *strp3 = ','; + } + strp3 = strrchr(buf2, ')'); + if (strp3) + *strp3 = 0; /* remove the closing paren */ + + for_inc->appargs = strdup(buf2); + + for_inc->app = strdup("Macro"); + } else { /* must be a regular app call */ + char *strp3; + strcpy(buf2, strp2); + strp3 = strchr(buf2,'('); + if (strp3) { + *strp3 = 0; + for_inc->app = strdup(buf2); + for_inc->appargs = strdup(strp3+1); + strp3 = strrchr(for_inc->appargs, ')'); + if (strp3) + *strp3 = 0; /* remove the closing paren */ + } + } + } + snprintf(buf1,sizeof(buf1),"$[%s]",p->u2.for_test); + for_test->app = 0; + for_test->appargs = strdup(buf1); + for_loop->goto_true = for_test; + snprintf(buf1,sizeof(buf1),"Finish for-%s-%d", label, control_statement_count); + for_end->app = strdup("NoOp"); + for_end->appargs = strdup(buf1); + /* link & load! */ + linkprio(exten, for_init); + linkprio(exten, for_test); + + /* now, put the body of the for loop here */ + exten->loop_break = for_end; + exten->loop_continue = for_inc; + + gen_prios(exten, new_label, p->u4.for_statements, mother_exten, this_context); /* this will link in all the statements here */ + + linkprio(exten, for_inc); + linkprio(exten, for_loop); + linkprio(exten, for_end); + + + exten->loop_break = loop_break_save; + exten->loop_continue = loop_continue_save; + for_loop->origin = p; + break; + + case PV_WHILE: + control_statement_count++; + loop_break_save = exten->loop_break; /* save them, then restore before leaving */ + loop_continue_save = exten->loop_continue; + snprintf(new_label,sizeof(new_label),"while-%s-%d", label, control_statement_count); + while_test = new_prio(); + while_loop = new_prio(); + while_end = new_prio(); + while_test->type = AEL_FOR_CONTROL; + while_test->goto_false = while_end; + while_loop->type = AEL_CONTROL1; /* simple goto */ + while_end->type = AEL_APPCALL; + snprintf(buf1,sizeof(buf1),"$[%s]",p->u1.str); + while_test->app = 0; + while_test->appargs = strdup(buf1); + while_loop->goto_true = while_test; + snprintf(buf1,sizeof(buf1),"Finish while-%s-%d", label, control_statement_count); + while_end->app = strdup("NoOp"); + while_end->appargs = strdup(buf1); + + linkprio(exten, while_test); + + /* now, put the body of the for loop here */ + exten->loop_break = while_end; + exten->loop_continue = while_test; + + gen_prios(exten, new_label, p->u2.statements, mother_exten, this_context); /* this will link in all the while body statements here */ + + linkprio(exten, while_loop); + linkprio(exten, while_end); + + + exten->loop_break = loop_break_save; + exten->loop_continue = loop_continue_save; + while_loop->origin = p; + break; + + case PV_SWITCH: + control_statement_count++; + local_control_statement_count = control_statement_count; + loop_break_save = exten->loop_break; /* save them, then restore before leaving */ + loop_continue_save = exten->loop_continue; + snprintf(new_label,sizeof(new_label),"sw-%s-%d", label, control_statement_count); + + switch_test = new_prio(); + switch_end = new_prio(); + switch_test->type = AEL_APPCALL; + switch_end->type = AEL_APPCALL; + snprintf(buf1,sizeof(buf1),"sw-%d-%s,10",control_statement_count, p->u1.str); + switch_test->app = strdup("Goto"); + switch_test->appargs = strdup(buf1); + snprintf(buf1,sizeof(buf1),"Finish switch-%s-%d", label, control_statement_count); + switch_end->app = strdup("NoOp"); + switch_end->appargs = strdup(buf1); + switch_end->origin = p; + switch_end->exten = exten; + + linkprio(exten, switch_test); + linkprio(exten, switch_end); + + exten->loop_break = switch_end; + exten->loop_continue = 0; + default_exists = 0; + + for (p2=p->u2.statements; p2; p2=p2->next) { + /* now, for each case/default put the body of the for loop here */ + if (p2->type == PV_CASE) { + /* ok, generate a extension and link it in */ + switch_case = new_exten(); + switch_case->context = this_context; + switch_case->is_switch = 1; + /* the break/continue locations are inherited from parent */ + switch_case->loop_break = exten->loop_break; + switch_case->loop_continue = exten->loop_continue; + + linkexten(exten,switch_case); + snprintf(buf1,sizeof(buf1),"sw-%d-%s", local_control_statement_count, p2->u1.str); + switch_case->name = strdup(buf1); + snprintf(new_label,sizeof(new_label),"sw-%s-%s-%d", label, p2->u1.str, local_control_statement_count); + + gen_prios(switch_case, new_label, p2->u2.statements, exten, this_context); /* this will link in all the case body statements here */ + + /* here is where we write code to "fall thru" to the next case... if there is one... */ + for (p3=p2->u2.statements; p3; p3=p3->next) { + if (!p3->next) + break; + } + /* p3 now points the last statement... */ + if (!p3 || ( p3->type != PV_GOTO && p3->type != PV_BREAK && p3->type != PV_RETURN) ) { + /* is there a following CASE/PATTERN/DEFAULT? */ + if (p2->next && p2->next->type == PV_CASE) { + fall_thru = new_prio(); + fall_thru->type = AEL_APPCALL; + fall_thru->app = strdup("Goto"); + snprintf(buf1,sizeof(buf1),"sw-%d-%s,10",local_control_statement_count, p2->next->u1.str); + fall_thru->appargs = strdup(buf1); + linkprio(switch_case, fall_thru); + } else if (p2->next && p2->next->type == PV_PATTERN) { + fall_thru = new_prio(); + fall_thru->type = AEL_APPCALL; + fall_thru->app = strdup("Goto"); + gen_match_to_pattern(p2->next->u1.str, buf2); + snprintf(buf1,sizeof(buf1),"sw-%d-%s,10", local_control_statement_count, buf2); + fall_thru->appargs = strdup(buf1); + linkprio(switch_case, fall_thru); + } else if (p2->next && p2->next->type == PV_DEFAULT) { + fall_thru = new_prio(); + fall_thru->type = AEL_APPCALL; + fall_thru->app = strdup("Goto"); + snprintf(buf1,sizeof(buf1),"sw-%d-.,10",local_control_statement_count); + fall_thru->appargs = strdup(buf1); + linkprio(switch_case, fall_thru); + } else if (!p2->next) { + fall_thru = new_prio(); + fall_thru->type = AEL_CONTROL1; + fall_thru->goto_true = switch_end; + fall_thru->app = strdup("Goto"); + linkprio(switch_case, fall_thru); + } + } + if (switch_case->return_needed) { /* returns don't generate a goto eoe (end of extension) any more, just a Return() app call) */ + char buf[2000]; + struct ael_priority *np2 = new_prio(); + np2->type = AEL_APPCALL; + np2->app = strdup("NoOp"); + snprintf(buf,sizeof(buf),"End of Extension %s", switch_case->name); + np2->appargs = strdup(buf); + linkprio(switch_case, np2); + switch_case-> return_target = np2; + } + } else if (p2->type == PV_PATTERN) { + /* ok, generate a extension and link it in */ + switch_case = new_exten(); + switch_case->context = this_context; + switch_case->is_switch = 1; + /* the break/continue locations are inherited from parent */ + switch_case->loop_break = exten->loop_break; + switch_case->loop_continue = exten->loop_continue; + + linkexten(exten,switch_case); + snprintf(buf1,sizeof(buf1),"_sw-%d-%s", local_control_statement_count, p2->u1.str); + switch_case->name = strdup(buf1); + snprintf(new_label,sizeof(new_label),"sw-%s-%s-%d", label, p2->u1.str, local_control_statement_count); + + gen_prios(switch_case, new_label, p2->u2.statements, exten, this_context); /* this will link in all the while body statements here */ + /* here is where we write code to "fall thru" to the next case... if there is one... */ + for (p3=p2->u2.statements; p3; p3=p3->next) { + if (!p3->next) + break; + } + /* p3 now points the last statement... */ + if (!p3 || ( p3->type != PV_GOTO && p3->type != PV_BREAK && p3->type != PV_RETURN)) { + /* is there a following CASE/PATTERN/DEFAULT? */ + if (p2->next && p2->next->type == PV_CASE) { + fall_thru = new_prio(); + fall_thru->type = AEL_APPCALL; + fall_thru->app = strdup("Goto"); + snprintf(buf1,sizeof(buf1),"sw-%d-%s,10",local_control_statement_count, p2->next->u1.str); + fall_thru->appargs = strdup(buf1); + linkprio(switch_case, fall_thru); + } else if (p2->next && p2->next->type == PV_PATTERN) { + fall_thru = new_prio(); + fall_thru->type = AEL_APPCALL; + fall_thru->app = strdup("Goto"); + gen_match_to_pattern(p2->next->u1.str, buf2); + snprintf(buf1,sizeof(buf1),"sw-%d-%s,10",local_control_statement_count, buf2); + fall_thru->appargs = strdup(buf1); + linkprio(switch_case, fall_thru); + } else if (p2->next && p2->next->type == PV_DEFAULT) { + fall_thru = new_prio(); + fall_thru->type = AEL_APPCALL; + fall_thru->app = strdup("Goto"); + snprintf(buf1,sizeof(buf1),"sw-%d-.,10",local_control_statement_count); + fall_thru->appargs = strdup(buf1); + linkprio(switch_case, fall_thru); + } else if (!p2->next) { + fall_thru = new_prio(); + fall_thru->type = AEL_CONTROL1; + fall_thru->goto_true = switch_end; + fall_thru->app = strdup("Goto"); + linkprio(switch_case, fall_thru); + } + } + if (switch_case->return_needed) { /* returns don't generate a goto eoe (end of extension) any more, just a Return() app call) */ + char buf[2000]; + struct ael_priority *np2 = new_prio(); + np2->type = AEL_APPCALL; + np2->app = strdup("NoOp"); + snprintf(buf,sizeof(buf),"End of Extension %s", switch_case->name); + np2->appargs = strdup(buf); + linkprio(switch_case, np2); + switch_case-> return_target = np2; + } + } else if (p2->type == PV_DEFAULT) { + /* ok, generate a extension and link it in */ + switch_case = new_exten(); + switch_case->context = this_context; + switch_case->is_switch = 1; + + /* new: the default case intros a pattern with ., which covers ALMOST everything. + but it doesn't cover a NULL pattern. So, we'll define a null extension to match + that goto's the default extension. */ + + default_exists++; + switch_null = new_exten(); + switch_null->context = this_context; + switch_null->is_switch = 1; + switch_empty = new_prio(); + snprintf(buf1,sizeof(buf1),"sw-%d-.|10",local_control_statement_count); + switch_empty->app = strdup("Goto"); + switch_empty->appargs = strdup(buf1); + linkprio(switch_null, switch_empty); + snprintf(buf1,sizeof(buf1),"sw-%d-", local_control_statement_count); + switch_null->name = strdup(buf1); + switch_null->loop_break = exten->loop_break; + switch_null->loop_continue = exten->loop_continue; + linkexten(exten,switch_null); + + /* the break/continue locations are inherited from parent */ + switch_case->loop_break = exten->loop_break; + switch_case->loop_continue = exten->loop_continue; + linkexten(exten,switch_case); + snprintf(buf1,sizeof(buf1),"_sw-%d-.", local_control_statement_count); + switch_case->name = strdup(buf1); + + snprintf(new_label,sizeof(new_label),"sw-%s-default-%d", label, local_control_statement_count); + + gen_prios(switch_case, new_label, p2->u2.statements, exten, this_context); /* this will link in all the default: body statements here */ + + /* here is where we write code to "fall thru" to the next case... if there is one... */ + for (p3=p2->u2.statements; p3; p3=p3->next) { + if (!p3->next) + break; + } + /* p3 now points the last statement... */ + if (!p3 || (p3->type != PV_GOTO && p3->type != PV_BREAK && p3->type != PV_RETURN)) { + /* is there a following CASE/PATTERN/DEFAULT? */ + if (p2->next && p2->next->type == PV_CASE) { + fall_thru = new_prio(); + fall_thru->type = AEL_APPCALL; + fall_thru->app = strdup("Goto"); + snprintf(buf1,sizeof(buf1),"sw-%d-%s,10",local_control_statement_count, p2->next->u1.str); + fall_thru->appargs = strdup(buf1); + linkprio(switch_case, fall_thru); + } else if (p2->next && p2->next->type == PV_PATTERN) { + fall_thru = new_prio(); + fall_thru->type = AEL_APPCALL; + fall_thru->app = strdup("Goto"); + gen_match_to_pattern(p2->next->u1.str, buf2); + snprintf(buf1,sizeof(buf1),"sw-%d-%s,10",local_control_statement_count, buf2); + fall_thru->appargs = strdup(buf1); + linkprio(switch_case, fall_thru); + } else if (p2->next && p2->next->type == PV_DEFAULT) { + fall_thru = new_prio(); + fall_thru->type = AEL_APPCALL; + fall_thru->app = strdup("Goto"); + snprintf(buf1,sizeof(buf1),"sw-%d-.,10",local_control_statement_count); + fall_thru->appargs = strdup(buf1); + linkprio(switch_case, fall_thru); + } else if (!p2->next) { + fall_thru = new_prio(); + fall_thru->type = AEL_CONTROL1; + fall_thru->goto_true = switch_end; + fall_thru->app = strdup("Goto"); + linkprio(switch_case, fall_thru); + } + } + if (switch_case->return_needed) { /* returns don't generate a goto eoe (end of extension) any more, just a Return() app call) */ + char buf[2000]; + struct ael_priority *np2 = new_prio(); + np2->type = AEL_APPCALL; + np2->app = strdup("NoOp"); + snprintf(buf,sizeof(buf),"End of Extension %s", switch_case->name); + np2->appargs = strdup(buf); + linkprio(switch_case, np2); + switch_case-> return_target = np2; + } + } else { + /* what could it be??? */ + } + } + + exten->loop_break = loop_break_save; + exten->loop_continue = loop_continue_save; + switch_test->origin = p; + switch_end->origin = p; + break; + + case PV_MACRO_CALL: + pr = new_prio(); + pr->type = AEL_APPCALL; + snprintf(buf1,sizeof(buf1),"%s,s,1", p->u1.str); + first = 1; + for (p2 = p->u2.arglist; p2; p2 = p2->next) { + if (first) + { + strcat(buf1,"("); + first = 0; + } + else + strcat(buf1,","); + strcat(buf1,p2->u1.str); + } + if (!first) + strcat(buf1,")"); + + pr->app = strdup("Gosub"); + pr->appargs = strdup(buf1); + pr->origin = p; + linkprio(exten, pr); + break; + + case PV_APPLICATION_CALL: + pr = new_prio(); + pr->type = AEL_APPCALL; + buf1[0] = 0; + for (p2 = p->u2.arglist; p2; p2 = p2->next) { + if (p2 != p->u2.arglist ) + strcat(buf1,","); + strcat(buf1,p2->u1.str); + } + pr->app = strdup(p->u1.str); + pr->appargs = strdup(buf1); + pr->origin = p; + linkprio(exten, pr); + break; + + case PV_BREAK: + pr = new_prio(); + pr->type = AEL_CONTROL1; /* simple goto */ + pr->goto_true = exten->loop_break; + pr->origin = p; + linkprio(exten, pr); + break; + + case PV_RETURN: /* hmmmm */ + pr = new_prio(); + pr->type = AEL_RETURN; /* simple Return */ + /* exten->return_needed++; */ + pr->app = strdup("Return"); + pr->appargs = strdup(""); + pr->origin = p; + linkprio(exten, pr); + break; + + case PV_CONTINUE: + pr = new_prio(); + pr->type = AEL_CONTROL1; /* simple goto */ + pr->goto_true = exten->loop_continue; + pr->origin = p; + linkprio(exten, pr); + break; + + case PV_IFTIME: + control_statement_count++; + snprintf(new_label,sizeof(new_label),"iftime-%s-%d", label, control_statement_count); + + if_test = new_prio(); + if_test->type = AEL_IFTIME_CONTROL; + snprintf(buf1,sizeof(buf1),"%s,%s,%s,%s", + p->u1.list->u1.str, + p->u1.list->next->u1.str, + p->u1.list->next->next->u1.str, + p->u1.list->next->next->next->u1.str); + if_test->app = 0; + if_test->appargs = strdup(buf1); + if_test->origin = p; + + if_end = new_prio(); + if_end->type = AEL_APPCALL; + snprintf(buf1,sizeof(buf1),"Finish iftime-%s-%d", label, control_statement_count); + if_end->app = strdup("NoOp"); + if_end->appargs = strdup(buf1); + + if (p->u3.else_statements) { + if_skip = new_prio(); + if_skip->type = AEL_CONTROL1; /* simple goto */ + if_skip->goto_true = if_end; + if_skip->origin = p; + + } else { + if_skip = 0; + + if_test->goto_false = if_end; + } + + if_false = new_prio(); + if_false->type = AEL_CONTROL1; + if (p->u3.else_statements) { + if_false->goto_true = if_skip; /* +1 */ + } else { + if_false->goto_true = if_end; + } + + /* link & load! */ + linkprio(exten, if_test); + linkprio(exten, if_false); + + /* now, put the body of the if here */ + + gen_prios(exten, new_label, p->u2.statements, mother_exten, this_context); /* this will link in all the statements here */ + + if (p->u3.else_statements) { + linkprio(exten, if_skip); + gen_prios(exten, new_label, p->u3.else_statements, mother_exten, this_context); /* this will link in all the statements here */ + + } + + linkprio(exten, if_end); + + break; + + case PV_RANDOM: + case PV_IF: + control_statement_count++; + snprintf(new_label,sizeof(new_label),"if-%s-%d", label, control_statement_count); + + if_test = new_prio(); + if_end = new_prio(); + if_test->type = AEL_IF_CONTROL; + if_end->type = AEL_APPCALL; + if ( p->type == PV_RANDOM ) + snprintf(buf1,sizeof(buf1),"$[${RAND(0,99)} < (%s)]",p->u1.str); + else + snprintf(buf1,sizeof(buf1),"$[%s]",p->u1.str); + if_test->app = 0; + if_test->appargs = strdup(buf1); + snprintf(buf1,sizeof(buf1),"Finish if-%s-%d", label, control_statement_count); + if_end->app = strdup("NoOp"); + if_end->appargs = strdup(buf1); + if_test->origin = p; + + if (p->u3.else_statements) { + if_skip = new_prio(); + if_skip->type = AEL_CONTROL1; /* simple goto */ + if_skip->goto_true = if_end; + if_test->goto_false = if_skip;; + } else { + if_skip = 0; + if_test->goto_false = if_end;; + } + + /* link & load! */ + linkprio(exten, if_test); + + /* now, put the body of the if here */ + + gen_prios(exten, new_label, p->u2.statements, mother_exten, this_context); /* this will link in all the statements here */ + + if (p->u3.else_statements) { + linkprio(exten, if_skip); + gen_prios(exten, new_label, p->u3.else_statements, mother_exten, this_context); /* this will link in all the statements here */ + + } + + linkprio(exten, if_end); + + break; + + case PV_STATEMENTBLOCK: + gen_prios(exten, label, p->u1.list, mother_exten, this_context ); /* recurse into the block */ + break; + + case PV_CATCH: + control_statement_count++; + /* generate an extension with name of catch, put all catch stats + into this exten! */ + switch_case = new_exten(); + switch_case->context = this_context; + linkexten(exten,switch_case); + switch_case->name = strdup(p->u1.str); + snprintf(new_label,sizeof(new_label),"catch-%s-%d",p->u1.str, control_statement_count); + + gen_prios(switch_case, new_label, p->u2.statements,mother_exten,this_context); /* this will link in all the catch body statements here */ + if (switch_case->return_needed) { /* returns now generate a Return() app call, no longer a goto to the end of the exten */ + char buf[2000]; + struct ael_priority *np2 = new_prio(); + np2->type = AEL_APPCALL; + np2->app = strdup("NoOp"); + snprintf(buf,sizeof(buf),"End of Extension %s", switch_case->name); + np2->appargs = strdup(buf); + linkprio(switch_case, np2); + switch_case-> return_target = np2; + } + + break; + default: + break; + } + } +} + +void set_priorities(struct ael_extension *exten) +{ + int i; + struct ael_priority *pr; + do { + if (exten->is_switch) + i = 10; + else if (exten->regexten) + i=2; + else + i=1; + + for (pr=exten->plist; pr; pr=pr->next) { + pr->priority_num = i; + + if (!pr->origin || (pr->origin && pr->origin->type != PV_LABEL) ) /* Labels don't show up in the dialplan, + but we want them to point to the right + priority, which would be the next line + after the label; */ + i++; + } + + exten = exten->next_exten; + } while ( exten ); +} + +void add_extensions(struct ael_extension *exten) +{ + struct ael_priority *pr; + char *label=0; + char realext[AST_MAX_EXTENSION]; + if (!exten) { + ast_log(LOG_WARNING, "This file is Empty!\n" ); + return; + } + do { + struct ael_priority *last = 0; + + pbx_substitute_variables_helper(NULL, exten->name, realext, sizeof(realext) - 1); + if (exten->hints) { + if (ast_add_extension2(exten->context, 0 /*no replace*/, realext, PRIORITY_HINT, NULL, exten->cidmatch, + exten->hints, NULL, ast_free_ptr, registrar)) { + ast_log(LOG_WARNING, "Unable to add step at priority 'hint' of extension '%s'\n", + exten->name); + } + } + + for (pr=exten->plist; pr; pr=pr->next) { + char app[2000]; + char appargs[2000]; + + /* before we can add the extension, we need to prep the app/appargs; + the CONTROL types need to be done after the priority numbers are calculated. + */ + if (pr->type == AEL_LABEL) /* don't try to put labels in the dialplan! */ { + last = pr; + continue; + } + + if (pr->app) + strcpy(app, pr->app); + else + app[0] = 0; + if (pr->appargs ) + strcpy(appargs, pr->appargs); + else + appargs[0] = 0; + switch( pr->type ) { + case AEL_APPCALL: + /* easy case. Everything is all set up */ + break; + + case AEL_CONTROL1: /* FOR loop, WHILE loop, BREAK, CONTINUE, IF, IFTIME */ + /* simple, unconditional goto. */ + strcpy(app,"Goto"); + if (pr->goto_true->origin && pr->goto_true->origin->type == PV_SWITCH ) { + snprintf(appargs,sizeof(appargs),"%s,%d", pr->goto_true->exten->name, pr->goto_true->priority_num); + } else if (pr->goto_true->origin && pr->goto_true->origin->type == PV_IFTIME && pr->goto_true->origin->u3.else_statements ) { + snprintf(appargs,sizeof(appargs),"%d", pr->goto_true->priority_num+1); + } else + snprintf(appargs,sizeof(appargs),"%d", pr->goto_true->priority_num); + break; + + case AEL_FOR_CONTROL: /* WHILE loop test, FOR loop test */ + strcpy(app,"GotoIf"); + snprintf(appargs,sizeof(appargs),"%s?%d:%d", pr->appargs, pr->priority_num+1, pr->goto_false->priority_num); + break; + + case AEL_IF_CONTROL: + strcpy(app,"GotoIf"); + if (pr->origin->u3.else_statements ) + snprintf(appargs,sizeof(appargs),"%s?%d:%d", pr->appargs, pr->priority_num+1, pr->goto_false->priority_num+1); + else + snprintf(appargs,sizeof(appargs),"%s?%d:%d", pr->appargs, pr->priority_num+1, pr->goto_false->priority_num); + break; + + case AEL_RAND_CONTROL: + strcpy(app,"Random"); + snprintf(appargs,sizeof(appargs),"%s:%d", pr->appargs, pr->goto_true->priority_num+1); + break; + + case AEL_IFTIME_CONTROL: + strcpy(app,"GotoIfTime"); + snprintf(appargs,sizeof(appargs),"%s?%d", pr->appargs, pr->priority_num+2); + break; + + case AEL_RETURN: + strcpy(app,"Return"); + appargs[0] = 0; + break; + + default: + break; + } + if (last && last->type == AEL_LABEL ) { + label = last->origin->u1.str; + } + else + label = 0; + + if (ast_add_extension2(exten->context, 0 /*no replace*/, realext, pr->priority_num, (label?label:NULL), exten->cidmatch, + app, strdup(appargs), ast_free_ptr, registrar)) { + ast_log(LOG_WARNING, "Unable to add step at priority '%d' of extension '%s'\n", pr->priority_num, + exten->name); + } + last = pr; + } + exten = exten->next_exten; + } while ( exten ); +} + +static void attach_exten(struct ael_extension **list, struct ael_extension *newmem) +{ + /* travel to the end of the list... */ + struct ael_extension *lptr; + if( !*list ) { + *list = newmem; + return; + } + lptr = *list; + + while( lptr->next_exten ) { + lptr = lptr->next_exten; + } + /* lptr should now pointing to the last element in the list; it has a null next_exten pointer */ + lptr->next_exten = newmem; +} + +static pval *get_extension_or_contxt(pval *p) +{ + while( p && p->type != PV_EXTENSION && p->type != PV_CONTEXT && p->type != PV_MACRO ) { + + p = p->dad; + } + + return p; +} + +static pval *get_contxt(pval *p) +{ + while( p && p->type != PV_CONTEXT && p->type != PV_MACRO ) { + + p = p->dad; + } + + return p; +} + +static void fix_gotos_in_extensions(struct ael_extension *exten) +{ + struct ael_extension *e; + for(e=exten;e;e=e->next_exten) { + + struct ael_priority *p; + for(p=e->plist;p;p=p->next) { + + if( p->origin && p->origin->type == PV_GOTO && p->origin->u3.goto_target_in_case ) { + + /* fix the extension of the goto target to the actual extension in the post-compiled dialplan */ + + pval *target = p->origin->u2.goto_target; + struct ael_extension *z = target->u3.compiled_label; + pval *pv2 = p->origin; + char buf1[500]; + char *apparg_save = p->appargs; + + p->appargs = 0; + if (!pv2->u1.list->next) /* just one -- it won't hurt to repeat the extension */ { + snprintf(buf1,sizeof(buf1),"%s,%s", z->name, pv2->u1.list->u1.str); + p->appargs = strdup(buf1); + + } else if (pv2->u1.list->next && !pv2->u1.list->next->next) /* two */ { + snprintf(buf1,sizeof(buf1),"%s,%s", z->name, pv2->u1.list->next->u1.str); + p->appargs = strdup(buf1); + } else if (pv2->u1.list->next && pv2->u1.list->next->next) { + snprintf(buf1,sizeof(buf1),"%s,%s,%s", pv2->u1.list->u1.str, + z->name, + pv2->u1.list->next->next->u1.str); + p->appargs = strdup(buf1); + } + else + printf("WHAT? The goto doesn't fall into one of three cases for GOTO????\n"); + + if( apparg_save ) { + free(apparg_save); + } + } + } + } +} + + +void ast_compile_ael2(struct ast_context **local_contexts, struct pval *root) +{ + pval *p,*p2; + struct ast_context *context; + char buf[2000]; + struct ael_extension *exten; + struct ael_extension *exten_list = 0; + + for (p=root; p; p=p->next ) { /* do the globals first, so they'll be there + when we try to eval them */ + switch (p->type) { + case PV_GLOBALS: + /* just VARDEC elements */ + for (p2=p->u1.list; p2; p2=p2->next) { + char buf2[2000]; + snprintf(buf2,sizeof(buf2),"%s=%s", p2->u1.str, p2->u2.val); + pbx_builtin_setvar(NULL, buf2); + } + break; + default: + break; + } + } + + for (p=root; p; p=p->next ) { + pval *lp; + int argc; + + switch (p->type) { + case PV_MACRO: + + context = ast_context_create(local_contexts, p->u1.str, registrar); + + exten = new_exten(); + exten->context = context; + exten->name = strdup("s"); + argc = 1; + for (lp=p->u2.arglist; lp; lp=lp->next) { + /* for each arg, set up a "Set" command */ + struct ael_priority *np2 = new_prio(); + np2->type = AEL_APPCALL; + np2->app = strdup("Set"); + snprintf(buf,sizeof(buf),"LOCAL(%s)=${ARG%d}", lp->u1.str, argc++); + remove_spaces_before_equals(buf); + np2->appargs = strdup(buf); + linkprio(exten, np2); + } + + /* CONTAINS APPCALLS, CATCH, just like extensions... */ + gen_prios(exten, p->u1.str, p->u3.macro_statements, 0, context ); + if (exten->return_needed) { /* most likely, this will go away */ + struct ael_priority *np2 = new_prio(); + np2->type = AEL_APPCALL; + np2->app = strdup("NoOp"); + snprintf(buf,sizeof(buf),"End of Macro %s-%s",p->u1.str, exten->name); + np2->appargs = strdup(buf); + linkprio(exten, np2); + exten-> return_target = np2; + } + + set_priorities(exten); + attach_exten(&exten_list, exten); + break; + + case PV_GLOBALS: + /* already done */ + break; + + case PV_CONTEXT: + context = ast_context_find_or_create(local_contexts, p->u1.str, registrar); + + /* contexts contain: ignorepat, includes, switches, eswitches, extensions, */ + for (p2=p->u2.statements; p2; p2=p2->next) { + pval *p3; + char *s3; + + switch (p2->type) { + case PV_EXTENSION: + exten = new_exten(); + exten->name = strdup(p2->u1.str); + exten->context = context; + + if( (s3=strchr(exten->name, '/') ) != 0 ) + { + *s3 = 0; + exten->cidmatch = s3+1; + } + + if ( p2->u3.hints ) + exten->hints = strdup(p2->u3.hints); + exten->regexten = p2->u4.regexten; + gen_prios(exten, p->u1.str, p2->u2.statements, 0, context ); + if (exten->return_needed) { /* returns don't generate a goto eoe (end of extension) any more, just a Return() app call) */ + struct ael_priority *np2 = new_prio(); + np2->type = AEL_APPCALL; + np2->app = strdup("NoOp"); + snprintf(buf,sizeof(buf),"End of Extension %s", exten->name); + np2->appargs = strdup(buf); + linkprio(exten, np2); + exten-> return_target = np2; + } + /* is the last priority in the extension a label? Then add a trailing no-op */ + if( !exten->plist_last ) + { + ast_log(LOG_WARNING, "Warning: file %s, line %d-%d: Empty Extension!\n", + p2->filename, p2->startline, p2->endline); + } + + if ( exten->plist_last && exten->plist_last->type == AEL_LABEL ) { + struct ael_priority *np2 = new_prio(); + np2->type = AEL_APPCALL; + np2->app = strdup("NoOp"); + snprintf(buf,sizeof(buf),"A NoOp to follow a trailing label %s", exten->plist_last->origin->u1.str); + np2->appargs = strdup(buf); + linkprio(exten, np2); + } + + set_priorities(exten); + attach_exten(&exten_list, exten); + break; + + case PV_IGNOREPAT: + ast_context_add_ignorepat2(context, p2->u1.str, registrar); + break; + + case PV_INCLUDES: + for (p3 = p2->u1.list; p3 ;p3=p3->next) { + if ( p3->u2.arglist ) { + snprintf(buf,sizeof(buf), "%s,%s,%s,%s,%s", + p3->u1.str, + p3->u2.arglist->u1.str, + p3->u2.arglist->next->u1.str, + p3->u2.arglist->next->next->u1.str, + p3->u2.arglist->next->next->next->u1.str); + ast_context_add_include2(context, buf, registrar); + } else + ast_context_add_include2(context, p3->u1.str, registrar); + } + break; + + case PV_SWITCHES: + for (p3 = p2->u1.list; p3 ;p3=p3->next) { + char *c = strchr(p3->u1.str, '/'); + if (c) { + *c = '\0'; + c++; + } else + c = ""; + + ast_context_add_switch2(context, p3->u1.str, c, 0, registrar); + } + break; + + case PV_ESWITCHES: + for (p3 = p2->u1.list; p3 ;p3=p3->next) { + char *c = strchr(p3->u1.str, '/'); + if (c) { + *c = '\0'; + c++; + } else + c = ""; + + ast_context_add_switch2(context, p3->u1.str, c, 1, registrar); + } + break; + default: + break; + } + } + + break; + + default: + /* huh? what? */ + break; + + } + } + /* moved these from being done after a macro or extension were processed, + to after all processing is done, for the sake of fixing gotos to labels inside cases... */ + /* I guess this would be considered 2nd pass of compiler now... */ + fix_gotos_in_extensions(exten_list); /* find and fix extension ref in gotos to labels that are in case statements */ + add_extensions(exten_list); /* actually makes calls to create priorities in ast_contexts -- feeds dialplan to asterisk */ + destroy_extensions(exten_list); /* all that remains is an empty husk, discard of it as is proper */ + +} + + +/* DESTROY the PVAL tree ============================================================================ */ + + + +void destroy_pval_item(pval *item) +{ + if (item == NULL) { + ast_log(LOG_WARNING, "null item\n"); + return; + } + + if (item->filename) + free(item->filename); + + switch (item->type) { + case PV_WORD: + /* fields: item->u1.str == string associated with this (word). */ + if (item->u1.str ) + free(item->u1.str); + if ( item->u2.arglist ) + destroy_pval(item->u2.arglist); + break; + + case PV_MACRO: + /* fields: item->u1.str == name of macro + item->u2.arglist == pval list of PV_WORD arguments of macro, as given by user + item->u2.arglist->u1.str == argument + item->u2.arglist->next == next arg + + item->u3.macro_statements == pval list of statements in macro body. + */ + destroy_pval(item->u2.arglist); + if (item->u1.str ) + free(item->u1.str); + destroy_pval(item->u3.macro_statements); + break; + + case PV_CONTEXT: + /* fields: item->u1.str == name of context + item->u2.statements == pval list of statements in context body + item->u3.abstract == int 1 if an abstract keyword were present + */ + if (item->u1.str) + free(item->u1.str); + destroy_pval(item->u2.statements); + break; + + case PV_MACRO_CALL: + /* fields: item->u1.str == name of macro to call + item->u2.arglist == pval list of PV_WORD arguments of macro call, as given by user + item->u2.arglist->u1.str == argument + item->u2.arglist->next == next arg + */ + if (item->u1.str) + free(item->u1.str); + destroy_pval(item->u2.arglist); + break; + + case PV_APPLICATION_CALL: + /* fields: item->u1.str == name of application to call + item->u2.arglist == pval list of PV_WORD arguments of macro call, as given by user + item->u2.arglist->u1.str == argument + item->u2.arglist->next == next arg + */ + if (item->u1.str) + free(item->u1.str); + destroy_pval(item->u2.arglist); + break; + + case PV_CASE: + /* fields: item->u1.str == value of case + item->u2.statements == pval list of statements under the case + */ + if (item->u1.str) + free(item->u1.str); + destroy_pval(item->u2.statements); + break; + + case PV_PATTERN: + /* fields: item->u1.str == value of case + item->u2.statements == pval list of statements under the case + */ + if (item->u1.str) + free(item->u1.str); + destroy_pval(item->u2.statements); + break; + + case PV_DEFAULT: + /* fields: + item->u2.statements == pval list of statements under the case + */ + destroy_pval(item->u2.statements); + break; + + case PV_CATCH: + /* fields: item->u1.str == name of extension to catch + item->u2.statements == pval list of statements in context body + */ + if (item->u1.str) + free(item->u1.str); + destroy_pval(item->u2.statements); + break; + + case PV_SWITCHES: + /* fields: item->u1.list == pval list of PV_WORD elements, one per entry in the list + */ + destroy_pval(item->u1.list); + break; + + case PV_ESWITCHES: + /* fields: item->u1.list == pval list of PV_WORD elements, one per entry in the list + */ + destroy_pval(item->u1.list); + break; + + case PV_INCLUDES: + /* fields: item->u1.list == pval list of PV_WORD elements, one per entry in the list + item->u2.arglist == pval list of 4 PV_WORD elements for time values + */ + destroy_pval(item->u1.list); + break; + + case PV_STATEMENTBLOCK: + /* fields: item->u1.list == pval list of statements in block, one per entry in the list + */ + destroy_pval(item->u1.list); + break; + + case PV_LOCALVARDEC: + case PV_VARDEC: + /* fields: item->u1.str == variable name + item->u2.val == variable value to assign + */ + if (item->u1.str) + free(item->u1.str); + if (item->u2.val) + free(item->u2.val); + break; + + case PV_GOTO: + /* fields: item->u1.list == pval list of PV_WORD target names, up to 3, in order as given by user. + item->u1.list->u1.str == where the data on a PV_WORD will always be. + */ + + destroy_pval(item->u1.list); + break; + + case PV_LABEL: + /* fields: item->u1.str == label name + */ + if (item->u1.str) + free(item->u1.str); + break; + + case PV_FOR: + /* fields: item->u1.for_init == a string containing the initalizer + item->u2.for_test == a string containing the loop test + item->u3.for_inc == a string containing the loop increment + + item->u4.for_statements == a pval list of statements in the for () + */ + if (item->u1.for_init) + free(item->u1.for_init); + if (item->u2.for_test) + free(item->u2.for_test); + if (item->u3.for_inc) + free(item->u3.for_inc); + destroy_pval(item->u4.for_statements); + break; + + case PV_WHILE: + /* fields: item->u1.str == the while conditional, as supplied by user + + item->u2.statements == a pval list of statements in the while () + */ + if (item->u1.str) + free(item->u1.str); + destroy_pval(item->u2.statements); + break; + + case PV_BREAK: + /* fields: none + */ + break; + + case PV_RETURN: + /* fields: none + */ + break; + + case PV_CONTINUE: + /* fields: none + */ + break; + + case PV_IFTIME: + /* fields: item->u1.list == the 4 time values, in PV_WORD structs, linked list + + item->u2.statements == a pval list of statements in the if () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + */ + destroy_pval(item->u1.list); + destroy_pval(item->u2.statements); + if (item->u3.else_statements) { + destroy_pval(item->u3.else_statements); + } + break; + + case PV_RANDOM: + /* fields: item->u1.str == the random percentage, as supplied by user + + item->u2.statements == a pval list of statements in the true part () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + fall thru to If */ + case PV_IF: + /* fields: item->u1.str == the if conditional, as supplied by user + + item->u2.statements == a pval list of statements in the if () + item->u3.else_statements == a pval list of statements in the else + (could be zero) + */ + if (item->u1.str) + free(item->u1.str); + destroy_pval(item->u2.statements); + if (item->u3.else_statements) { + destroy_pval(item->u3.else_statements); + } + break; + + case PV_SWITCH: + /* fields: item->u1.str == the switch expression + + item->u2.statements == a pval list of statements in the switch, + (will be case statements, most likely!) + */ + if (item->u1.str) + free(item->u1.str); + destroy_pval(item->u2.statements); + break; + + case PV_EXTENSION: + /* fields: item->u1.str == the extension name, label, whatever it's called + + item->u2.statements == a pval list of statements in the extension + item->u3.hints == a char * hint argument + item->u4.regexten == an int boolean. non-zero says that regexten was specified + */ + if (item->u1.str) + free(item->u1.str); + if (item->u3.hints) + free(item->u3.hints); + destroy_pval(item->u2.statements); + break; + + case PV_IGNOREPAT: + /* fields: item->u1.str == the ignorepat data + */ + if (item->u1.str) + free(item->u1.str); + break; + + case PV_GLOBALS: + /* fields: item->u1.statements == pval list of statements, usually vardecs + */ + destroy_pval(item->u1.statements); + break; + } + free(item); +} + +void destroy_pval(pval *item) +{ + pval *i,*nxt; + + for (i=item; i; i=nxt) { + nxt = i->next; + + destroy_pval_item(i); + } +} + +#ifdef AAL_ARGCHECK +static char *ael_funclist[] = +{ + "AGENT", + "ARRAY", + "BASE64_DECODE", + "BASE64_ENCODE", + "CALLERID", + "CDR", + "CHANNEL", + "CHECKSIPDOMAIN", + "CHECK_MD5", + "CURL", + "CUT", + "DB", + "DB_EXISTS", + "DUNDILOOKUP", + "ENUMLOOKUP", + "ENV", + "EVAL", + "EXISTS", + "FIELDQTY", + "FILTER", + "GROUP", + "GROUP_COUNT", + "GROUP_LIST", + "GROUP_MATCH_COUNT", + "IAXPEER", + "IF", + "IFTIME", + "ISNULL", + "KEYPADHASH", + "LANGUAGE", + "LEN", + "MATH", + "MD5", + "MUSICCLASS", + "QUEUEAGENTCOUNT", + "QUEUE_MEMBER_COUNT", + "QUEUE_MEMBER_LIST", + "QUOTE", + "RAND", + "REGEX", + "SET", + "SHA1", + "SIPCHANINFO", + "SIPPEER", + "SIP_HEADER", + "SORT", + "STAT", + "STRFTIME", + "STRPTIME", + "TIMEOUT", + "TXTCIDNAME", + "URIDECODE", + "URIENCODE", + "VMCOUNT" +}; + + +int ael_is_funcname(char *name) +{ + int s,t; + t = sizeof(ael_funclist)/sizeof(char*); + s = 0; + while ((s < t) && strcasecmp(name, ael_funclist[s])) + s++; + if ( s < t ) + return 1; + else + return 0; +} +#endif + + +/* PVAL PI */ + +/* ----------------- implementation ------------------- */ + + +int pvalCheckType( pval *p, char *funcname, pvaltype type ) +{ + if (p->type != type) + { + ast_log(LOG_ERROR, "Func: %s the pval passed is not appropriate for this function!\n", funcname); + return 0; + } + return 1; +} + + +pval *pvalCreateNode( pvaltype type ) +{ + pval *p = calloc(1,sizeof(pval)); /* why, oh why, don't I use ast_calloc? Way, way, way too messy if I do! */ + p->type = type; /* remember, this can be used externally or internally to asterisk */ + return p; +} + +pvaltype pvalObjectGetType( pval *p ) +{ + return p->type; +} + + +void pvalWordSetString( pval *p, char *str) +{ + if (!pvalCheckType(p, "pvalWordSetString", PV_WORD)) + return; + p->u1.str = str; +} + +char *pvalWordGetString( pval *p ) +{ + if (!pvalCheckType(p, "pvalWordGetString", PV_WORD)) + return 0; + return p->u1.str; +} + + +void pvalMacroSetName( pval *p, char *name) +{ + if (!pvalCheckType(p, "pvalMacroSetName", PV_MACRO)) + return; + p->u1.str = name; +} + +char *pvalMacroGetName( pval *p ) +{ + if (!pvalCheckType(p, "pvalMacroGetName", PV_MACRO)) + return 0; + return p->u1.str; +} + +void pvalMacroSetArglist( pval *p, pval *arglist ) +{ + if (!pvalCheckType(p, "pvalMacroSetArglist", PV_MACRO)) + return; + p->u2.arglist = arglist; +} + +void pvalMacroAddArg( pval *p, pval *arg ) /* single arg only! */ +{ + if (!pvalCheckType(p, "pvalMacroAddArg", PV_MACRO)) + return; + if (!p->u2.arglist) + p->u2.arglist = arg; + else + linku1(p->u2.arglist, arg); + +} + +pval *pvalMacroWalkArgs( pval *p, pval **arg ) +{ + if (!pvalCheckType(p, "pvalMacroWalkArgs", PV_MACRO)) + return 0; + if (!(*arg)) + *arg = p->u2.arglist; + else { + *arg = (*arg)->next; + } + return *arg; +} + +void pvalMacroAddStatement( pval *p, pval *statement ) +{ + if (!pvalCheckType(p, "pvalMacroAddStatement", PV_MACRO)) + return; + if (!p->u3.macro_statements) + p->u3.macro_statements = statement; + else + linku1(p->u3.macro_statements, statement); + + +} + +pval *pvalMacroWalkStatements( pval *p, pval **next_statement ) +{ + if (!pvalCheckType(p, "pvalMacroWalkStatements", PV_MACRO)) + return 0; + if (!(*next_statement)) + *next_statement = p->u3.macro_statements; + else { + *next_statement = (*next_statement)->next; + } + return *next_statement; +} + + + +void pvalContextSetName( pval *p, char *name) +{ + if (!pvalCheckType(p, "pvalContextSetName", PV_CONTEXT)) + return; + p->u1.str = name; +} + +char *pvalContextGetName( pval *p ) +{ + if (!pvalCheckType(p, "pvalContextGetName", PV_CONTEXT)) + return 0; + return p->u1.str; +} + +void pvalContextSetAbstract( pval *p ) +{ + if (!pvalCheckType(p, "pvalContextSetAbstract", PV_CONTEXT)) + return; + p->u3.abstract = 1; +} + +void pvalContextUnsetAbstract( pval *p ) +{ + if (!pvalCheckType(p, "pvalContextUnsetAbstract", PV_CONTEXT)) + return; + p->u3.abstract = 0; +} + +int pvalContextGetAbstract( pval *p ) +{ + if (!pvalCheckType(p, "pvalContextGetAbstract", PV_CONTEXT)) + return 0; + return p->u3.abstract; +} + + + +void pvalContextAddStatement( pval *p, pval *statement) /* this includes SWITCHES, INCLUDES, IGNOREPAT, etc */ +{ + if (!pvalCheckType(p, "pvalContextAddStatement", PV_CONTEXT)) + return; + if (!p->u2.statements) + p->u2.statements = statement; + else + linku1(p->u2.statements, statement); +} + +pval *pvalContextWalkStatements( pval *p, pval **statements ) +{ + if (!pvalCheckType(p, "pvalContextWalkStatements", PV_CONTEXT)) + return 0; + if (!(*statements)) + *statements = p->u2.statements; + else { + *statements = (*statements)->next; + } + return *statements; +} + + +void pvalMacroCallSetMacroName( pval *p, char *name ) +{ + if (!pvalCheckType(p, "pvalMacroCallSetMacroName", PV_MACRO_CALL)) + return; + p->u1.str = name; +} + +char* pvalMacroCallGetMacroName( pval *p ) +{ + if (!pvalCheckType(p, "pvalMacroCallGetMacroName", PV_MACRO_CALL)) + return 0; + return p->u1.str; +} + +void pvalMacroCallSetArglist( pval *p, pval *arglist ) +{ + if (!pvalCheckType(p, "pvalMacroCallSetArglist", PV_MACRO_CALL)) + return; + p->u2.arglist = arglist; +} + +void pvalMacroCallAddArg( pval *p, pval *arg ) +{ + if (!pvalCheckType(p, "pvalMacroCallGetAddArg", PV_MACRO_CALL)) + return; + if (!p->u2.arglist) + p->u2.arglist = arg; + else + linku1(p->u2.arglist, arg); +} + +pval *pvalMacroCallWalkArgs( pval *p, pval **args ) +{ + if (!pvalCheckType(p, "pvalMacroCallWalkArgs", PV_MACRO_CALL)) + return 0; + if (!(*args)) + *args = p->u2.arglist; + else { + *args = (*args)->next; + } + return *args; +} + + +void pvalAppCallSetAppName( pval *p, char *name ) +{ + if (!pvalCheckType(p, "pvalAppCallSetAppName", PV_APPLICATION_CALL)) + return; + p->u1.str = name; +} + +char* pvalAppCallGetAppName( pval *p ) +{ + if (!pvalCheckType(p, "pvalAppCallGetAppName", PV_APPLICATION_CALL)) + return 0; + return p->u1.str; +} + +void pvalAppCallSetArglist( pval *p, pval *arglist ) +{ + if (!pvalCheckType(p, "pvalAppCallSetArglist", PV_APPLICATION_CALL)) + return; + p->u2.arglist = arglist; +} + +void pvalAppCallAddArg( pval *p, pval *arg ) +{ + if (!pvalCheckType(p, "pvalAppCallAddArg", PV_APPLICATION_CALL)) + return; + if (!p->u2.arglist) + p->u2.arglist = arg; + else + linku1(p->u2.arglist, arg); +} + +pval *pvalAppCallWalkArgs( pval *p, pval **args ) +{ + if (!pvalCheckType(p, "pvalAppCallWalkArgs", PV_APPLICATION_CALL)) + return 0; + if (!(*args)) + *args = p->u2.arglist; + else { + *args = (*args)->next; + } + return *args; +} + + +void pvalCasePatSetVal( pval *p, char *val ) +{ + if (!pvalCheckType(p, "pvalAppCallWalkArgs", PV_APPLICATION_CALL)) + return; + p->u1.str = val; +} + +char* pvalCasePatGetVal( pval *p ) +{ + return p->u1.str; +} + +void pvalCasePatDefAddStatement( pval *p, pval *statement ) +{ + if (!p->u2.arglist) + p->u2.statements = statement; + else + linku1(p->u2.statements, statement); +} + +pval *pvalCasePatDefWalkStatements( pval *p, pval **statement ) +{ + if (!(*statement)) + *statement = p->u2.statements; + else { + *statement = (*statement)->next; + } + return *statement; +} + + +void pvalCatchSetExtName( pval *p, char *name ) +{ + if (!pvalCheckType(p, "pvalCatchSetExtName", PV_CATCH)) + return; + p->u1.str = name; +} + +char* pvalCatchGetExtName( pval *p ) +{ + if (!pvalCheckType(p, "pvalCatchGetExtName", PV_CATCH)) + return 0; + return p->u1.str; +} + +void pvalCatchSetStatement( pval *p, pval *statement ) +{ + if (!pvalCheckType(p, "pvalCatchSetStatement", PV_CATCH)) + return; + p->u2.statements = statement; +} + +pval *pvalCatchGetStatement( pval *p ) +{ + if (!pvalCheckType(p, "pvalCatchGetStatement", PV_CATCH)) + return 0; + return p->u2.statements; +} + + +void pvalSwitchesAddSwitch( pval *p, char *name ) +{ + pval *s; + if (!pvalCheckType(p, "pvalSwitchesAddSwitch", PV_SWITCHES)) + return; + s = pvalCreateNode(PV_WORD); + s->u1.str = name; + p->u1.list = linku1(p->u1.list, s); +} + +char* pvalSwitchesWalkNames( pval *p, pval **next_item ) +{ + if (!pvalCheckType(p, "pvalSwitchesWalkNames", PV_SWITCHES)) + return 0; + if (!(*next_item)) + *next_item = p->u1.list; + else { + *next_item = (*next_item)->next; + } + return (*next_item)->u1.str; +} + +void pvalESwitchesAddSwitch( pval *p, char *name ) +{ + pval *s; + if (!pvalCheckType(p, "pvalESwitchesAddSwitch", PV_ESWITCHES)) + return; + s = pvalCreateNode(PV_WORD); + s->u1.str = name; + p->u1.list = linku1(p->u1.list, s); +} + +char* pvalESwitchesWalkNames( pval *p, pval **next_item ) +{ + if (!pvalCheckType(p, "pvalESwitchesWalkNames", PV_ESWITCHES)) + return 0; + if (!(*next_item)) + *next_item = p->u1.list; + else { + *next_item = (*next_item)->next; + } + return (*next_item)->u1.str; +} + + +void pvalIncludesAddInclude( pval *p, const char *include ) +{ + pval *s; + if (!pvalCheckType(p, "pvalIncludesAddSwitch", PV_INCLUDES)) + return; + s = pvalCreateNode(PV_WORD); + s->u1.str = (char *)include; + p->u1.list = linku1(p->u1.list, s); +} + /* an include is a WORD with string set to path */ + +void pvalIncludesAddIncludeWithTimeConstraints( pval *p, const char *include, char *hour_range, char *dom_range, char *dow_range, char *month_range ) +{ + pval *hr = pvalCreateNode(PV_WORD); + pval *dom = pvalCreateNode(PV_WORD); + pval *dow = pvalCreateNode(PV_WORD); + pval *mon = pvalCreateNode(PV_WORD); + pval *s = pvalCreateNode(PV_WORD); + + if (!pvalCheckType(p, "pvalIncludeAddIncludeWithTimeConstraints", PV_INCLUDES)) + return; + + s->u1.str = (char *)include; + p->u1.list = linku1(p->u1.list, s); + + hr->u1.str = hour_range; + dom->u1.str = dom_range; + dow->u1.str = dow_range; + mon->u1.str = month_range; + + s->u2.arglist = hr; + + hr->next = dom; + dom->next = dow; + dow->next = mon; + mon->next = 0; +} + /* is this right??? come back and correct it */ /*the ptr is to the WORD */ +void pvalIncludeGetTimeConstraints( pval *p, char **hour_range, char **dom_range, char **dow_range, char **month_range ) +{ + if (!pvalCheckType(p, "pvalIncludeGetTimeConstraints", PV_WORD)) + return; + if (p->u2.arglist) { + *hour_range = p->u2.arglist->u1.str; + *dom_range = p->u2.arglist->next->u1.str; + *dow_range = p->u2.arglist->next->next->u1.str; + *month_range = p->u2.arglist->next->next->next->u1.str; + } else { + *hour_range = 0; + *dom_range = 0; + *dow_range = 0; + *month_range = 0; + } +} + /* is this right??? come back and correct it */ /*the ptr is to the WORD */ +char* pvalIncludesWalk( pval *p, pval **next_item ) +{ + if (!pvalCheckType(p, "pvalIncludesWalk", PV_INCLUDES)) + return 0; + if (!(*next_item)) + *next_item = p->u1.list; + else { + *next_item = (*next_item)->next; + } + return (*next_item)->u1.str; +} + + +void pvalStatementBlockAddStatement( pval *p, pval *statement) +{ + if (!pvalCheckType(p, "pvalStatementBlockAddStatement", PV_STATEMENTBLOCK)) + return; + p->u1.list = linku1(p->u1.list, statement); +} + +pval *pvalStatementBlockWalkStatements( pval *p, pval **next_statement) +{ + if (!pvalCheckType(p, "pvalStatementBlockWalkStatements", PV_STATEMENTBLOCK)) + return 0; + if (!(*next_statement)) + *next_statement = p->u1.list; + else { + *next_statement = (*next_statement)->next; + } + return *next_statement; +} + +void pvalVarDecSetVarname( pval *p, char *name ) +{ + if (!pvalCheckType(p, "pvalVarDecSetVarname", PV_VARDEC)) + return; + p->u1.str = name; +} + +void pvalVarDecSetValue( pval *p, char *value ) +{ + if (!pvalCheckType(p, "pvalVarDecSetValue", PV_VARDEC)) + return; + p->u2.val = value; +} + +char* pvalVarDecGetVarname( pval *p ) +{ + if (!pvalCheckType(p, "pvalVarDecGetVarname", PV_VARDEC)) + return 0; + return p->u1.str; +} + +char* pvalVarDecGetValue( pval *p ) +{ + if (!pvalCheckType(p, "pvalVarDecGetValue", PV_VARDEC)) + return 0; + return p->u2.val; +} + +void pvalGotoSetTarget( pval *p, char *context, char *exten, char *label ) +{ + pval *con, *ext, *pri; + + if (!pvalCheckType(p, "pvalGotoSetTarget", PV_GOTO)) + return; + if (context && strlen(context)) { + con = pvalCreateNode(PV_WORD); + ext = pvalCreateNode(PV_WORD); + pri = pvalCreateNode(PV_WORD); + + con->u1.str = context; + ext->u1.str = exten; + pri->u1.str = label; + + con->next = ext; + ext->next = pri; + p->u1.list = con; + } else if (exten && strlen(exten)) { + ext = pvalCreateNode(PV_WORD); + pri = pvalCreateNode(PV_WORD); + + ext->u1.str = exten; + pri->u1.str = label; + + ext->next = pri; + p->u1.list = ext; + } else { + pri = pvalCreateNode(PV_WORD); + + pri->u1.str = label; + + p->u1.list = pri; + } +} + +void pvalGotoGetTarget( pval *p, char **context, char **exten, char **label ) +{ + if (!pvalCheckType(p, "pvalGotoGetTarget", PV_GOTO)) + return; + if (p->u1.list && p->u1.list->next && p->u1.list->next->next) { + *context = p->u1.list->u1.str; + *exten = p->u1.list->next->u1.str; + *label = p->u1.list->next->next->u1.str; + + } else if (p->u1.list && p->u1.list->next ) { + *exten = p->u1.list->u1.str; + *label = p->u1.list->next->u1.str; + *context = 0; + + } else if (p->u1.list) { + *label = p->u1.list->u1.str; + *context = 0; + *exten = 0; + + } else { + *context = 0; + *exten = 0; + *label = 0; + } +} + + +void pvalLabelSetName( pval *p, char *name ) +{ + if (!pvalCheckType(p, "pvalLabelSetName", PV_LABEL)) + return; + p->u1.str = name; +} + +char* pvalLabelGetName( pval *p ) +{ + if (!pvalCheckType(p, "pvalLabelGetName", PV_LABEL)) + return 0; + return p->u1.str; +} + + +void pvalForSetInit( pval *p, char *init ) +{ + if (!pvalCheckType(p, "pvalForSetInit", PV_FOR)) + return; + p->u1.for_init = init; +} + +void pvalForSetTest( pval *p, char *test ) +{ + if (!pvalCheckType(p, "pvalForSetTest", PV_FOR)) + return; + p->u2.for_test = test; +} + +void pvalForSetInc( pval *p, char *inc ) +{ + if (!pvalCheckType(p, "pvalForSetInc", PV_FOR)) + return; + p->u3.for_inc = inc; +} + +void pvalForSetStatement( pval *p, pval *statement ) +{ + if (!pvalCheckType(p, "pvalForSetStatement", PV_FOR)) + return; + p->u4.for_statements = statement; +} + +char* pvalForGetInit( pval *p ) +{ + if (!pvalCheckType(p, "pvalForGetInit", PV_FOR)) + return 0; + return p->u1.for_init; +} + +char* pvalForGetTest( pval *p ) +{ + if (!pvalCheckType(p, "pvalForGetTest", PV_FOR)) + return 0; + return p->u2.for_test; +} + +char* pvalForGetInc( pval *p ) +{ + if (!pvalCheckType(p, "pvalForGetInc", PV_FOR)) + return 0; + return p->u3.for_inc; +} + +pval* pvalForGetStatement( pval *p ) +{ + if (!pvalCheckType(p, "pvalForGetStatement", PV_FOR)) + return 0; + return p->u4.for_statements; +} + + + +void pvalIfSetCondition( pval *p, char *expr ) +{ + if (!pvalCheckType(p, "pvalIfSetCondition", PV_IF)) + return; + p->u1.str = expr; +} + +char* pvalIfGetCondition( pval *p ) +{ + if (!pvalCheckType(p, "pvalIfGetCondition", PV_IFTIME)) + return 0; + return p->u1.str; +} + +void pvalIfTimeSetCondition( pval *p, char *hour_range, char *dow_range, char *dom_range, char *mon_range ) /* time range format: 24-hour format begin-end|dow range|dom range|month range */ +{ + pval *hr = pvalCreateNode(PV_WORD); + pval *dow = pvalCreateNode(PV_WORD); + pval *dom = pvalCreateNode(PV_WORD); + pval *mon = pvalCreateNode(PV_WORD); + if (!pvalCheckType(p, "pvalIfTimeSetCondition", PV_IFTIME)) + return; + pvalWordSetString(hr, hour_range); + pvalWordSetString(dow, dow_range); + pvalWordSetString(dom, dom_range); + pvalWordSetString(mon, mon_range); + dom->next = mon; + dow->next = dom; + hr->next = dow; + p->u1.list = hr; +} + + /* is this right??? come back and correct it */ +void pvalIfTimeGetCondition( pval *p, char **hour_range, char **dow_range, char **dom_range, char **month_range ) +{ + if (!pvalCheckType(p, "pvalIfTimeGetCondition", PV_IFTIME)) + return; + *hour_range = p->u1.list->u1.str; + *dow_range = p->u1.list->next->u1.str; + *dom_range = p->u1.list->next->next->u1.str; + *month_range = p->u1.list->next->next->next->u1.str; +} + +void pvalRandomSetCondition( pval *p, char *percent ) +{ + if (!pvalCheckType(p, "pvalRandomSetCondition", PV_RANDOM)) + return; + p->u1.str = percent; +} + +char* pvalRandomGetCondition( pval *p ) +{ + if (!pvalCheckType(p, "pvalRandomGetCondition", PV_RANDOM)) + return 0; + return p->u1.str; +} + +void pvalConditionalSetThenStatement( pval *p, pval *statement ) +{ + p->u2.statements = statement; +} + +void pvalConditionalSetElseStatement( pval *p, pval *statement ) +{ + p->u3.else_statements = statement; +} + +pval* pvalConditionalGetThenStatement( pval *p ) +{ + return p->u2.statements; +} + +pval* pvalConditionalGetElseStatement( pval *p ) +{ + return p->u3.else_statements; +} + +void pvalSwitchSetTestexpr( pval *p, char *expr ) +{ + if (!pvalCheckType(p, "pvalSwitchSetTestexpr", PV_SWITCH)) + return; + p->u1.str = expr; +} + +char* pvalSwitchGetTestexpr( pval *p ) +{ + if (!pvalCheckType(p, "pvalSwitchGetTestexpr", PV_SWITCH)) + return 0; + return p->u1.str; +} + +void pvalSwitchAddCase( pval *p, pval *Case ) +{ + if (!pvalCheckType(p, "pvalSwitchAddCase", PV_SWITCH)) + return; + if (!pvalCheckType(Case, "pvalSwitchAddCase", PV_CASE)) + return; + if (!p->u2.statements) + p->u2.statements = Case; + else + linku1(p->u2.statements, Case); +} + +pval* pvalSwitchWalkCases( pval *p, pval **next_case ) +{ + if (!pvalCheckType(p, "pvalSwitchWalkCases", PV_SWITCH)) + return 0; + if (!(*next_case)) + *next_case = p->u2.statements; + else { + *next_case = (*next_case)->next; + } + return *next_case; +} + + +void pvalExtenSetName( pval *p, char *name ) +{ + if (!pvalCheckType(p, "pvalExtenSetName", PV_EXTENSION)) + return; + p->u1.str = name; +} + +char* pvalExtenGetName( pval *p ) +{ + if (!pvalCheckType(p, "pvalExtenGetName", PV_EXTENSION)) + return 0; + return p->u1.str; +} + +void pvalExtenSetRegexten( pval *p ) +{ + if (!pvalCheckType(p, "pvalExtenSetRegexten", PV_EXTENSION)) + return; + p->u4.regexten = 1; +} + +void pvalExtenUnSetRegexten( pval *p ) +{ + if (!pvalCheckType(p, "pvalExtenUnSetRegexten", PV_EXTENSION)) + return; + p->u4.regexten = 0; +} + +int pvalExtenGetRegexten( pval *p ) +{ + if (!pvalCheckType(p, "pvalExtenGetRegexten", PV_EXTENSION)) + return 0; + return p->u4.regexten; +} + +void pvalExtenSetHints( pval *p, char *hints ) +{ + if (!pvalCheckType(p, "pvalExtenSetHints", PV_EXTENSION)) + return; + p->u3.hints = hints; +} + +char* pvalExtenGetHints( pval *p ) +{ + if (!pvalCheckType(p, "pvalExtenGetHints", PV_EXTENSION)) + return 0; + return p->u3.hints; +} + +void pvalExtenSetStatement( pval *p, pval *statement ) +{ + if (!pvalCheckType(p, "pvalExtenSetStatement", PV_EXTENSION)) + return; + p->u2.statements = statement; +} + +pval* pvalExtenGetStatement( pval *p ) +{ + if (!pvalCheckType(p, "pvalExtenGetStatement", PV_EXTENSION)) + return 0; + return p->u2.statements; +} + + +void pvalIgnorePatSetPattern( pval *p, char *pat ) +{ + if (!pvalCheckType(p, "pvalIgnorePatSetPattern", PV_IGNOREPAT)) + return; + p->u1.str = pat; +} + +char* pvalIgnorePatGetPattern( pval *p ) +{ + if (!pvalCheckType(p, "pvalIgnorePatGetPattern", PV_IGNOREPAT)) + return 0; + return p->u1.str; +} + + +void pvalGlobalsAddStatement( pval *p, pval *statement ) +{ + if (p->type != PV_GLOBALS) { + ast_log(LOG_ERROR, "pvalGlobalsAddStatement called where first arg is not a Globals!\n"); + } else { + if (!p->u1.statements) { + p->u1.statements = statement; + } else { + p->u1.statements = linku1(p->u1.statements,statement); + } + } +} + +pval* pvalGlobalsWalkStatements( pval *p, pval **next_statement ) +{ + if (!pvalCheckType(p, "pvalGlobalsWalkStatements", PV_GLOBALS)) + return 0; + if (!next_statement) { + *next_statement = p; + return p; + } else { + *next_statement = (*next_statement)->next; + return (*next_statement)->next; + } +} + + +void pvalTopLevAddObject( pval *p, pval *contextOrObj ) +{ + if (p) { + linku1(p,contextOrObj); + } else { + ast_log(LOG_ERROR, "First arg to pvalTopLevel is NULL!\n"); + } +} + +pval *pvalTopLevWalkObjects(pval *p, pval **next_obj ) +{ + if (!next_obj) { + *next_obj = p; + return p; + } else { + *next_obj = (*next_obj)->next; + return (*next_obj)->next; + } +} + +/* append second element to the list in the first one via next pointers */ +pval * linku1(pval *head, pval *tail) +{ + if (!head) + return tail; + if (tail) { + if (!head->next) { + head->next = tail; + } else { + head->u1_last->next = tail; + } + head->u1_last = tail; + tail->prev = head; /* the dad link only points to containers */ + } + return head; +} + +#ifdef HERE_BY_MISTAKE_I_THINK +static char *config = "extensions.ael"; +int do_pbx_load_module(void) +{ + int errs, sem_err, sem_warn, sem_note; + char *rfilename; + struct ast_context *local_contexts=NULL, *con; + struct pval *parse_tree; + + ast_log(LOG_NOTICE, "Starting AEL load process.\n"); + if (config[0] == '/') + rfilename = (char *)config; + else { + rfilename = alloca(strlen(config) + strlen(ast_config_AST_CONFIG_DIR) + 2); + sprintf(rfilename, "%s/%s", ast_config_AST_CONFIG_DIR, config); + } + ast_log(LOG_NOTICE, "AEL load process: calculated config file name '%s'.\n", rfilename); + + if (access(rfilename,R_OK) != 0) { + ast_log(LOG_NOTICE, "File %s not found; AEL declining load\n", rfilename); + return AST_MODULE_LOAD_DECLINE; + } + + parse_tree = ael2_parse(rfilename, &errs); + ast_log(LOG_DEBUG, "AEL load process: parsed config file name '%s'.\n", rfilename); + ael2_semantic_check(parse_tree, &sem_err, &sem_warn, &sem_note); + if (errs == 0 && sem_err == 0) { + ast_log(LOG_DEBUG, "AEL load process: checked config file name '%s'.\n", rfilename); + ast_compile_ael2(&local_contexts, parse_tree); + ast_log(LOG_DEBUG, "AEL load process: compiled config file name '%s'.\n", rfilename); + + ast_merge_contexts_and_delete(&local_contexts, registrar); + ast_log(LOG_DEBUG, "AEL load process: merged config file name '%s'.\n", rfilename); + for (con = ast_walk_contexts(NULL); con; con = ast_walk_contexts(con)) + ast_context_verify_includes(con); + ast_log(LOG_DEBUG, "AEL load process: verified config file name '%s'.\n", rfilename); + } else { + ast_log(LOG_ERROR, "Sorry, but %d syntax errors and %d semantic errors were detected. It doesn't make sense to compile.\n", errs, sem_err); + destroy_pval(parse_tree); /* free up the memory */ + return AST_MODULE_LOAD_FAILURE; + } + destroy_pval(parse_tree); /* free up the memory */ + + return AST_MODULE_LOAD_SUCCESS; +} +#endif + diff --git a/trunk/res/res_adsi.c b/trunk/res/res_adsi.c new file mode 100644 index 000000000..2df5324aa --- /dev/null +++ b/trunk/res/res_adsi.c @@ -0,0 +1,1120 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * + * Includes code and algorithms from the Zapata library. + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief ADSI support + * + * \author Mark Spencer <markster@digium.com> + * + * \note this module is required by app_voicemail and app_getcpeid + * \todo Move app_getcpeid into this module + * \todo Create a core layer so that app_voicemail does not require + * res_adsi to load + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <time.h> +#include <math.h> + +#include "asterisk/adsi.h" +#include "asterisk/ulaw.h" +#include "asterisk/alaw.h" +#include "asterisk/callerid.h" +#include "asterisk/fskmodem.h" +#include "asterisk/channel.h" +#include "asterisk/module.h" +#include "asterisk/config.h" +#include "asterisk/file.h" + +#define DEFAULT_ADSI_MAX_RETRIES 3 + +#define ADSI_MAX_INTRO 20 +#define ADSI_MAX_SPEED_DIAL 6 + +#define ADSI_FLAG_DATAMODE (1 << 8) + +static int maxretries = DEFAULT_ADSI_MAX_RETRIES; + +/* Asterisk ADSI button definitions */ +#define ADSI_SPEED_DIAL 10 /* 10-15 are reserved for speed dial */ + +static char intro[ADSI_MAX_INTRO][20]; +static int aligns[ADSI_MAX_INTRO]; + +#define SPEEDDIAL_MAX_LEN 20 +static char speeddial[ADSI_MAX_SPEED_DIAL][3][SPEEDDIAL_MAX_LEN]; + +static int alignment = 0; + +static int adsi_generate(unsigned char *buf, int msgtype, unsigned char *msg, int msglen, int msgnum, int last, int codec) +{ + int sum, x, bytes = 0; + /* Initial carrier (imaginary) */ + float cr = 1.0, ci = 0.0, scont = 0.0; + + if (msglen > 255) + msglen = 255; + + /* If first message, Send 150ms of MARK's */ + if (msgnum == 1) { + for (x = 0; x < 150; x++) /* was 150 */ + PUT_CLID_MARKMS; + } + + /* Put message type */ + PUT_CLID(msgtype); + sum = msgtype; + + /* Put message length (plus one for the message number) */ + PUT_CLID(msglen + 1); + sum += msglen + 1; + + /* Put message number */ + PUT_CLID(msgnum); + sum += msgnum; + + /* Put actual message */ + for (x = 0; x < msglen; x++) { + PUT_CLID(msg[x]); + sum += msg[x]; + } + + /* Put 2's compliment of sum */ + PUT_CLID(256-(sum & 0xff)); + +#if 0 + if (last) { + /* Put trailing marks */ + for (x = 0; x < 50; x++) + PUT_CLID_MARKMS; + } +#endif + return bytes; + +} + +static int adsi_careful_send(struct ast_channel *chan, unsigned char *buf, int len, int *remainder) +{ + /* Sends carefully on a full duplex channel by using reading for + timing */ + struct ast_frame *inf, outf; + int amt; + + /* Zero out our outgoing frame */ + memset(&outf, 0, sizeof(outf)); + + if (remainder && *remainder) { + amt = len; + + /* Send remainder if provided */ + if (amt > *remainder) + amt = *remainder; + else + *remainder = *remainder - amt; + outf.frametype = AST_FRAME_VOICE; + outf.subclass = AST_FORMAT_ULAW; + outf.data = buf; + outf.datalen = amt; + outf.samples = amt; + if (ast_write(chan, &outf)) { + ast_log(LOG_WARNING, "Failed to carefully write frame\n"); + return -1; + } + /* Update pointers and lengths */ + buf += amt; + len -= amt; + } + + while(len) { + amt = len; + /* If we don't get anything at all back in a second, forget + about it */ + if (ast_waitfor(chan, 1000) < 1) + return -1; + /* Detect hangup */ + if (!(inf = ast_read(chan))) + return -1; + + /* Drop any frames that are not voice */ + if (inf->frametype != AST_FRAME_VOICE) { + ast_frfree(inf); + continue; + } + + if (inf->subclass != AST_FORMAT_ULAW) { + ast_log(LOG_WARNING, "Channel not in ulaw?\n"); + ast_frfree(inf); + return -1; + } + /* Send no more than they sent us */ + if (amt > inf->datalen) + amt = inf->datalen; + else if (remainder) + *remainder = inf->datalen - amt; + outf.frametype = AST_FRAME_VOICE; + outf.subclass = AST_FORMAT_ULAW; + outf.data = buf; + outf.datalen = amt; + outf.samples = amt; + if (ast_write(chan, &outf)) { + ast_log(LOG_WARNING, "Failed to carefully write frame\n"); + ast_frfree(inf); + return -1; + } + /* Update pointers and lengths */ + buf += amt; + len -= amt; + ast_frfree(inf); + } + return 0; +} + +static int __adsi_transmit_messages(struct ast_channel *chan, unsigned char **msg, int *msglen, int *msgtype) +{ + /* msglen must be no more than 256 bits, each */ + unsigned char buf[24000 * 5]; + int pos = 0, res, x, start = 0, retries = 0, waittime, rem = 0, def; + char ack[3]; + struct ast_frame *f; + + if (chan->adsicpe == AST_ADSI_UNAVAILABLE) { + /* Don't bother if we know they don't support ADSI */ + errno = ENOSYS; + return -1; + } + + while(retries < maxretries) { + if (!(chan->adsicpe & ADSI_FLAG_DATAMODE)) { + /* Generate CAS (no SAS) */ + ast_gen_cas(buf, 0, 680, AST_FORMAT_ULAW); + + /* Send CAS */ + if (adsi_careful_send(chan, buf, 680, NULL)) + ast_log(LOG_WARNING, "Unable to send CAS\n"); + + /* Wait For DTMF result */ + waittime = 500; + for(;;) { + if (((res = ast_waitfor(chan, waittime)) < 1)) { + /* Didn't get back DTMF A in time */ + ast_debug(1, "No ADSI CPE detected (%d)\n", res); + if (!chan->adsicpe) + chan->adsicpe = AST_ADSI_UNAVAILABLE; + errno = ENOSYS; + return -1; + } + waittime = res; + if (!(f = ast_read(chan))) { + ast_debug(1, "Hangup in ADSI\n"); + return -1; + } + if (f->frametype == AST_FRAME_DTMF) { + if (f->subclass == 'A') { + /* Okay, this is an ADSI CPE. Note this for future reference, too */ + if (!chan->adsicpe) + chan->adsicpe = AST_ADSI_AVAILABLE; + break; + } else { + if (f->subclass == 'D') + ast_debug(1, "Off-hook capable CPE only, not ADSI\n"); + else + ast_log(LOG_WARNING, "Unknown ADSI response '%c'\n", f->subclass); + if (!chan->adsicpe) + chan->adsicpe = AST_ADSI_UNAVAILABLE; + errno = ENOSYS; + ast_frfree(f); + return -1; + } + } + ast_frfree(f); + } + + ast_debug(1, "ADSI Compatible CPE Detected\n"); + } else { + ast_debug(1, "Already in data mode\n"); + } + + x = 0; + pos = 0; +#if 1 + def= ast_channel_defer_dtmf(chan); +#endif + while ((x < 6) && msg[x]) { + if ((res = adsi_generate(buf + pos, msgtype[x], msg[x], msglen[x], x+1 - start, (x == 5) || !msg[x+1], AST_FORMAT_ULAW)) < 0) { + ast_log(LOG_WARNING, "Failed to generate ADSI message %d on channel %s\n", x + 1, chan->name); + return -1; + } + ast_debug(1, "Message %d, of %d input bytes, %d output bytes\n", x + 1, msglen[x], res); + pos += res; + x++; + } + + + rem = 0; + res = adsi_careful_send(chan, buf, pos, &rem); + if (!def) + ast_channel_undefer_dtmf(chan); + if (res) + return -1; + + ast_debug(1, "Sent total spill of %d bytes\n", pos); + + memset(ack, 0, sizeof(ack)); + /* Get real result and check for hangup */ + if ((res = ast_readstring(chan, ack, 2, 1000, 1000, "")) < 0) + return -1; + if (ack[0] == 'D') { + ast_debug(1, "Acked up to message %d\n", atoi(ack + 1)); start += atoi(ack + 1); + if (start >= x) + break; + else { + retries++; + ast_debug(1, "Retransmitting (%d), from %d\n", retries, start + 1); + } + } else { + retries++; + ast_log(LOG_WARNING, "Unexpected response to ack: %s (retry %d)\n", ack, retries); + } + } + if (retries >= maxretries) { + ast_log(LOG_WARNING, "Maximum ADSI Retries (%d) exceeded\n", maxretries); + errno = ETIMEDOUT; + return -1; + } + return 0; + +} + +static int _ast_adsi_begin_download(struct ast_channel *chan, char *service, unsigned char *fdn, unsigned char *sec, int version) +{ + int bytes = 0; + unsigned char buf[256]; + char ack[2]; + + /* Setup the resident soft key stuff, a piece at a time */ + /* Upload what scripts we can for voicemail ahead of time */ + bytes += ast_adsi_download_connect(buf + bytes, service, fdn, sec, version); + if (ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DOWNLOAD, 0)) + return -1; + if (ast_readstring(chan, ack, 1, 10000, 10000, "")) + return -1; + if (ack[0] == 'B') + return 0; + ast_debug(1, "Download was denied by CPE\n"); + return -1; +} + +static int _ast_adsi_end_download(struct ast_channel *chan) +{ + int bytes = 0; + unsigned char buf[256]; + + /* Setup the resident soft key stuff, a piece at a time */ + /* Upload what scripts we can for voicemail ahead of time */ + bytes += ast_adsi_download_disconnect(buf + bytes); + if (ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DOWNLOAD, 0)) + return -1; + return 0; +} + +static int _ast_adsi_transmit_message_full(struct ast_channel *chan, unsigned char *msg, int msglen, int msgtype, int dowait) +{ + unsigned char *msgs[5] = { NULL, NULL, NULL, NULL, NULL }; + int msglens[5], msgtypes[5], newdatamode = (chan->adsicpe & ADSI_FLAG_DATAMODE), res, x, writeformat = chan->writeformat, readformat = chan->readformat, waitforswitch = 0; + + for (x = 0; x < msglen; x += (msg[x+1]+2)) { + if (msg[x] == ADSI_SWITCH_TO_DATA) { + ast_debug(1, "Switch to data is sent!\n"); + waitforswitch++; + newdatamode = ADSI_FLAG_DATAMODE; + } + + if (msg[x] == ADSI_SWITCH_TO_VOICE) { + ast_debug(1, "Switch to voice is sent!\n"); + waitforswitch++; + newdatamode = 0; + } + } + msgs[0] = msg; + + msglens[0] = msglen; + msgtypes[0] = msgtype; + + if (msglen > 253) { + ast_log(LOG_WARNING, "Can't send ADSI message of %d bytes, too large\n", msglen); + return -1; + } + + ast_stopstream(chan); + + if (ast_set_write_format(chan, AST_FORMAT_ULAW)) { + ast_log(LOG_WARNING, "Unable to set write format to ULAW\n"); + return -1; + } + + if (ast_set_read_format(chan, AST_FORMAT_ULAW)) { + ast_log(LOG_WARNING, "Unable to set read format to ULAW\n"); + if (writeformat) { + if (ast_set_write_format(chan, writeformat)) + ast_log(LOG_WARNING, "Unable to restore write format to %d\n", writeformat); + } + return -1; + } + res = __adsi_transmit_messages(chan, msgs, msglens, msgtypes); + + if (dowait) { + ast_debug(1, "Wait for switch is '%d'\n", waitforswitch); + while (waitforswitch-- && ((res = ast_waitfordigit(chan, 1000)) > 0)) { + res = 0; + ast_debug(1, "Waiting for 'B'...\n"); + } + } + + if (!res) + chan->adsicpe = (chan->adsicpe & ~ADSI_FLAG_DATAMODE) | newdatamode; + + if (writeformat) + ast_set_write_format(chan, writeformat); + if (readformat) + ast_set_read_format(chan, readformat); + + if (!res) + res = ast_safe_sleep(chan, 100 ); + return res; +} + +static int _ast_adsi_transmit_message(struct ast_channel *chan, unsigned char *msg, int msglen, int msgtype) +{ + return ast_adsi_transmit_message_full(chan, msg, msglen, msgtype, 1); +} + +static inline int ccopy(unsigned char *dst, const unsigned char *src, int max) +{ + int x = 0; + /* Carefully copy the requested data */ + while ((x < max) && src[x] && (src[x] != 0xff)) { + dst[x] = src[x]; + x++; + } + return x; +} + +static int _ast_adsi_load_soft_key(unsigned char *buf, int key, const char *llabel, const char *slabel, char *ret, int data) +{ + int bytes = 0; + + /* Abort if invalid key specified */ + if ((key < 2) || (key > 33)) + return -1; + + buf[bytes++] = ADSI_LOAD_SOFTKEY; + /* Reserve for length */ + bytes++; + /* Which key */ + buf[bytes++] = key; + + /* Carefully copy long label */ + bytes += ccopy(buf + bytes, (const unsigned char *)llabel, 18); + + /* Place delimiter */ + buf[bytes++] = 0xff; + + /* Short label */ + bytes += ccopy(buf + bytes, (const unsigned char *)slabel, 7); + + + /* If specified, copy return string */ + if (ret) { + /* Place delimiter */ + buf[bytes++] = 0xff; + if (data) + buf[bytes++] = ADSI_SWITCH_TO_DATA2; + /* Carefully copy return string */ + bytes += ccopy(buf + bytes, (const unsigned char *)ret, 20); + + } + /* Replace parameter length */ + buf[1] = bytes - 2; + return bytes; + +} + +static int _ast_adsi_connect_session(unsigned char *buf, unsigned char *fdn, int ver) +{ + int bytes = 0, x; + + /* Message type */ + buf[bytes++] = ADSI_CONNECT_SESSION; + + /* Reserve space for length */ + bytes++; + + if (fdn) { + for (x = 0; x < 4; x++) + buf[bytes++] = fdn[x]; + if (ver > -1) + buf[bytes++] = ver & 0xff; + } + + buf[1] = bytes - 2; + return bytes; + +} + +static int _ast_adsi_download_connect(unsigned char *buf, char *service, unsigned char *fdn, unsigned char *sec, int ver) +{ + int bytes = 0, x; + + /* Message type */ + buf[bytes++] = ADSI_DOWNLOAD_CONNECT; + + /* Reserve space for length */ + bytes++; + + /* Primary column */ + bytes+= ccopy(buf + bytes, (unsigned char *)service, 18); + + /* Delimiter */ + buf[bytes++] = 0xff; + + for (x = 0; x < 4; x++) + buf[bytes++] = fdn[x]; + + for (x = 0; x < 4; x++) + buf[bytes++] = sec[x]; + + buf[bytes++] = ver & 0xff; + + buf[1] = bytes - 2; + + return bytes; + +} + +static int _ast_adsi_disconnect_session(unsigned char *buf) +{ + int bytes = 0; + + /* Message type */ + buf[bytes++] = ADSI_DISC_SESSION; + + /* Reserve space for length */ + bytes++; + + buf[1] = bytes - 2; + return bytes; + +} + +static int _ast_adsi_query_cpeid(unsigned char *buf) +{ + int bytes = 0; + buf[bytes++] = ADSI_QUERY_CPEID; + /* Reserve space for length */ + bytes++; + buf[1] = bytes - 2; + return bytes; +} + +static int _ast_adsi_query_cpeinfo(unsigned char *buf) +{ + int bytes = 0; + buf[bytes++] = ADSI_QUERY_CONFIG; + /* Reserve space for length */ + bytes++; + buf[1] = bytes - 2; + return bytes; +} + +static int _ast_adsi_read_encoded_dtmf(struct ast_channel *chan, unsigned char *buf, int maxlen) +{ + int bytes = 0, res, gotstar = 0, pos = 0; + unsigned char current = 0; + + memset(buf, 0, sizeof(buf)); + + while(bytes <= maxlen) { + /* Wait up to a second for a digit */ + if (!(res = ast_waitfordigit(chan, 1000))) + break; + if (res == '*') { + gotstar = 1; + continue; + } + /* Ignore anything other than a digit */ + if ((res < '0') || (res > '9')) + continue; + res -= '0'; + if (gotstar) + res += 9; + if (pos) { + pos = 0; + buf[bytes++] = (res << 4) | current; + } else { + pos = 1; + current = res; + } + gotstar = 0; + } + + return bytes; +} + +static int _ast_adsi_get_cpeid(struct ast_channel *chan, unsigned char *cpeid, int voice) +{ + unsigned char buf[256] = ""; + int bytes = 0, res; + + bytes += ast_adsi_data_mode(buf); + ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DISPLAY, 0); + + bytes = 0; + bytes += ast_adsi_query_cpeid(buf); + ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DISPLAY, 0); + + /* Get response */ + res = ast_adsi_read_encoded_dtmf(chan, cpeid, 4); + if (res != 4) { + ast_log(LOG_WARNING, "Got %d bytes back of encoded DTMF, expecting 4\n", res); + res = 0; + } else { + res = 1; + } + + if (voice) { + bytes = 0; + bytes += ast_adsi_voice_mode(buf, 0); + ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DISPLAY, 0); + /* Ignore the resulting DTMF B announcing it's in voice mode */ + ast_waitfordigit(chan, 1000); + } + return res; +} + +static int _ast_adsi_get_cpeinfo(struct ast_channel *chan, int *width, int *height, int *buttons, int voice) +{ + unsigned char buf[256] = ""; + int bytes = 0, res; + + bytes += ast_adsi_data_mode(buf); + ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DISPLAY, 0); + + bytes = 0; + bytes += ast_adsi_query_cpeinfo(buf); + ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DISPLAY, 0); + + /* Get width */ + if ((res = ast_readstring(chan, (char *)buf, 2, 1000, 500, "")) < 0) + return res; + if (strlen((char *)buf) != 2) { + ast_log(LOG_WARNING, "Got %d bytes of width, expecting 2\n", res); + res = 0; + } else { + res = 1; + } + if (width) + *width = atoi((char *)buf); + /* Get height */ + memset(buf, 0, sizeof(buf)); + if (res) { + if ((res = ast_readstring(chan, (char *)buf, 2, 1000, 500, "")) < 0) + return res; + if (strlen((char *)buf) != 2) { + ast_log(LOG_WARNING, "Got %d bytes of height, expecting 2\n", res); + res = 0; + } else { + res = 1; + } + if (height) + *height= atoi((char *)buf); + } + /* Get buttons */ + memset(buf, 0, sizeof(buf)); + if (res) { + if ((res = ast_readstring(chan, (char *)buf, 1, 1000, 500, "")) < 0) + return res; + if (strlen((char *)buf) != 1) { + ast_log(LOG_WARNING, "Got %d bytes of buttons, expecting 1\n", res); + res = 0; + } else { + res = 1; + } + if (buttons) + *buttons = atoi((char *)buf); + } + if (voice) { + bytes = 0; + bytes += ast_adsi_voice_mode(buf, 0); + ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DISPLAY, 0); + /* Ignore the resulting DTMF B announcing it's in voice mode */ + ast_waitfordigit(chan, 1000); + } + return res; +} + +static int _ast_adsi_data_mode(unsigned char *buf) +{ + int bytes = 0; + + /* Message type */ + buf[bytes++] = ADSI_SWITCH_TO_DATA; + + /* Reserve space for length */ + bytes++; + + buf[1] = bytes - 2; + return bytes; + +} + +static int _ast_adsi_clear_soft_keys(unsigned char *buf) +{ + int bytes = 0; + + /* Message type */ + buf[bytes++] = ADSI_CLEAR_SOFTKEY; + + /* Reserve space for length */ + bytes++; + + buf[1] = bytes - 2; + return bytes; + +} + +static int _ast_adsi_clear_screen(unsigned char *buf) +{ + int bytes = 0; + + /* Message type */ + buf[bytes++] = ADSI_CLEAR_SCREEN; + + /* Reserve space for length */ + bytes++; + + buf[1] = bytes - 2; + return bytes; + +} + +static int _ast_adsi_voice_mode(unsigned char *buf, int when) +{ + int bytes = 0; + + /* Message type */ + buf[bytes++] = ADSI_SWITCH_TO_VOICE; + + /* Reserve space for length */ + bytes++; + + buf[bytes++] = when & 0x7f; + + buf[1] = bytes - 2; + return bytes; + +} + +static int _ast_adsi_available(struct ast_channel *chan) +{ + int cpe = chan->adsicpe & 0xff; + if ((cpe == AST_ADSI_AVAILABLE) || + (cpe == AST_ADSI_UNKNOWN)) + return 1; + return 0; +} + +static int _ast_adsi_download_disconnect(unsigned char *buf) +{ + int bytes = 0; + + /* Message type */ + buf[bytes++] = ADSI_DOWNLOAD_DISC; + + /* Reserve space for length */ + bytes++; + + buf[1] = bytes - 2; + return bytes; + +} + +static int _ast_adsi_display(unsigned char *buf, int page, int line, int just, int wrap, + char *col1, char *col2) +{ + int bytes = 0; + + /* Sanity check line number */ + + if (page) { + if (line > 4) return -1; + } else { + if (line > 33) return -1; + } + + if (line < 1) + return -1; + /* Parameter type */ + buf[bytes++] = ADSI_LOAD_VIRTUAL_DISP; + + /* Reserve space for size */ + bytes++; + + /* Page and wrap indicator */ + buf[bytes++] = ((page & 0x1) << 7) | ((wrap & 0x1) << 6) | (line & 0x3f); + + /* Justification */ + buf[bytes++] = (just & 0x3) << 5; + + /* Omit highlight mode definition */ + buf[bytes++] = 0xff; + + /* Primary column */ + bytes+= ccopy(buf + bytes, (unsigned char *)col1, 20); + + /* Delimiter */ + buf[bytes++] = 0xff; + + /* Secondary column */ + bytes += ccopy(buf + bytes, (unsigned char *)col2, 20); + + /* Update length */ + buf[1] = bytes - 2; + + return bytes; + +} + +static int _ast_adsi_input_control(unsigned char *buf, int page, int line, int display, int format, int just) +{ + int bytes = 0; + + if (page) { + if (line > 4) return -1; + } else { + if (line > 33) return -1; + } + + if (line < 1) + return -1; + + buf[bytes++] = ADSI_INPUT_CONTROL; + bytes++; + buf[bytes++] = ((page & 1) << 7) | (line & 0x3f); + buf[bytes++] = ((display & 1) << 7) | ((just & 0x3) << 4) | (format & 0x7); + + buf[1] = bytes - 2; + return bytes; + +} + +static int _ast_adsi_input_format(unsigned char *buf, int num, int dir, int wrap, char *format1, char *format2) +{ + int bytes = 0; + + if (!strlen((char *)format1)) + return -1; + + buf[bytes++] = ADSI_INPUT_FORMAT; + bytes++; + buf[bytes++] = ((dir & 1) << 7) | ((wrap & 1) << 6) | (num & 0x7); + bytes += ccopy(buf + bytes, (unsigned char *)format1, 20); + buf[bytes++] = 0xff; + if (format2 && strlen((char *)format2)) { + bytes += ccopy(buf + bytes, (unsigned char *)format2, 20); + } + buf[1] = bytes - 2; + return bytes; +} + +static int _ast_adsi_set_keys(unsigned char *buf, unsigned char *keys) +{ + int bytes = 0, x; + + /* Message type */ + buf[bytes++] = ADSI_INIT_SOFTKEY_LINE; + /* Space for size */ + bytes++; + /* Key definitions */ + for (x = 0; x < 6; x++) + buf[bytes++] = (keys[x] & 0x3f) ? keys[x] : (keys[x] | 0x1); + buf[1] = bytes - 2; + return bytes; +} + +static int _ast_adsi_set_line(unsigned char *buf, int page, int line) +{ + int bytes = 0; + + /* Sanity check line number */ + + if (page) { + if (line > 4) return -1; + } else { + if (line > 33) return -1; + } + + if (line < 1) + return -1; + /* Parameter type */ + buf[bytes++] = ADSI_LINE_CONTROL; + + /* Reserve space for size */ + bytes++; + + /* Page and line */ + buf[bytes++] = ((page & 0x1) << 7) | (line & 0x3f); + + buf[1] = bytes - 2; + return bytes; + +}; + +static int total = 0; +static int speeds = 0; + +static int _ast_adsi_channel_restore(struct ast_channel *chan) +{ + unsigned char dsp[256] = "", keyd[6] = ""; + int bytes, x; + + /* Start with initial display setup */ + bytes = 0; + bytes += ast_adsi_set_line(dsp + bytes, ADSI_INFO_PAGE, 1); + + /* Prepare key setup messages */ + + if (speeds) { + for (x = 0; x < speeds; x++) + keyd[x] = ADSI_SPEED_DIAL + x; + bytes += ast_adsi_set_keys(dsp + bytes, keyd); + } + ast_adsi_transmit_message_full(chan, dsp, bytes, ADSI_MSG_DISPLAY, 0); + return 0; + +} + +static int _ast_adsi_print(struct ast_channel *chan, char **lines, int *aligns, int voice) +{ + unsigned char buf[4096]; + int bytes = 0, res, x; + + for(x = 0; lines[x]; x++) + bytes += ast_adsi_display(buf + bytes, ADSI_INFO_PAGE, x+1, aligns[x], 0, lines[x], ""); + bytes += ast_adsi_set_line(buf + bytes, ADSI_INFO_PAGE, 1); + if (voice) + bytes += ast_adsi_voice_mode(buf + bytes, 0); + res = ast_adsi_transmit_message_full(chan, buf, bytes, ADSI_MSG_DISPLAY, 0); + if (voice) + /* Ignore the resulting DTMF B announcing it's in voice mode */ + ast_waitfordigit(chan, 1000); + return res; +} + +static int _ast_adsi_load_session(struct ast_channel *chan, unsigned char *app, int ver, int data) +{ + unsigned char dsp[256] = ""; + int bytes = 0, res; + char resp[2]; + + /* Connect to session */ + bytes += ast_adsi_connect_session(dsp + bytes, app, ver); + + if (data) + bytes += ast_adsi_data_mode(dsp + bytes); + + /* Prepare key setup messages */ + if (ast_adsi_transmit_message_full(chan, dsp, bytes, ADSI_MSG_DISPLAY, 0)) + return -1; + if (app) { + if ((res = ast_readstring(chan, resp, 1, 1200, 1200, "")) < 0) + return -1; + if (res) { + ast_debug(1, "No response from CPE about version. Assuming not there.\n"); + return 0; + } + if (!strcmp(resp, "B")) { + ast_debug(1, "CPE has script '%s' version %d already loaded\n", app, ver); + return 1; + } else if (!strcmp(resp, "A")) { + ast_debug(1, "CPE hasn't script '%s' version %d already loaded\n", app, ver); + } else { + ast_log(LOG_WARNING, "Unexpected CPE response to script query: %s\n", resp); + } + } else + return 1; + return 0; + +} + +static int _ast_adsi_unload_session(struct ast_channel *chan) +{ + unsigned char dsp[256] = ""; + int bytes = 0; + + /* Connect to session */ + bytes += ast_adsi_disconnect_session(dsp + bytes); + bytes += ast_adsi_voice_mode(dsp + bytes, 0); + + /* Prepare key setup messages */ + if (ast_adsi_transmit_message_full(chan, dsp, bytes, ADSI_MSG_DISPLAY, 0)) + return -1; + + return 0; +} + +static int str2align(const char *s) +{ + if (!strncasecmp(s, "l", 1)) + return ADSI_JUST_LEFT; + else if (!strncasecmp(s, "r", 1)) + return ADSI_JUST_RIGHT; + else if (!strncasecmp(s, "i", 1)) + return ADSI_JUST_IND; + else + return ADSI_JUST_CENT; +} + +static void init_state(void) +{ + int x; + + for (x = 0; x < ADSI_MAX_INTRO; x++) + aligns[x] = ADSI_JUST_CENT; + ast_copy_string(intro[0], "Welcome to the", sizeof(intro[0])); + ast_copy_string(intro[1], "Asterisk", sizeof(intro[1])); + ast_copy_string(intro[2], "Open Source PBX", sizeof(intro[2])); + total = 3; + speeds = 0; + for (x = 3; x < ADSI_MAX_INTRO; x++) + intro[x][0] = '\0'; + memset(speeddial, 0, sizeof(speeddial)); + alignment = ADSI_JUST_CENT; +} + +static void adsi_load(int reload) +{ + int x = 0; + struct ast_config *conf = NULL; + struct ast_variable *v; + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + char *name, *sname; + init_state(); + + if (!(conf = ast_config_load("adsi.conf", config_flags))) + return; + else if (conf == CONFIG_STATUS_FILEUNCHANGED) + return; + for (v = ast_variable_browse(conf, "intro"); v; v = v->next) { + if (!strcasecmp(v->name, "alignment")) + alignment = str2align(v->value); + else if (!strcasecmp(v->name, "greeting")) { + if (x < ADSI_MAX_INTRO) { + aligns[x] = alignment; + ast_copy_string(intro[x], v->value, sizeof(intro[x])); + x++; + } + } else if (!strcasecmp(v->name, "maxretries")) { + if (atoi(v->value) > 0) + maxretries = atoi(v->value); + } + } + if (x) + total = x; + + x = 0; + for (v = ast_variable_browse(conf, "speeddial"); v; v = v->next) { + char buf[3 * SPEEDDIAL_MAX_LEN]; + char *stringp = buf; + ast_copy_string(buf, v->value, sizeof(buf)); + name = strsep(&stringp, ","); + sname = strsep(&stringp, ","); + if (!sname) + sname = name; + if (x < ADSI_MAX_SPEED_DIAL) { + ast_copy_string(speeddial[x][0], v->name, sizeof(speeddial[x][0])); + ast_copy_string(speeddial[x][1], name, 18); + ast_copy_string(speeddial[x][2], sname, 7); + x++; + } + } + if (x) + speeds = x; + ast_config_destroy(conf); + + return; +} + +static int reload(void) +{ + adsi_load(1); + return 0; +} + +static int load_module(void) +{ + adsi_load(0); + + ast_adsi_begin_download = _ast_adsi_begin_download; + ast_adsi_end_download = _ast_adsi_end_download; + ast_adsi_channel_restore = _ast_adsi_channel_restore; + ast_adsi_print = _ast_adsi_print; + ast_adsi_load_session = _ast_adsi_load_session; + ast_adsi_unload_session = _ast_adsi_unload_session; + ast_adsi_transmit_message = _ast_adsi_transmit_message; + ast_adsi_transmit_message_full = _ast_adsi_transmit_message_full; + ast_adsi_read_encoded_dtmf = _ast_adsi_read_encoded_dtmf; + ast_adsi_connect_session = _ast_adsi_connect_session; + ast_adsi_query_cpeid = _ast_adsi_query_cpeid; + ast_adsi_query_cpeinfo = _ast_adsi_query_cpeinfo; + ast_adsi_get_cpeid = _ast_adsi_get_cpeid; + ast_adsi_get_cpeinfo = _ast_adsi_get_cpeinfo; + ast_adsi_download_connect = _ast_adsi_download_connect; + ast_adsi_disconnect_session = _ast_adsi_disconnect_session; + ast_adsi_download_disconnect = _ast_adsi_download_disconnect; + ast_adsi_data_mode = _ast_adsi_data_mode; + ast_adsi_clear_soft_keys = _ast_adsi_clear_soft_keys; + ast_adsi_clear_screen = _ast_adsi_clear_screen; + ast_adsi_voice_mode = _ast_adsi_voice_mode; + ast_adsi_available = _ast_adsi_available; + ast_adsi_display = _ast_adsi_display; + ast_adsi_set_line = _ast_adsi_set_line; + ast_adsi_load_soft_key = _ast_adsi_load_soft_key; + ast_adsi_set_keys = _ast_adsi_set_keys; + ast_adsi_input_control = _ast_adsi_input_control; + ast_adsi_input_format = _ast_adsi_input_format; + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + /* Can't unload this once we're loaded */ + return -1; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "ADSI Resource", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); diff --git a/trunk/res/res_ael_share.c b/trunk/res/res_ael_share.c new file mode 100644 index 000000000..805dfd9a8 --- /dev/null +++ b/trunk/res/res_ael_share.c @@ -0,0 +1,54 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2007, Digium, Inc. + * + * Steve Murphy <murf@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Shareable AEL code -- mainly between internal and external modules + * + * \author Steve Murphy <murf@digium.com> + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/config.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/cli.h" + + +static int unload_module(void) +{ + return 0; +} + +static int load_module(void) +{ + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "share-able code for AEL", + .load = load_module, + .unload = unload_module + ); diff --git a/trunk/res/res_agi.c b/trunk/res/res_agi.c new file mode 100644 index 000000000..75692089b --- /dev/null +++ b/trunk/res/res_agi.c @@ -0,0 +1,2992 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief AGI - the Asterisk Gateway Interface + * + * \author Mark Spencer <markster@digium.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <math.h> +#include <signal.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <pthread.h> + +#include "asterisk/paths.h" /* use many ast_config_AST_*_DIR */ +#include "asterisk/network.h" +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/astdb.h" +#include "asterisk/callerid.h" +#include "asterisk/cli.h" +#include "asterisk/image.h" +#include "asterisk/say.h" +#include "asterisk/app.h" +#include "asterisk/dsp.h" +#include "asterisk/musiconhold.h" +#include "asterisk/utils.h" +#include "asterisk/lock.h" +#include "asterisk/strings.h" +#include "asterisk/agi.h" +#include "asterisk/manager.h" +#include "asterisk/version.h" +#include "asterisk/speech.h" +#include "asterisk/manager.h" + +#define MAX_ARGS 128 +#define AGI_NANDFS_RETRY 3 +#define AGI_BUF_LEN 2048 + +static char *app = "AGI"; + +static char *eapp = "EAGI"; + +static char *deadapp = "DeadAGI"; + +static char *synopsis = "Executes an AGI compliant application"; +static char *esynopsis = "Executes an EAGI compliant application"; +static char *deadsynopsis = "Executes AGI on a hungup channel"; + +static char *descrip = +" [E|Dead]AGI(command,args): Executes an Asterisk Gateway Interface compliant\n" +"program on a channel. AGI allows Asterisk to launch external programs\n" +"written in any language to control a telephony channel, play audio,\n" +"read DTMF digits, etc. by communicating with the AGI protocol on stdin\n" +"and stdout.\n" +" This channel will stop dialplan execution on hangup inside of this\n" +"application, except when using DeadAGI. Otherwise, dialplan execution\n" +"will continue normally.\n" +" A locally executed AGI script will receive SIGHUP on hangup from the channel\n" +"except when using DeadAGI. This can be disabled by setting the AGISIGHUP channel\n" +"variable to \"no\" before executing the AGI application.\n" +" Using 'EAGI' provides enhanced AGI, with incoming audio available out of band\n" +"on file descriptor 3\n\n" +" Use the CLI command 'agi show' to list available agi commands\n" +" This application sets the following channel variable upon completion:\n" +" AGISTATUS The status of the attempt to the run the AGI script\n" +" text string, one of SUCCESS | FAILURE | NOTFOUND | HANGUP\n"; + +static int agidebug = 0; + +#define TONE_BLOCK_SIZE 200 + +/* Max time to connect to an AGI remote host */ +#define MAX_AGI_CONNECT 2000 + +#define AGI_PORT 4573 + +enum agi_result { + AGI_RESULT_SUCCESS, + AGI_RESULT_SUCCESS_FAST, + AGI_RESULT_SUCCESS_ASYNC, + AGI_RESULT_FAILURE, + AGI_RESULT_NOTFOUND, + AGI_RESULT_HANGUP, +}; + +static agi_command *find_command(char *cmds[], int exact); + +AST_THREADSTORAGE(agi_buf); +#define AGI_BUF_INITSIZE 256 + +int ast_agi_fdprintf(struct ast_channel *chan, int fd, char *fmt, ...) +{ + int res = 0; + va_list ap; + struct ast_str *buf; + + if (!(buf = ast_str_thread_get(&agi_buf, AGI_BUF_INITSIZE))) + return -1; + + va_start(ap, fmt); + res = ast_str_set_va(&buf, 0, fmt, ap); + va_end(ap); + + if (res == -1) { + ast_log(LOG_ERROR, "Out of memory\n"); + return -1; + } + + if (agidebug) { + if (chan) { + ast_verbose("<%s>AGI Tx >> %s", chan->name, buf->str); + } else { + ast_verbose("AGI Tx >> %s", buf->str); + } + } + + return ast_carefulwrite(fd, buf->str, buf->used, 100); +} + +/* linked list of AGI commands ready to be executed by Async AGI */ +struct agi_cmd { + char *cmd_buffer; + char *cmd_id; + AST_LIST_ENTRY(agi_cmd) entry; +}; + +static void free_agi_cmd(struct agi_cmd *cmd) +{ + ast_free(cmd->cmd_buffer); + ast_free(cmd->cmd_id); + ast_free(cmd); +} + +/* AGI datastore destructor */ +static void agi_destroy_commands_cb(void *data) +{ + struct agi_cmd *cmd; + AST_LIST_HEAD(, agi_cmd) *chan_cmds = data; + AST_LIST_LOCK(chan_cmds); + while ( (cmd = AST_LIST_REMOVE_HEAD(chan_cmds, entry)) ) { + free_agi_cmd(cmd); + } + AST_LIST_UNLOCK(chan_cmds); + AST_LIST_HEAD_DESTROY(chan_cmds); + ast_free(chan_cmds); +} + +/* channel datastore to keep the queue of AGI commands in the channel */ +static const struct ast_datastore_info agi_commands_datastore_info = { + .type = "AsyncAGI", + .destroy = agi_destroy_commands_cb +}; + +static const char mandescr_asyncagi[] = +"Description: Add an AGI command to the execute queue of the channel in Async AGI\n" +"Variables:\n" +" *Channel: Channel that is currently in Async AGI\n" +" *Command: Application to execute\n" +" CommandID: comand id. This will be sent back in CommandID header of AsyncAGI exec event notification\n" +"\n"; + +static struct agi_cmd *get_agi_cmd(struct ast_channel *chan) +{ + struct ast_datastore *store; + struct agi_cmd *cmd; + AST_LIST_HEAD(, agi_cmd) *agi_commands; + + ast_channel_lock(chan); + store = ast_channel_datastore_find(chan, &agi_commands_datastore_info, NULL); + ast_channel_unlock(chan); + if (!store) { + ast_log(LOG_ERROR, "Hu? datastore disappeared at Async AGI on Channel %s!\n", chan->name); + return NULL; + } + agi_commands = store->data; + AST_LIST_LOCK(agi_commands); + cmd = AST_LIST_REMOVE_HEAD(agi_commands, entry); + AST_LIST_UNLOCK(agi_commands); + return cmd; +} + +/* channel is locked when calling this one either from the CLI or manager thread */ +static int add_agi_cmd(struct ast_channel *chan, const char *cmd_buff, const char *cmd_id) +{ + struct ast_datastore *store; + struct agi_cmd *cmd; + AST_LIST_HEAD(, agi_cmd) *agi_commands; + + store = ast_channel_datastore_find(chan, &agi_commands_datastore_info, NULL); + if (!store) { + ast_log(LOG_WARNING, "Channel %s is not at Async AGI.\n", chan->name); + return -1; + } + agi_commands = store->data; + cmd = ast_calloc(1, sizeof(*cmd)); + if (!cmd) { + return -1; + } + cmd->cmd_buffer = ast_strdup(cmd_buff); + if (!cmd->cmd_buffer) { + ast_free(cmd); + return -1; + } + cmd->cmd_id = ast_strdup(cmd_id); + if (!cmd->cmd_id) { + ast_free(cmd->cmd_buffer); + ast_free(cmd); + return -1; + } + AST_LIST_LOCK(agi_commands); + AST_LIST_INSERT_TAIL(agi_commands, cmd, entry); + AST_LIST_UNLOCK(agi_commands); + return 0; +} + +static int add_to_agi(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + AST_LIST_HEAD(, agi_cmd) *agi_cmds_list; + + /* check if already on AGI */ + ast_channel_lock(chan); + datastore = ast_channel_datastore_find(chan, &agi_commands_datastore_info, NULL); + ast_channel_unlock(chan); + if (datastore) { + /* we already have an AGI datastore, let's just + return success */ + return 0; + } + + /* the channel has never been on Async AGI, + let's allocate it's datastore */ + datastore = ast_channel_datastore_alloc(&agi_commands_datastore_info, "AGI"); + if (!datastore) { + return -1; + } + agi_cmds_list = ast_calloc(1, sizeof(*agi_cmds_list)); + if (!agi_cmds_list) { + ast_log(LOG_ERROR, "Unable to allocate Async AGI commands list.\n"); + ast_channel_datastore_free(datastore); + return -1; + } + datastore->data = agi_cmds_list; + AST_LIST_HEAD_INIT(agi_cmds_list); + ast_channel_lock(chan); + ast_channel_datastore_add(chan, datastore); + ast_channel_unlock(chan); + return 0; +} + +/*! + * \brief CLI command to add applications to execute in Async AGI + * \param e + * \param cmd + * \param a + * + * \retval CLI_SUCCESS on success + * \retval NULL when init or tab completion is used +*/ +static char *handle_cli_agi_add_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_channel *chan; + switch (cmd) { + case CLI_INIT: + e->command = "agi exec"; + e->usage = "Usage: agi exec <channel name> <app and arguments> [id]\n" + " Add AGI command to the execute queue of the specified channel in Async AGI\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 2) + return ast_complete_channels(a->line, a->word, a->pos, a->n, 2); + return NULL; + } + + if (a->argc < 4) + return CLI_SHOWUSAGE; + chan = ast_get_channel_by_name_locked(a->argv[2]); + if (!chan) { + ast_log(LOG_WARNING, "Channel %s does not exists or cannot lock it\n", a->argv[2]); + return CLI_FAILURE; + } + if (add_agi_cmd(chan, a->argv[3], (a->argc > 4 ? a->argv[4] : ""))) { + ast_log(LOG_WARNING, "failed to add AGI command to queue of channel %s\n", chan->name); + ast_channel_unlock(chan); + return CLI_FAILURE; + } + ast_log(LOG_DEBUG, "Added AGI command to channel %s queue\n", chan->name); + ast_channel_unlock(chan); + return CLI_SUCCESS; +} + +/*! + * \brief Add a new command to execute by the Async AGI application + * \param s + * \param m + * + * It will append the application to the specified channel's queue + * if the channel is not inside Async AGI application it will return an error + * \retval 0 on success or incorrect use + * \retval 1 on failure to add the command ( most likely because the channel + * is not in Async AGI loop ) +*/ +static int action_add_agi_cmd(struct mansession *s, const struct message *m) +{ + const char *channel = astman_get_header(m, "Channel"); + const char *cmdbuff = astman_get_header(m, "Command"); + const char *cmdid = astman_get_header(m, "CommandID"); + struct ast_channel *chan; + char buf[256]; + if (ast_strlen_zero(channel) || ast_strlen_zero(cmdbuff)) { + astman_send_error(s, m, "Both, Channel and Command are *required*"); + return 0; + } + chan = ast_get_channel_by_name_locked(channel); + if (!chan) { + snprintf(buf, sizeof(buf), "Channel %s does not exists or cannot get its lock", channel); + astman_send_error(s, m, buf); + return 1; + } + if (add_agi_cmd(chan, cmdbuff, cmdid)) { + snprintf(buf, sizeof(buf), "Failed to add AGI command to channel %s queue", chan->name); + astman_send_error(s, m, buf); + ast_channel_unlock(chan); + return 1; + } + astman_send_ack(s, m, "Added AGI command to queue"); + ast_channel_unlock(chan); + return 0; +} + +static int agi_handle_command(struct ast_channel *chan, AGI *agi, char *buf, int dead); +static void setup_env(struct ast_channel *chan, char *request, int fd, int enhanced, int argc, char *argv[]); +static enum agi_result launch_asyncagi(struct ast_channel *chan, char *argv[], int *efd) +{ +/* This buffer sizes might cause truncation if the AGI command writes more data + than AGI_BUF_SIZE as result. But let's be serious, is there an AGI command + that writes a response larger than 1024 bytes?, I don't think so, most of + them are just result=blah stuff. However probably if GET VARIABLE is called + and the variable has large amount of data, that could be a problem. We could + make this buffers dynamic, but let's leave that as a second step. + + AMI_BUF_SIZE is twice AGI_BUF_SIZE just for the sake of choosing a safe + number. Some characters of AGI buf will be url encoded to be sent to manager + clients. An URL encoded character will take 3 bytes, but again, to cause + truncation more than about 70% of the AGI buffer should be URL encoded for + that to happen. Not likely at all. + + On the other hand. I wonder if read() could eventually return less data than + the amount already available in the pipe? If so, how to deal with that? + So far, my tests on Linux have not had any problems. + */ +#define AGI_BUF_SIZE 1024 +#define AMI_BUF_SIZE 2048 + struct ast_frame *f; + struct agi_cmd *cmd; + int res, fds[2]; + int timeout = 100; + char agi_buffer[AGI_BUF_SIZE + 1]; + char ami_buffer[AMI_BUF_SIZE]; + enum agi_result returnstatus = AGI_RESULT_SUCCESS_ASYNC; + AGI async_agi; + + if (efd) { + ast_log(LOG_WARNING, "Async AGI does not support Enhanced AGI yet\n"); + return AGI_RESULT_FAILURE; + } + + /* add AsyncAGI datastore to the channel */ + if (add_to_agi(chan)) { + ast_log(LOG_ERROR, "failed to start Async AGI on channel %s\n", chan->name); + return AGI_RESULT_FAILURE; + } + + /* this pipe allows us to create a "fake" AGI struct to use + the AGI commands */ + res = pipe(fds); + if (res) { + ast_log(LOG_ERROR, "failed to create Async AGI pipe\n"); + /* intentionally do not remove datastore, added with + add_to_agi(), from channel. It will be removed when + the channel is hung up anyways */ + return AGI_RESULT_FAILURE; + } + /* handlers will get the pipe write fd and we read the AGI responses + from the pipe read fd */ + async_agi.fd = fds[1]; + async_agi.ctrl = fds[1]; + async_agi.audio = -1; /* no audio support */ + async_agi.fast = 0; + + /* notify possible manager users of a new channel ready to + receive commands */ + setup_env(chan, "async", fds[1], 0, 0, NULL); + /* read the environment */ + res = read(fds[0], agi_buffer, AGI_BUF_SIZE); + if (!res) { + ast_log(LOG_ERROR, "failed to read from Async AGI pipe on channel %s\n", chan->name); + returnstatus = AGI_RESULT_FAILURE; + goto quit; + } + agi_buffer[res] = '\0'; + /* encode it and send it thru the manager so whoever is going to take + care of AGI commands on this channel can decide which AGI commands + to execute based on the setup info */ + ast_uri_encode(agi_buffer, ami_buffer, AMI_BUF_SIZE, 1); + manager_event(EVENT_FLAG_CALL, "AsyncAGI", "SubEvent: Start\r\nChannel: %s\r\nEnv: %s\r\n", chan->name, ami_buffer); + while (1) { + /* bail out if we need to hangup */ + if (ast_check_hangup(chan)) { + ast_log(LOG_DEBUG, "ast_check_hangup returned true on chan %s\n", chan->name); + break; + } + /* retrieve a command + (commands are added via the manager or the cli threads) */ + cmd = get_agi_cmd(chan); + if (cmd) { + /* OK, we have a command, let's call the + command handler. */ + res = agi_handle_command(chan, &async_agi, cmd->cmd_buffer, 0); + if ((res < 0) || (res == AST_PBX_KEEPALIVE)) { + free_agi_cmd(cmd); + break; + } + /* the command handler must have written to our fake + AGI struct fd (the pipe), let's read the response */ + res = read(fds[0], agi_buffer, AGI_BUF_SIZE); + if (!res) { + returnstatus = AGI_RESULT_FAILURE; + ast_log(LOG_ERROR, "failed to read from AsyncAGI pipe on channel %s\n", chan->name); + free_agi_cmd(cmd); + break; + } + /* we have a response, let's send the response thru the + manager. Include the CommandID if it was specified + when the command was added */ + agi_buffer[res] = '\0'; + ast_uri_encode(agi_buffer, ami_buffer, AMI_BUF_SIZE, 1); + if (ast_strlen_zero(cmd->cmd_id)) + manager_event(EVENT_FLAG_CALL, "AsyncAGI", "SubEvent: Exec\r\nChannel: %s\r\nResult: %s\r\n", chan->name, ami_buffer); + else + manager_event(EVENT_FLAG_CALL, "AsyncAGI", "SubEvent: Exec\r\nChannel: %s\r\nCommandID: %s\r\nResult: %s\r\n", chan->name, cmd->cmd_id, ami_buffer); + free_agi_cmd(cmd); + } else { + /* no command so far, wait a bit for a frame to read */ + res = ast_waitfor(chan, timeout); + if (res < 0) { + ast_log(LOG_DEBUG, "ast_waitfor returned <= 0 on chan %s\n", chan->name); + break; + } + if (res == 0) + continue; + f = ast_read(chan); + if (!f) { + ast_log(LOG_DEBUG, "No frame read on channel %s, going out ...\n", chan->name); + returnstatus = AGI_RESULT_HANGUP; + break; + } + /* is there any other frame we should care about + besides AST_CONTROL_HANGUP? */ + if (f->frametype == AST_FRAME_CONTROL && f->subclass == AST_CONTROL_HANGUP) { + ast_log(LOG_DEBUG, "Got HANGUP frame on channel %s, going out ...\n", chan->name); + ast_frfree(f); + break; + } + ast_frfree(f); + } + } +quit: + /* notify manager users this channel cannot be + controlled anymore by Async AGI */ + manager_event(EVENT_FLAG_CALL, "AsyncAGI", "SubEvent: End\r\nChannel: %s\r\n", chan->name); + + /* close the pipe */ + close(fds[0]); + close(fds[1]); + + /* intentionally don't get rid of the datastore. So commands can be + still in the queue in case AsyncAGI gets called again. + Datastore destructor will be called on channel destroy anyway */ + + return returnstatus; + +#undef AGI_BUF_SIZE +#undef AMI_BUF_SIZE +} + +/* launch_netscript: The fastagi handler. + FastAGI defaults to port 4573 */ +static enum agi_result launch_netscript(char *agiurl, char *argv[], int *fds, int *efd, int *opid) +{ + int s, flags, res, port = AGI_PORT; + struct pollfd pfds[1]; + char *host, *c, *script = ""; + struct sockaddr_in sin; + struct hostent *hp; + struct ast_hostent ahp; + + /* agiusl is "agi://host.domain[:port][/script/name]" */ + host = ast_strdupa(agiurl + 6); /* Remove agi:// */ + /* Strip off any script name */ + if ((c = strchr(host, '/'))) { + *c = '\0'; + c++; + script = c; + } + if ((c = strchr(host, ':'))) { + *c = '\0'; + c++; + port = atoi(c); + } + if (efd) { + ast_log(LOG_WARNING, "AGI URI's don't support Enhanced AGI yet\n"); + return -1; + } + if (!(hp = ast_gethostbyname(host, &ahp))) { + ast_log(LOG_WARNING, "Unable to locate host '%s'\n", host); + return -1; + } + if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + ast_log(LOG_WARNING, "Unable to create socket: %s\n", strerror(errno)); + return -1; + } + if ((flags = fcntl(s, F_GETFL)) < 0) { + ast_log(LOG_WARNING, "Fcntl(F_GETFL) failed: %s\n", strerror(errno)); + close(s); + return -1; + } + if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0) { + ast_log(LOG_WARNING, "Fnctl(F_SETFL) failed: %s\n", strerror(errno)); + close(s); + return -1; + } + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr)); + if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) && (errno != EINPROGRESS)) { + ast_log(LOG_WARNING, "Connect failed with unexpected error: %s\n", strerror(errno)); + close(s); + return AGI_RESULT_FAILURE; + } + + pfds[0].fd = s; + pfds[0].events = POLLOUT; + while ((res = poll(pfds, 1, MAX_AGI_CONNECT)) != 1) { + if (errno != EINTR) { + if (!res) { + ast_log(LOG_WARNING, "FastAGI connection to '%s' timed out after MAX_AGI_CONNECT (%d) milliseconds.\n", + agiurl, MAX_AGI_CONNECT); + } else + ast_log(LOG_WARNING, "Connect to '%s' failed: %s\n", agiurl, strerror(errno)); + close(s); + return AGI_RESULT_FAILURE; + } + } + + if (ast_agi_fdprintf(NULL, s, "agi_network: yes\n") < 0) { + if (errno != EINTR) { + ast_log(LOG_WARNING, "Connect to '%s' failed: %s\n", agiurl, strerror(errno)); + close(s); + return AGI_RESULT_FAILURE; + } + } + + /* If we have a script parameter, relay it to the fastagi server */ + /* Script parameters take the form of: AGI(agi://my.example.com/?extension=${EXTEN}) */ + if (!ast_strlen_zero(script)) + ast_agi_fdprintf(NULL, s, "agi_network_script: %s\n", script); + + ast_debug(4, "Wow, connected!\n"); + fds[0] = s; + fds[1] = s; + *opid = -1; + return AGI_RESULT_SUCCESS_FAST; +} + +static enum agi_result launch_script(struct ast_channel *chan, char *script, char *argv[], int *fds, int *efd, int *opid) +{ + char tmp[256]; + int pid, toast[2], fromast[2], audio[2], x, res; + sigset_t signal_set, old_set; + struct stat st; + + if (!strncasecmp(script, "agi://", 6)) + return launch_netscript(script, argv, fds, efd, opid); + if (!strncasecmp(script, "agi:async", sizeof("agi:async")-1)) + return launch_asyncagi(chan, argv, efd); + + if (script[0] != '/') { + snprintf(tmp, sizeof(tmp), "%s/%s", ast_config_AST_AGI_DIR, script); + script = tmp; + } + + /* Before even trying let's see if the file actually exists */ + if (stat(script, &st)) { + ast_log(LOG_WARNING, "Failed to execute '%s': File does not exist.\n", script); + return AGI_RESULT_NOTFOUND; + } + + if (pipe(toast)) { + ast_log(LOG_WARNING, "Unable to create toast pipe: %s\n",strerror(errno)); + return AGI_RESULT_FAILURE; + } + if (pipe(fromast)) { + ast_log(LOG_WARNING, "unable to create fromast pipe: %s\n", strerror(errno)); + close(toast[0]); + close(toast[1]); + return AGI_RESULT_FAILURE; + } + if (efd) { + if (pipe(audio)) { + ast_log(LOG_WARNING, "unable to create audio pipe: %s\n", strerror(errno)); + close(fromast[0]); + close(fromast[1]); + close(toast[0]); + close(toast[1]); + return AGI_RESULT_FAILURE; + } + res = fcntl(audio[1], F_GETFL); + if (res > -1) + res = fcntl(audio[1], F_SETFL, res | O_NONBLOCK); + if (res < 0) { + ast_log(LOG_WARNING, "unable to set audio pipe parameters: %s\n", strerror(errno)); + close(fromast[0]); + close(fromast[1]); + close(toast[0]); + close(toast[1]); + close(audio[0]); + close(audio[1]); + return AGI_RESULT_FAILURE; + } + } + + /* Block SIGHUP during the fork - prevents a race */ + sigfillset(&signal_set); + pthread_sigmask(SIG_BLOCK, &signal_set, &old_set); + if ((pid = fork()) < 0) { + ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno)); + pthread_sigmask(SIG_SETMASK, &old_set, NULL); + return AGI_RESULT_FAILURE; + } + if (!pid) { + /* Pass paths to AGI via environmental variables */ + setenv("AST_CONFIG_DIR", ast_config_AST_CONFIG_DIR, 1); + setenv("AST_CONFIG_FILE", ast_config_AST_CONFIG_FILE, 1); + setenv("AST_MODULE_DIR", ast_config_AST_MODULE_DIR, 1); + setenv("AST_SPOOL_DIR", ast_config_AST_SPOOL_DIR, 1); + setenv("AST_MONITOR_DIR", ast_config_AST_MONITOR_DIR, 1); + setenv("AST_VAR_DIR", ast_config_AST_VAR_DIR, 1); + setenv("AST_DATA_DIR", ast_config_AST_DATA_DIR, 1); + setenv("AST_LOG_DIR", ast_config_AST_LOG_DIR, 1); + setenv("AST_AGI_DIR", ast_config_AST_AGI_DIR, 1); + setenv("AST_KEY_DIR", ast_config_AST_KEY_DIR, 1); + setenv("AST_RUN_DIR", ast_config_AST_RUN_DIR, 1); + + /* Don't run AGI scripts with realtime priority -- it causes audio stutter */ + ast_set_priority(0); + + /* Redirect stdin and out, provide enhanced audio channel if desired */ + dup2(fromast[0], STDIN_FILENO); + dup2(toast[1], STDOUT_FILENO); + if (efd) + dup2(audio[0], STDERR_FILENO + 1); + else + close(STDERR_FILENO + 1); + + /* Before we unblock our signals, return our trapped signals back to the defaults */ + signal(SIGHUP, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGURG, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + signal(SIGXFSZ, SIG_DFL); + + /* unblock important signal handlers */ + if (pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL)) { + ast_log(LOG_WARNING, "unable to unblock signals for AGI script: %s\n", strerror(errno)); + _exit(1); + } + + /* Close everything but stdin/out/error */ + for (x = STDERR_FILENO + 2; x < 1024; x++) + close(x); + + /* Execute script */ + /* XXX argv should be deprecated in favor of passing agi_argX paramaters */ + execv(script, argv); + /* Can't use ast_log since FD's are closed */ + fprintf(stdout, "verbose \"Failed to execute '%s': %s\" 2\n", script, strerror(errno)); + fflush(stdout); + _exit(1); + } + pthread_sigmask(SIG_SETMASK, &old_set, NULL); + ast_verb(3, "Launched AGI Script %s\n", script); + fds[0] = toast[0]; + fds[1] = fromast[1]; + if (efd) + *efd = audio[1]; + /* close what we're not using in the parent */ + close(toast[1]); + close(fromast[0]); + + if (efd) + close(audio[0]); + + *opid = pid; + return AGI_RESULT_SUCCESS; +} + +static void setup_env(struct ast_channel *chan, char *request, int fd, int enhanced, int argc, char *argv[]) +{ + int count; + + /* Print initial environment, with agi_request always being the first + thing */ + ast_agi_fdprintf(chan, fd, "agi_request: %s\n", request); + ast_agi_fdprintf(chan, fd, "agi_channel: %s\n", chan->name); + ast_agi_fdprintf(chan, fd, "agi_language: %s\n", chan->language); + ast_agi_fdprintf(chan, fd, "agi_type: %s\n", chan->tech->type); + ast_agi_fdprintf(chan, fd, "agi_uniqueid: %s\n", chan->uniqueid); + ast_agi_fdprintf(chan, fd, "agi_version: %s\n", ast_get_version()); + + /* ANI/DNIS */ + ast_agi_fdprintf(chan, fd, "agi_callerid: %s\n", S_OR(chan->cid.cid_num, "unknown")); + ast_agi_fdprintf(chan, fd, "agi_calleridname: %s\n", S_OR(chan->cid.cid_name, "unknown")); + ast_agi_fdprintf(chan, fd, "agi_callingpres: %d\n", chan->cid.cid_pres); + ast_agi_fdprintf(chan, fd, "agi_callingani2: %d\n", chan->cid.cid_ani2); + ast_agi_fdprintf(chan, fd, "agi_callington: %d\n", chan->cid.cid_ton); + ast_agi_fdprintf(chan, fd, "agi_callingtns: %d\n", chan->cid.cid_tns); + ast_agi_fdprintf(chan, fd, "agi_dnid: %s\n", S_OR(chan->cid.cid_dnid, "unknown")); + ast_agi_fdprintf(chan, fd, "agi_rdnis: %s\n", S_OR(chan->cid.cid_rdnis, "unknown")); + + /* Context information */ + ast_agi_fdprintf(chan, fd, "agi_context: %s\n", chan->context); + ast_agi_fdprintf(chan, fd, "agi_extension: %s\n", chan->exten); + ast_agi_fdprintf(chan, fd, "agi_priority: %d\n", chan->priority); + ast_agi_fdprintf(chan, fd, "agi_enhanced: %s\n", enhanced ? "1.0" : "0.0"); + + /* User information */ + ast_agi_fdprintf(chan, fd, "agi_accountcode: %s\n", chan->accountcode ? chan->accountcode : ""); + ast_agi_fdprintf(chan, fd, "agi_threadid: %ld\n", (long)pthread_self()); + + /* Send any parameters to the fastagi server that have been passed via the agi application */ + /* Agi application paramaters take the form of: AGI(/path/to/example/script|${EXTEN}) */ + for(count = 1; count < argc; count++) + ast_agi_fdprintf(chan, fd, "agi_arg_%d: %s\n", count, argv[count]); + + /* End with empty return */ + ast_agi_fdprintf(chan, fd, "\n"); +} + +static int handle_answer(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res = 0; + + /* Answer the channel */ + if (chan->_state != AST_STATE_UP) + res = ast_answer(chan); + + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", res); + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; +} + +static int handle_waitfordigit(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res, to; + + if (argc != 4) + return RESULT_SHOWUSAGE; + if (sscanf(argv[3], "%d", &to) != 1) + return RESULT_SHOWUSAGE; + res = ast_waitfordigit_full(chan, to, agi->audio, agi->ctrl); + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", res); + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; +} + +static int handle_sendtext(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res; + + if (argc != 3) + return RESULT_SHOWUSAGE; + + /* At the moment, the parser (perhaps broken) returns with + the last argument PLUS the newline at the end of the input + buffer. This probably needs to be fixed, but I wont do that + because other stuff may break as a result. The right way + would probably be to strip off the trailing newline before + parsing, then here, add a newline at the end of the string + before sending it to ast_sendtext --DUDE */ + res = ast_sendtext(chan, argv[2]); + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", res); + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; +} + +static int handle_recvchar(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res; + + if (argc != 3) + return RESULT_SHOWUSAGE; + + res = ast_recvchar(chan,atoi(argv[2])); + if (res == 0) { + ast_agi_fdprintf(chan, agi->fd, "200 result=%d (timeout)\n", res); + return RESULT_SUCCESS; + } + if (res > 0) { + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", res); + return RESULT_SUCCESS; + } + else { + ast_agi_fdprintf(chan, agi->fd, "200 result=%d (hangup)\n", res); + return RESULT_FAILURE; + } +} + +static int handle_recvtext(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + char *buf; + + if (argc != 3) + return RESULT_SHOWUSAGE; + + buf = ast_recvtext(chan,atoi(argv[2])); + if (buf) { + ast_agi_fdprintf(chan, agi->fd, "200 result=1 (%s)\n", buf); + ast_free(buf); + } else { + ast_agi_fdprintf(chan, agi->fd, "200 result=-1\n"); + } + return RESULT_SUCCESS; +} + +static int handle_tddmode(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res, x; + + if (argc != 3) + return RESULT_SHOWUSAGE; + + if (!strncasecmp(argv[2],"on",2)) + x = 1; + else + x = 0; + if (!strncasecmp(argv[2],"mate",4)) + x = 2; + if (!strncasecmp(argv[2],"tdd",3)) + x = 1; + res = ast_channel_setoption(chan, AST_OPTION_TDD, &x, sizeof(char), 0); + if (res != RESULT_SUCCESS) + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + else + ast_agi_fdprintf(chan, agi->fd, "200 result=1\n"); + return RESULT_SUCCESS; +} + +static int handle_sendimage(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res; + + if (argc != 3) + return RESULT_SHOWUSAGE; + + res = ast_send_image(chan, argv[2]); + if (!ast_check_hangup(chan)) + res = 0; + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", res); + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; +} + +static int handle_controlstreamfile(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res = 0, skipms = 3000; + char *fwd = NULL, *rev = NULL, *pause = NULL, *stop = NULL; + + if (argc < 5 || argc > 9) + return RESULT_SHOWUSAGE; + + if (!ast_strlen_zero(argv[4])) + stop = argv[4]; + else + stop = NULL; + + if ((argc > 5) && (sscanf(argv[5], "%d", &skipms) != 1)) + return RESULT_SHOWUSAGE; + + if (argc > 6 && !ast_strlen_zero(argv[6])) + fwd = argv[6]; + else + fwd = "#"; + + if (argc > 7 && !ast_strlen_zero(argv[7])) + rev = argv[7]; + else + rev = "*"; + + if (argc > 8 && !ast_strlen_zero(argv[8])) + pause = argv[8]; + else + pause = NULL; + + res = ast_control_streamfile(chan, argv[3], fwd, rev, stop, pause, NULL, skipms, NULL); + + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", res); + + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; +} + +static int handle_streamfile(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res, vres; + struct ast_filestream *fs, *vfs; + long sample_offset = 0, max_length; + char *edigits = ""; + + if (argc < 4 || argc > 5) + return RESULT_SHOWUSAGE; + + if (argv[3]) + edigits = argv[3]; + + if ((argc > 4) && (sscanf(argv[4], "%ld", &sample_offset) != 1)) + return RESULT_SHOWUSAGE; + + if (!(fs = ast_openstream(chan, argv[2], chan->language))) { + ast_agi_fdprintf(chan, agi->fd, "200 result=%d endpos=%ld\n", 0, sample_offset); + return RESULT_SUCCESS; + } + + if ((vfs = ast_openvstream(chan, argv[2], chan->language))) + ast_debug(1, "Ooh, found a video stream, too\n"); + + ast_verb(3, "Playing '%s' (escape_digits=%s) (sample_offset %ld)\n", argv[2], edigits, sample_offset); + + ast_seekstream(fs, 0, SEEK_END); + max_length = ast_tellstream(fs); + ast_seekstream(fs, sample_offset, SEEK_SET); + res = ast_applystream(chan, fs); + if (vfs) + vres = ast_applystream(chan, vfs); + ast_playstream(fs); + if (vfs) + ast_playstream(vfs); + + res = ast_waitstream_full(chan, argv[3], agi->audio, agi->ctrl); + /* this is to check for if ast_waitstream closed the stream, we probably are at + * the end of the stream, return that amount, else check for the amount */ + sample_offset = (chan->stream) ? ast_tellstream(fs) : max_length; + ast_stopstream(chan); + if (res == 1) { + /* Stop this command, don't print a result line, as there is a new command */ + return RESULT_SUCCESS; + } + ast_agi_fdprintf(chan, agi->fd, "200 result=%d endpos=%ld\n", res, sample_offset); + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; +} + +/* get option - really similar to the handle_streamfile, but with a timeout */ +static int handle_getoption(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res, vres; + struct ast_filestream *fs, *vfs; + long sample_offset = 0, max_length; + int timeout = 0; + char *edigits = ""; + + if ( argc < 4 || argc > 5 ) + return RESULT_SHOWUSAGE; + + if ( argv[3] ) + edigits = argv[3]; + + if ( argc == 5 ) + timeout = atoi(argv[4]); + else if (chan->pbx->dtimeout) { + /* by default dtimeout is set to 5sec */ + timeout = chan->pbx->dtimeout * 1000; /* in msec */ + } + + if (!(fs = ast_openstream(chan, argv[2], chan->language))) { + ast_agi_fdprintf(chan, agi->fd, "200 result=%d endpos=%ld\n", 0, sample_offset); + ast_log(LOG_WARNING, "Unable to open %s\n", argv[2]); + return RESULT_SUCCESS; + } + + if ((vfs = ast_openvstream(chan, argv[2], chan->language))) + ast_debug(1, "Ooh, found a video stream, too\n"); + + ast_verb(3, "Playing '%s' (escape_digits=%s) (timeout %d)\n", argv[2], edigits, timeout); + + ast_seekstream(fs, 0, SEEK_END); + max_length = ast_tellstream(fs); + ast_seekstream(fs, sample_offset, SEEK_SET); + res = ast_applystream(chan, fs); + if (vfs) + vres = ast_applystream(chan, vfs); + ast_playstream(fs); + if (vfs) + ast_playstream(vfs); + + res = ast_waitstream_full(chan, argv[3], agi->audio, agi->ctrl); + /* this is to check for if ast_waitstream closed the stream, we probably are at + * the end of the stream, return that amount, else check for the amount */ + sample_offset = (chan->stream)?ast_tellstream(fs):max_length; + ast_stopstream(chan); + if (res == 1) { + /* Stop this command, don't print a result line, as there is a new command */ + return RESULT_SUCCESS; + } + + /* If the user didnt press a key, wait for digitTimeout*/ + if (res == 0 ) { + res = ast_waitfordigit_full(chan, timeout, agi->audio, agi->ctrl); + /* Make sure the new result is in the escape digits of the GET OPTION */ + if ( !strchr(edigits,res) ) + res=0; + } + + ast_agi_fdprintf(chan, agi->fd, "200 result=%d endpos=%ld\n", res, sample_offset); + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; +} + + + + +/*--- handle_saynumber: Say number in various language syntaxes ---*/ +/* While waiting, we're sending a NULL. */ +static int handle_saynumber(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res, num; + + if (argc < 4 || argc > 5) + return RESULT_SHOWUSAGE; + if (sscanf(argv[2], "%d", &num) != 1) + return RESULT_SHOWUSAGE; + res = ast_say_number_full(chan, num, argv[3], chan->language, argc > 4 ? argv[4] : NULL, agi->audio, agi->ctrl); + if (res == 1) + return RESULT_SUCCESS; + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", res); + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; +} + +static int handle_saydigits(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res, num; + + if (argc != 4) + return RESULT_SHOWUSAGE; + if (sscanf(argv[2], "%d", &num) != 1) + return RESULT_SHOWUSAGE; + + res = ast_say_digit_str_full(chan, argv[2], argv[3], chan->language, agi->audio, agi->ctrl); + if (res == 1) /* New command */ + return RESULT_SUCCESS; + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", res); + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; +} + +static int handle_sayalpha(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res; + + if (argc != 4) + return RESULT_SHOWUSAGE; + + res = ast_say_character_str_full(chan, argv[2], argv[3], chan->language, agi->audio, agi->ctrl); + if (res == 1) /* New command */ + return RESULT_SUCCESS; + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", res); + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; +} + +static int handle_saydate(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res, num; + + if (argc != 4) + return RESULT_SHOWUSAGE; + if (sscanf(argv[2], "%d", &num) != 1) + return RESULT_SHOWUSAGE; + res = ast_say_date(chan, num, argv[3], chan->language); + if (res == 1) + return RESULT_SUCCESS; + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", res); + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; +} + +static int handle_saytime(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res, num; + + if (argc != 4) + return RESULT_SHOWUSAGE; + if (sscanf(argv[2], "%d", &num) != 1) + return RESULT_SHOWUSAGE; + res = ast_say_time(chan, num, argv[3], chan->language); + if (res == 1) + return RESULT_SUCCESS; + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", res); + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; +} + +static int handle_saydatetime(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res = 0; + time_t unixtime; + char *format, *zone = NULL; + + if (argc < 4) + return RESULT_SHOWUSAGE; + + if (argc > 4) { + format = argv[4]; + } else { + /* XXX this doesn't belong here, but in the 'say' module */ + if (!strcasecmp(chan->language, "de")) { + format = "A dBY HMS"; + } else { + format = "ABdY 'digits/at' IMp"; + } + } + + if (argc > 5 && !ast_strlen_zero(argv[5])) + zone = argv[5]; + + if (ast_get_time_t(argv[2], &unixtime, 0, NULL)) + return RESULT_SHOWUSAGE; + + res = ast_say_date_with_format(chan, unixtime, argv[3], chan->language, format, zone); + if (res == 1) + return RESULT_SUCCESS; + + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", res); + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; +} + +static int handle_sayphonetic(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res; + + if (argc != 4) + return RESULT_SHOWUSAGE; + + res = ast_say_phonetic_str_full(chan, argv[2], argv[3], chan->language, agi->audio, agi->ctrl); + if (res == 1) /* New command */ + return RESULT_SUCCESS; + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", res); + return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE; +} + +static int handle_getdata(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int res, max, timeout; + char data[1024]; + + if (argc < 3) + return RESULT_SHOWUSAGE; + if (argc >= 4) + timeout = atoi(argv[3]); + else + timeout = 0; + if (argc >= 5) + max = atoi(argv[4]); + else + max = 1024; + res = ast_app_getdata_full(chan, argv[2], data, max, timeout, agi->audio, agi->ctrl); + if (res == 2) /* New command */ + return RESULT_SUCCESS; + else if (res == 1) + ast_agi_fdprintf(chan, agi->fd, "200 result=%s (timeout)\n", data); + else if (res < 0 ) + ast_agi_fdprintf(chan, agi->fd, "200 result=-1\n"); + else + ast_agi_fdprintf(chan, agi->fd, "200 result=%s\n", data); + return RESULT_SUCCESS; +} + +static int handle_setcontext(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + + if (argc != 3) + return RESULT_SHOWUSAGE; + ast_copy_string(chan->context, argv[2], sizeof(chan->context)); + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return RESULT_SUCCESS; +} + +static int handle_setextension(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + if (argc != 3) + return RESULT_SHOWUSAGE; + ast_copy_string(chan->exten, argv[2], sizeof(chan->exten)); + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return RESULT_SUCCESS; +} + +static int handle_setpriority(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + int pri; + + if (argc != 3) + return RESULT_SHOWUSAGE; + + if (sscanf(argv[2], "%d", &pri) != 1) { + if ((pri = ast_findlabel_extension(chan, chan->context, chan->exten, argv[2], chan->cid.cid_num)) < 1) + return RESULT_SHOWUSAGE; + } + + ast_explicit_goto(chan, NULL, NULL, pri); + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return RESULT_SUCCESS; +} + +static int handle_recordfile(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + struct ast_filestream *fs; + struct ast_frame *f; + struct timeval start; + long sample_offset = 0; + int res = 0; + int ms; + + struct ast_dsp *sildet=NULL; /* silence detector dsp */ + int totalsilence = 0; + int dspsilence = 0; + int silence = 0; /* amount of silence to allow */ + int gotsilence = 0; /* did we timeout for silence? */ + char *silencestr=NULL; + int rfmt=0; + + + /* XXX EAGI FIXME XXX */ + + if (argc < 6) + return RESULT_SHOWUSAGE; + if (sscanf(argv[5], "%d", &ms) != 1) + return RESULT_SHOWUSAGE; + + if (argc > 6) + silencestr = strchr(argv[6],'s'); + if ((argc > 7) && (!silencestr)) + silencestr = strchr(argv[7],'s'); + if ((argc > 8) && (!silencestr)) + silencestr = strchr(argv[8],'s'); + + if (silencestr) { + if (strlen(silencestr) > 2) { + if ((silencestr[0] == 's') && (silencestr[1] == '=')) { + silencestr++; + silencestr++; + if (silencestr) + silence = atoi(silencestr); + if (silence > 0) + silence *= 1000; + } + } + } + + if (silence > 0) { + rfmt = chan->readformat; + res = ast_set_read_format(chan, AST_FORMAT_SLINEAR); + if (res < 0) { + ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n"); + return -1; + } + sildet = ast_dsp_new(); + if (!sildet) { + ast_log(LOG_WARNING, "Unable to create silence detector :(\n"); + return -1; + } + ast_dsp_set_threshold(sildet, 256); + } + + /* backward compatibility, if no offset given, arg[6] would have been + * caught below and taken to be a beep, else if it is a digit then it is a + * offset */ + if ((argc >6) && (sscanf(argv[6], "%ld", &sample_offset) != 1) && (!strchr(argv[6], '='))) + res = ast_streamfile(chan, "beep", chan->language); + + if ((argc > 7) && (!strchr(argv[7], '='))) + res = ast_streamfile(chan, "beep", chan->language); + + if (!res) + res = ast_waitstream(chan, argv[4]); + if (res) { + ast_agi_fdprintf(chan, agi->fd, "200 result=%d (randomerror) endpos=%ld\n", res, sample_offset); + } else { + fs = ast_writefile(argv[2], argv[3], NULL, O_CREAT | O_WRONLY | (sample_offset ? O_APPEND : 0), 0, AST_FILE_MODE); + if (!fs) { + res = -1; + ast_agi_fdprintf(chan, agi->fd, "200 result=%d (writefile)\n", res); + if (sildet) + ast_dsp_free(sildet); + return RESULT_FAILURE; + } + + /* Request a video update */ + ast_indicate(chan, AST_CONTROL_VIDUPDATE); + + chan->stream = fs; + ast_applystream(chan,fs); + /* really should have checks */ + ast_seekstream(fs, sample_offset, SEEK_SET); + ast_truncstream(fs); + + start = ast_tvnow(); + while ((ms < 0) || ast_tvdiff_ms(ast_tvnow(), start) < ms) { + res = ast_waitfor(chan, -1); + if (res < 0) { + ast_closestream(fs); + ast_agi_fdprintf(chan, agi->fd, "200 result=%d (waitfor) endpos=%ld\n", res,sample_offset); + if (sildet) + ast_dsp_free(sildet); + return RESULT_FAILURE; + } + f = ast_read(chan); + if (!f) { + ast_agi_fdprintf(chan, agi->fd, "200 result=%d (hangup) endpos=%ld\n", -1, sample_offset); + ast_closestream(fs); + if (sildet) + ast_dsp_free(sildet); + return RESULT_FAILURE; + } + switch(f->frametype) { + case AST_FRAME_DTMF: + if (strchr(argv[4], f->subclass)) { + /* This is an interrupting chracter, so rewind to chop off any small + amount of DTMF that may have been recorded + */ + ast_stream_rewind(fs, 200); + ast_truncstream(fs); + sample_offset = ast_tellstream(fs); + ast_agi_fdprintf(chan, agi->fd, "200 result=%d (dtmf) endpos=%ld\n", f->subclass, sample_offset); + ast_closestream(fs); + ast_frfree(f); + if (sildet) + ast_dsp_free(sildet); + return RESULT_SUCCESS; + } + break; + case AST_FRAME_VOICE: + ast_writestream(fs, f); + /* this is a safe place to check progress since we know that fs + * is valid after a write, and it will then have our current + * location */ + sample_offset = ast_tellstream(fs); + if (silence > 0) { + dspsilence = 0; + ast_dsp_silence(sildet, f, &dspsilence); + if (dspsilence) { + totalsilence = dspsilence; + } else { + totalsilence = 0; + } + if (totalsilence > silence) { + /* Ended happily with silence */ + gotsilence = 1; + break; + } + } + break; + case AST_FRAME_VIDEO: + ast_writestream(fs, f); + default: + /* Ignore all other frames */ + break; + } + ast_frfree(f); + if (gotsilence) + break; + } + + if (gotsilence) { + ast_stream_rewind(fs, silence-1000); + ast_truncstream(fs); + sample_offset = ast_tellstream(fs); + } + ast_agi_fdprintf(chan, agi->fd, "200 result=%d (timeout) endpos=%ld\n", res, sample_offset); + ast_closestream(fs); + } + + if (silence > 0) { + res = ast_set_read_format(chan, rfmt); + if (res) + ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", chan->name); + ast_dsp_free(sildet); + } + return RESULT_SUCCESS; +} + +static int handle_autohangup(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + int timeout; + + if (argc != 3) + return RESULT_SHOWUSAGE; + if (sscanf(argv[2], "%d", &timeout) != 1) + return RESULT_SHOWUSAGE; + if (timeout < 0) + timeout = 0; + if (timeout) + chan->whentohangup = time(NULL) + timeout; + else + chan->whentohangup = 0; + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return RESULT_SUCCESS; +} + +static int handle_hangup(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + struct ast_channel *c; + + if (argc == 1) { + /* no argument: hangup the current channel */ + ast_softhangup(chan,AST_SOFTHANGUP_EXPLICIT); + ast_agi_fdprintf(chan, agi->fd, "200 result=1\n"); + return RESULT_SUCCESS; + } else if (argc == 2) { + /* one argument: look for info on the specified channel */ + c = ast_get_channel_by_name_locked(argv[1]); + if (c) { + /* we have a matching channel */ + ast_softhangup(c,AST_SOFTHANGUP_EXPLICIT); + ast_agi_fdprintf(chan, agi->fd, "200 result=1\n"); + ast_channel_unlock(c); + return RESULT_SUCCESS; + } + /* if we get this far no channel name matched the argument given */ + ast_agi_fdprintf(chan, agi->fd, "200 result=-1\n"); + return RESULT_SUCCESS; + } else { + return RESULT_SHOWUSAGE; + } +} + +static int handle_exec(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + int res; + struct ast_app *app; + + if (argc < 2) + return RESULT_SHOWUSAGE; + + ast_verb(3, "AGI Script Executing Application: (%s) Options: (%s)\n", argv[1], argv[2]); + + if ((app = pbx_findapp(argv[1]))) { + res = pbx_exec(chan, app, argv[2]); + } else { + ast_log(LOG_WARNING, "Could not find application (%s)\n", argv[1]); + res = -2; + } + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", res); + + /* Even though this is wrong, users are depending upon this result. */ + return res; +} + +static int handle_setcallerid(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + char tmp[256]=""; + char *l = NULL, *n = NULL; + + if (argv[2]) { + ast_copy_string(tmp, argv[2], sizeof(tmp)); + ast_callerid_parse(tmp, &n, &l); + if (l) + ast_shrink_phone_number(l); + else + l = ""; + if (!n) + n = ""; + ast_set_callerid(chan, l, n, NULL); + } + + ast_agi_fdprintf(chan, agi->fd, "200 result=1\n"); + return RESULT_SUCCESS; +} + +static int handle_channelstatus(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + struct ast_channel *c; + if (argc == 2) { + /* no argument: supply info on the current channel */ + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", chan->_state); + return RESULT_SUCCESS; + } else if (argc == 3) { + /* one argument: look for info on the specified channel */ + c = ast_get_channel_by_name_locked(argv[2]); + if (c) { + ast_agi_fdprintf(chan, agi->fd, "200 result=%d\n", c->_state); + ast_channel_unlock(c); + return RESULT_SUCCESS; + } + /* if we get this far no channel name matched the argument given */ + ast_agi_fdprintf(chan, agi->fd, "200 result=-1\n"); + return RESULT_SUCCESS; + } else { + return RESULT_SHOWUSAGE; + } +} + +static int handle_setvariable(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + if (argv[3]) + pbx_builtin_setvar_helper(chan, argv[2], argv[3]); + + ast_agi_fdprintf(chan, agi->fd, "200 result=1\n"); + return RESULT_SUCCESS; +} + +static int handle_getvariable(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + char *ret; + char tempstr[1024]; + + if (argc != 3) + return RESULT_SHOWUSAGE; + + /* check if we want to execute an ast_custom_function */ + if (!ast_strlen_zero(argv[2]) && (argv[2][strlen(argv[2]) - 1] == ')')) { + ret = ast_func_read(chan, argv[2], tempstr, sizeof(tempstr)) ? NULL : tempstr; + } else { + pbx_retrieve_variable(chan, argv[2], &ret, tempstr, sizeof(tempstr), NULL); + } + + if (ret) + ast_agi_fdprintf(chan, agi->fd, "200 result=1 (%s)\n", ret); + else + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + + return RESULT_SUCCESS; +} + +static int handle_getvariablefull(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + char tmp[4096]; + struct ast_channel *chan2=NULL; + + if ((argc != 4) && (argc != 5)) + return RESULT_SHOWUSAGE; + if (argc == 5) { + chan2 = ast_get_channel_by_name_locked(argv[4]); + } else { + chan2 = chan; + } + if (chan2) { + pbx_substitute_variables_helper(chan2, argv[3], tmp, sizeof(tmp) - 1); + ast_agi_fdprintf(chan, agi->fd, "200 result=1 (%s)\n", tmp); + } else { + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + } + if (chan2 && (chan2 != chan)) + ast_channel_unlock(chan2); + return RESULT_SUCCESS; +} + +static int handle_verbose(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + int level = 0; + char *prefix; + + if (argc < 2) + return RESULT_SHOWUSAGE; + + if (argv[2]) + sscanf(argv[2], "%d", &level); + + switch (level) { + case 4: + prefix = VERBOSE_PREFIX_4; + break; + case 3: + prefix = VERBOSE_PREFIX_3; + break; + case 2: + prefix = VERBOSE_PREFIX_2; + break; + case 1: + default: + prefix = VERBOSE_PREFIX_1; + break; + } + + if (level <= option_verbose) + ast_verbose("%s %s: %s\n", prefix, chan->data, argv[1]); + + ast_agi_fdprintf(chan, agi->fd, "200 result=1\n"); + + return RESULT_SUCCESS; +} + +static int handle_dbget(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + int res; + char tmp[256]; + + if (argc != 4) + return RESULT_SHOWUSAGE; + res = ast_db_get(argv[2], argv[3], tmp, sizeof(tmp)); + if (res) + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + else + ast_agi_fdprintf(chan, agi->fd, "200 result=1 (%s)\n", tmp); + + return RESULT_SUCCESS; +} + +static int handle_dbput(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + int res; + + if (argc != 5) + return RESULT_SHOWUSAGE; + res = ast_db_put(argv[2], argv[3], argv[4]); + ast_agi_fdprintf(chan, agi->fd, "200 result=%c\n", res ? '0' : '1'); + return RESULT_SUCCESS; +} + +static int handle_dbdel(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + int res; + + if (argc != 4) + return RESULT_SHOWUSAGE; + res = ast_db_del(argv[2], argv[3]); + ast_agi_fdprintf(chan, agi->fd, "200 result=%c\n", res ? '0' : '1'); + return RESULT_SUCCESS; +} + +static int handle_dbdeltree(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + int res; + + if ((argc < 3) || (argc > 4)) + return RESULT_SHOWUSAGE; + if (argc == 4) + res = ast_db_deltree(argv[2], argv[3]); + else + res = ast_db_deltree(argv[2], NULL); + + ast_agi_fdprintf(chan, agi->fd, "200 result=%c\n", res ? '0' : '1'); + return RESULT_SUCCESS; +} + +static char *handle_cli_agi_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "agi debug [off]"; + e->usage = + "Usage: agi debug [off]\n" + " Enables/disables dumping of AGI transactions for\n" + " debugging purposes.\n"; + return NULL; + + case CLI_GENERATE: + return NULL; + } + if (a->argc < e->args - 1 || a->argc > e->args ) + return CLI_SHOWUSAGE; + if (a->argc == e->args - 1) { + agidebug = 1; + } else { + if (strncasecmp(a->argv[e->args - 1], "off", 3) == 0) { + agidebug = 0; + } else { + return CLI_SHOWUSAGE; + } + } + ast_cli(a->fd, "AGI Debugging %sabled\n", agidebug ? "En" : "Dis"); + return CLI_SUCCESS; +} + +static int handle_noop(struct ast_channel *chan, AGI *agi, int arg, char *argv[]) +{ + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return RESULT_SUCCESS; +} + +static int handle_setmusic(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + if (!strncasecmp(argv[2], "on", 2)) + ast_moh_start(chan, argc > 3 ? argv[3] : NULL, NULL); + else if (!strncasecmp(argv[2], "off", 3)) + ast_moh_stop(chan); + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return RESULT_SUCCESS; +} + +static int handle_speechcreate(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + /* If a structure already exists, return an error */ + if (agi->speech) { + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return RESULT_SUCCESS; + } + + if ((agi->speech = ast_speech_new(argv[2], AST_FORMAT_SLINEAR))) + ast_agi_fdprintf(chan, agi->fd, "200 result=1\n"); + else + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + + return RESULT_SUCCESS; +} + +static int handle_speechset(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + /* Check for minimum arguments */ + if (argc != 3) + return RESULT_SHOWUSAGE; + + /* Check to make sure speech structure exists */ + if (!agi->speech) { + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return RESULT_SUCCESS; + } + + ast_speech_change(agi->speech, argv[2], argv[3]); + ast_agi_fdprintf(chan, agi->fd, "200 result=1\n"); + + return RESULT_SUCCESS; +} + +static int handle_speechdestroy(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + if (agi->speech) { + ast_speech_destroy(agi->speech); + agi->speech = NULL; + ast_agi_fdprintf(chan, agi->fd, "200 result=1\n"); + } else { + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + } + + return RESULT_SUCCESS; +} + +static int handle_speechloadgrammar(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + if (argc != 5) + return RESULT_SHOWUSAGE; + + if (!agi->speech) { + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return RESULT_SUCCESS; + } + + if (ast_speech_grammar_load(agi->speech, argv[3], argv[4])) + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + else + ast_agi_fdprintf(chan, agi->fd, "200 result=1\n"); + + return RESULT_SUCCESS; +} + +static int handle_speechunloadgrammar(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + if (argc != 4) + return RESULT_SHOWUSAGE; + + if (!agi->speech) { + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return RESULT_SUCCESS; + } + + if (ast_speech_grammar_unload(agi->speech, argv[3])) + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + else + ast_agi_fdprintf(chan, agi->fd, "200 result=1\n"); + + return RESULT_SUCCESS; +} + +static int handle_speechactivategrammar(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + if (argc != 4) + return RESULT_SHOWUSAGE; + + if (!agi->speech) { + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return RESULT_SUCCESS; + } + + if (ast_speech_grammar_activate(agi->speech, argv[3])) + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + else + ast_agi_fdprintf(chan, agi->fd, "200 result=1\n"); + + return RESULT_SUCCESS; +} + +static int handle_speechdeactivategrammar(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + if (argc != 4) + return RESULT_SHOWUSAGE; + + if (!agi->speech) { + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return RESULT_SUCCESS; + } + + if (ast_speech_grammar_deactivate(agi->speech, argv[3])) + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + else + ast_agi_fdprintf(chan, agi->fd, "200 result=1\n"); + + return RESULT_SUCCESS; +} + +static int speech_streamfile(struct ast_channel *chan, const char *filename, const char *preflang, int offset) +{ + struct ast_filestream *fs = NULL; + + if (!(fs = ast_openstream(chan, filename, preflang))) + return -1; + + if (offset) + ast_seekstream(fs, offset, SEEK_SET); + + if (ast_applystream(chan, fs)) + return -1; + + if (ast_playstream(fs)) + return -1; + + return 0; +} + +static int handle_speechrecognize(struct ast_channel *chan, AGI *agi, int argc, char **argv) +{ + struct ast_speech *speech = agi->speech; + char *prompt, dtmf = 0, tmp[4096] = "", *buf = tmp; + int timeout = 0, offset = 0, old_read_format = 0, res = 0, i = 0; + long current_offset = 0; + const char *reason = NULL; + struct ast_frame *fr = NULL; + struct ast_speech_result *result = NULL; + size_t left = sizeof(tmp); + time_t start = 0, current; + + if (argc < 4) + return RESULT_SHOWUSAGE; + + if (!speech) { + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return RESULT_SUCCESS; + } + + prompt = argv[2]; + timeout = atoi(argv[3]); + + /* If offset is specified then convert from text to integer */ + if (argc == 5) + offset = atoi(argv[4]); + + /* We want frames coming in signed linear */ + old_read_format = chan->readformat; + if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) { + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return RESULT_SUCCESS; + } + + /* Setup speech structure */ + if (speech->state == AST_SPEECH_STATE_NOT_READY || speech->state == AST_SPEECH_STATE_DONE) { + ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY); + ast_speech_start(speech); + } + + /* Start playing prompt */ + speech_streamfile(chan, prompt, chan->language, offset); + + /* Go into loop reading in frames, passing to speech thingy, checking for hangup, all that jazz */ + while (ast_strlen_zero(reason)) { + /* Run scheduled items */ + ast_sched_runq(chan->sched); + + /* See maximum time of waiting */ + if ((res = ast_sched_wait(chan->sched)) < 0) + res = 1000; + + /* Wait for frame */ + if (ast_waitfor(chan, res) > 0) { + if (!(fr = ast_read(chan))) { + reason = "hangup"; + break; + } + } + + /* Perform timeout check */ + if ((timeout > 0) && (start > 0)) { + time(¤t); + if ((current - start) >= timeout) { + reason = "timeout"; + if (fr) + ast_frfree(fr); + break; + } + } + + /* Check the speech structure for any changes */ + ast_mutex_lock(&speech->lock); + + /* See if we need to quiet the audio stream playback */ + if (ast_test_flag(speech, AST_SPEECH_QUIET) && chan->stream) { + current_offset = ast_tellstream(chan->stream); + ast_stopstream(chan); + ast_clear_flag(speech, AST_SPEECH_QUIET); + } + + /* Check each state */ + switch (speech->state) { + case AST_SPEECH_STATE_READY: + /* If the stream is done, start timeout calculation */ + if ((timeout > 0) && ((!chan->stream) || (chan->streamid == -1 && chan->timingfunc == NULL))) { + ast_stopstream(chan); + time(&start); + } + /* Write audio frame data into speech engine if possible */ + if (fr && fr->frametype == AST_FRAME_VOICE) + ast_speech_write(speech, fr->data, fr->datalen); + break; + case AST_SPEECH_STATE_WAIT: + /* Cue waiting sound if not already playing */ + if ((!chan->stream) || (chan->streamid == -1 && chan->timingfunc == NULL)) { + ast_stopstream(chan); + /* If a processing sound exists, or is not none - play it */ + if (!ast_strlen_zero(speech->processing_sound) && strcasecmp(speech->processing_sound, "none")) + speech_streamfile(chan, speech->processing_sound, chan->language, 0); + } + break; + case AST_SPEECH_STATE_DONE: + /* Get the results */ + speech->results = ast_speech_results_get(speech); + /* Change state to not ready */ + ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY); + reason = "speech"; + break; + default: + break; + } + ast_mutex_unlock(&speech->lock); + + /* Check frame for DTMF or hangup */ + if (fr) { + if (fr->frametype == AST_FRAME_DTMF) { + reason = "dtmf"; + dtmf = fr->subclass; + } else if (fr->frametype == AST_FRAME_CONTROL && fr->subclass == AST_CONTROL_HANGUP) { + reason = "hangup"; + } + ast_frfree(fr); + } + } + + if (!strcasecmp(reason, "speech")) { + /* Build string containing speech results */ + for (result = speech->results; result; result = AST_LIST_NEXT(result, list)) { + /* Build result string */ + ast_build_string(&buf, &left, "%sscore%d=%d text%d=\"%s\" grammar%d=%s", (i > 0 ? " " : ""), i, result->score, i, result->text, i, result->grammar); + /* Increment result count */ + i++; + } + /* Print out */ + ast_agi_fdprintf(chan, agi->fd, "200 result=1 (speech) endpos=%ld results=%d %s\n", current_offset, i, tmp); + } else if (!strcasecmp(reason, "dtmf")) { + ast_agi_fdprintf(chan, agi->fd, "200 result=1 (digit) digit=%c endpos=%ld\n", dtmf, current_offset); + } else if (!strcasecmp(reason, "hangup") || !strcasecmp(reason, "timeout")) { + ast_agi_fdprintf(chan, agi->fd, "200 result=1 (%s) endpos=%ld\n", reason, current_offset); + } else { + ast_agi_fdprintf(chan, agi->fd, "200 result=0 endpos=%ld\n", current_offset); + } + + return RESULT_SUCCESS; +} + +static int handle_asyncagi_break(struct ast_channel *chan, AGI *agi, int argc, char *argv[]) +{ + ast_agi_fdprintf(chan, agi->fd, "200 result=0\n"); + return AST_PBX_KEEPALIVE; +} + +static char usage_setmusic[] = +" Usage: SET MUSIC ON <on|off> <class>\n" +" Enables/Disables the music on hold generator. If <class> is\n" +" not specified, then the default music on hold class will be used.\n" +" Always returns 0.\n"; + +static char usage_dbput[] = +" Usage: DATABASE PUT <family> <key> <value>\n" +" Adds or updates an entry in the Asterisk database for a\n" +" given family, key, and value.\n" +" Returns 1 if successful, 0 otherwise.\n"; + +static char usage_dbget[] = +" Usage: DATABASE GET <family> <key>\n" +" Retrieves an entry in the Asterisk database for a\n" +" given family and key.\n" +" Returns 0 if <key> is not set. Returns 1 if <key>\n" +" is set and returns the variable in parentheses.\n" +" Example return code: 200 result=1 (testvariable)\n"; + +static char usage_dbdel[] = +" Usage: DATABASE DEL <family> <key>\n" +" Deletes an entry in the Asterisk database for a\n" +" given family and key.\n" +" Returns 1 if successful, 0 otherwise.\n"; + +static char usage_dbdeltree[] = +" Usage: DATABASE DELTREE <family> [keytree]\n" +" Deletes a family or specific keytree within a family\n" +" in the Asterisk database.\n" +" Returns 1 if successful, 0 otherwise.\n"; + +static char usage_verbose[] = +" Usage: VERBOSE <message> <level>\n" +" Sends <message> to the console via verbose message system.\n" +" <level> is the the verbose level (1-4)\n" +" Always returns 1.\n"; + +static char usage_getvariable[] = +" Usage: GET VARIABLE <variablename>\n" +" Returns 0 if <variablename> is not set. Returns 1 if <variablename>\n" +" is set and returns the variable in parentheses.\n" +" example return code: 200 result=1 (testvariable)\n"; + +static char usage_getvariablefull[] = +" Usage: GET FULL VARIABLE <variablename> [<channel name>]\n" +" Returns 0 if <variablename> is not set or channel does not exist. Returns 1\n" +"if <variablename> is set and returns the variable in parenthesis. Understands\n" +"complex variable names and builtin variables, unlike GET VARIABLE.\n" +" example return code: 200 result=1 (testvariable)\n"; + +static char usage_setvariable[] = +" Usage: SET VARIABLE <variablename> <value>\n"; + +static char usage_channelstatus[] = +" Usage: CHANNEL STATUS [<channelname>]\n" +" Returns the status of the specified channel.\n" +" If no channel name is given the returns the status of the\n" +" current channel. Return values:\n" +" 0 Channel is down and available\n" +" 1 Channel is down, but reserved\n" +" 2 Channel is off hook\n" +" 3 Digits (or equivalent) have been dialed\n" +" 4 Line is ringing\n" +" 5 Remote end is ringing\n" +" 6 Line is up\n" +" 7 Line is busy\n"; + +static char usage_setcallerid[] = +" Usage: SET CALLERID <number>\n" +" Changes the callerid of the current channel.\n"; + +static char usage_exec[] = +" Usage: EXEC <application> <options>\n" +" Executes <application> with given <options>.\n" +" Returns whatever the application returns, or -2 on failure to find application\n"; + +static char usage_hangup[] = +" Usage: HANGUP [<channelname>]\n" +" Hangs up the specified channel.\n" +" If no channel name is given, hangs up the current channel\n"; + +static char usage_answer[] = +" Usage: ANSWER\n" +" Answers channel if not already in answer state. Returns -1 on\n" +" channel failure, or 0 if successful.\n"; + +static char usage_waitfordigit[] = +" Usage: WAIT FOR DIGIT <timeout>\n" +" Waits up to 'timeout' milliseconds for channel to receive a DTMF digit.\n" +" Returns -1 on channel failure, 0 if no digit is received in the timeout, or\n" +" the numerical value of the ascii of the digit if one is received. Use -1\n" +" for the timeout value if you desire the call to block indefinitely.\n"; + +static char usage_sendtext[] = +" Usage: SEND TEXT \"<text to send>\"\n" +" Sends the given text on a channel. Most channels do not support the\n" +" transmission of text. Returns 0 if text is sent, or if the channel does not\n" +" support text transmission. Returns -1 only on error/hangup. Text\n" +" consisting of greater than one word should be placed in quotes since the\n" +" command only accepts a single argument.\n"; + +static char usage_recvchar[] = +" Usage: RECEIVE CHAR <timeout>\n" +" Receives a character of text on a channel. Specify timeout to be the\n" +" maximum time to wait for input in milliseconds, or 0 for infinite. Most channels\n" +" do not support the reception of text. Returns the decimal value of the character\n" +" if one is received, or 0 if the channel does not support text reception. Returns\n" +" -1 only on error/hangup.\n"; + +static char usage_recvtext[] = +" Usage: RECEIVE TEXT <timeout>\n" +" Receives a string of text on a channel. Specify timeout to be the\n" +" maximum time to wait for input in milliseconds, or 0 for infinite. Most channels\n" +" do not support the reception of text. Returns -1 for failure or 1 for success, and the string in parentheses.\n"; + +static char usage_tddmode[] = +" Usage: TDD MODE <on|off>\n" +" Enable/Disable TDD transmission/reception on a channel. Returns 1 if\n" +" successful, or 0 if channel is not TDD-capable.\n"; + +static char usage_sendimage[] = +" Usage: SEND IMAGE <image>\n" +" Sends the given image on a channel. Most channels do not support the\n" +" transmission of images. Returns 0 if image is sent, or if the channel does not\n" +" support image transmission. Returns -1 only on error/hangup. Image names\n" +" should not include extensions.\n"; + +static char usage_streamfile[] = +" Usage: STREAM FILE <filename> <escape digits> [sample offset]\n" +" Send the given file, allowing playback to be interrupted by the given\n" +" digits, if any. Use double quotes for the digits if you wish none to be\n" +" permitted. If sample offset is provided then the audio will seek to sample\n" +" offset before play starts. Returns 0 if playback completes without a digit\n" +" being pressed, or the ASCII numerical value of the digit if one was pressed,\n" +" or -1 on error or if the channel was disconnected. Remember, the file\n" +" extension must not be included in the filename.\n"; + +static char usage_controlstreamfile[] = +" Usage: CONTROL STREAM FILE <filename> <escape digits> [skipms] [ffchar] [rewchr] [pausechr]\n" +" Send the given file, allowing playback to be controled by the given\n" +" digits, if any. Use double quotes for the digits if you wish none to be\n" +" permitted. Returns 0 if playback completes without a digit\n" +" being pressed, or the ASCII numerical value of the digit if one was pressed,\n" +" or -1 on error or if the channel was disconnected. Remember, the file\n" +" extension must not be included in the filename.\n\n" +" Note: ffchar and rewchar default to * and # respectively.\n"; + +static char usage_getoption[] = +" Usage: GET OPTION <filename> <escape_digits> [timeout]\n" +" Behaves similar to STREAM FILE but used with a timeout option.\n"; + +static char usage_saynumber[] = +" Usage: SAY NUMBER <number> <escape digits> [gender]\n" +" Say a given number, returning early if any of the given DTMF digits\n" +" are received on the channel. Returns 0 if playback completes without a digit\n" +" being pressed, or the ASCII numerical value of the digit if one was pressed or\n" +" -1 on error/hangup.\n"; + +static char usage_saydigits[] = +" Usage: SAY DIGITS <number> <escape digits>\n" +" Say a given digit string, returning early if any of the given DTMF digits\n" +" are received on the channel. Returns 0 if playback completes without a digit\n" +" being pressed, or the ASCII numerical value of the digit if one was pressed or\n" +" -1 on error/hangup.\n"; + +static char usage_sayalpha[] = +" Usage: SAY ALPHA <number> <escape digits>\n" +" Say a given character string, returning early if any of the given DTMF digits\n" +" are received on the channel. Returns 0 if playback completes without a digit\n" +" being pressed, or the ASCII numerical value of the digit if one was pressed or\n" +" -1 on error/hangup.\n"; + +static char usage_saydate[] = +" Usage: SAY DATE <date> <escape digits>\n" +" Say a given date, returning early if any of the given DTMF digits are\n" +" received on the channel. <date> is number of seconds elapsed since 00:00:00\n" +" on January 1, 1970, Coordinated Universal Time (UTC). Returns 0 if playback\n" +" completes without a digit being pressed, or the ASCII numerical value of the\n" +" digit if one was pressed or -1 on error/hangup.\n"; + +static char usage_saytime[] = +" Usage: SAY TIME <time> <escape digits>\n" +" Say a given time, returning early if any of the given DTMF digits are\n" +" received on the channel. <time> is number of seconds elapsed since 00:00:00\n" +" on January 1, 1970, Coordinated Universal Time (UTC). Returns 0 if playback\n" +" completes without a digit being pressed, or the ASCII numerical value of the\n" +" digit if one was pressed or -1 on error/hangup.\n"; + +static char usage_saydatetime[] = +" Usage: SAY DATETIME <time> <escape digits> [format] [timezone]\n" +" Say a given time, returning early if any of the given DTMF digits are\n" +" received on the channel. <time> is number of seconds elapsed since 00:00:00\n" +" on January 1, 1970, Coordinated Universal Time (UTC). [format] is the format\n" +" the time should be said in. See voicemail.conf (defaults to \"ABdY\n" +" 'digits/at' IMp\"). Acceptable values for [timezone] can be found in\n" +" /usr/share/zoneinfo. Defaults to machine default. Returns 0 if playback\n" +" completes without a digit being pressed, or the ASCII numerical value of the\n" +" digit if one was pressed or -1 on error/hangup.\n"; + +static char usage_sayphonetic[] = +" Usage: SAY PHONETIC <string> <escape digits>\n" +" Say a given character string with phonetics, returning early if any of the\n" +" given DTMF digits are received on the channel. Returns 0 if playback\n" +" completes without a digit pressed, the ASCII numerical value of the digit\n" +" if one was pressed, or -1 on error/hangup.\n"; + +static char usage_getdata[] = +" Usage: GET DATA <file to be streamed> [timeout] [max digits]\n" +" Stream the given file, and recieve DTMF data. Returns the digits received\n" +"from the channel at the other end.\n"; + +static char usage_setcontext[] = +" Usage: SET CONTEXT <desired context>\n" +" Sets the context for continuation upon exiting the application.\n"; + +static char usage_setextension[] = +" Usage: SET EXTENSION <new extension>\n" +" Changes the extension for continuation upon exiting the application.\n"; + +static char usage_setpriority[] = +" Usage: SET PRIORITY <priority>\n" +" Changes the priority for continuation upon exiting the application.\n" +" The priority must be a valid priority or label.\n"; + +static char usage_recordfile[] = +" Usage: RECORD FILE <filename> <format> <escape digits> <timeout> \\\n" +" [offset samples] [BEEP] [s=silence]\n" +" Record to a file until a given dtmf digit in the sequence is received\n" +" Returns -1 on hangup or error. The format will specify what kind of file\n" +" will be recorded. The timeout is the maximum record time in milliseconds, or\n" +" -1 for no timeout. \"Offset samples\" is optional, and, if provided, will seek\n" +" to the offset without exceeding the end of the file. \"silence\" is the number\n" +" of seconds of silence allowed before the function returns despite the\n" +" lack of dtmf digits or reaching timeout. Silence value must be\n" +" preceeded by \"s=\" and is also optional.\n"; + +static char usage_autohangup[] = +" Usage: SET AUTOHANGUP <time>\n" +" Cause the channel to automatically hangup at <time> seconds in the\n" +" future. Of course it can be hungup before then as well. Setting to 0 will\n" +" cause the autohangup feature to be disabled on this channel.\n"; + +static char usage_break_aagi[] = +" Usage: ASYNCAGI BREAK\n" +" Break the Async AGI loop.\n"; + +static char usage_noop[] = +" Usage: NoOp\n" +" Does nothing.\n"; + +static char usage_speechcreate[] = +" Usage: SPEECH CREATE <engine>\n" +" Create a speech object to be used by the other Speech AGI commands.\n"; + +static char usage_speechset[] = +" Usage: SPEECH SET <name> <value>\n" +" Set an engine-specific setting.\n"; + +static char usage_speechdestroy[] = +" Usage: SPEECH DESTROY\n" +" Destroy the speech object created by SPEECH CREATE.\n"; + +static char usage_speechloadgrammar[] = +" Usage: SPEECH LOAD GRAMMAR <grammar name> <path to grammar>\n" +" Loads the specified grammar as the specified name.\n"; + +static char usage_speechunloadgrammar[] = +" Usage: SPEECH UNLOAD GRAMMAR <grammar name>\n" +" Unloads the specified grammar.\n"; + +static char usage_speechactivategrammar[] = +" Usage: SPEECH ACTIVATE GRAMMAR <grammar name>\n" +" Activates the specified grammar on the speech object.\n"; + +static char usage_speechdeactivategrammar[] = +" Usage: SPEECH DEACTIVATE GRAMMAR <grammar name>\n" +" Deactivates the specified grammar on the speech object.\n"; + +static char usage_speechrecognize[] = +" Usage: SPEECH RECOGNIZE <prompt> <timeout> [<offset>]\n" +" Plays back given prompt while listening for speech and dtmf.\n"; + +/*! + * \brief AGI commands list + */ +static struct agi_command commands[] = { + { { "answer", NULL }, handle_answer, "Answer channel", usage_answer , 0 }, + { { "channel", "status", NULL }, handle_channelstatus, "Returns status of the connected channel", usage_channelstatus , 0 }, + { { "database", "del", NULL }, handle_dbdel, "Removes database key/value", usage_dbdel , 1 }, + { { "database", "deltree", NULL }, handle_dbdeltree, "Removes database keytree/value", usage_dbdeltree , 1 }, + { { "database", "get", NULL }, handle_dbget, "Gets database value", usage_dbget , 1 }, + { { "database", "put", NULL }, handle_dbput, "Adds/updates database value", usage_dbput , 1 }, + { { "exec", NULL }, handle_exec, "Executes a given Application", usage_exec , 1 }, + { { "get", "data", NULL }, handle_getdata, "Prompts for DTMF on a channel", usage_getdata , 0 }, + { { "get", "full", "variable", NULL }, handle_getvariablefull, "Evaluates a channel expression", usage_getvariablefull , 1 }, + { { "get", "option", NULL }, handle_getoption, "Stream file, prompt for DTMF, with timeout", usage_getoption , 0 }, + { { "get", "variable", NULL }, handle_getvariable, "Gets a channel variable", usage_getvariable , 1 }, + { { "hangup", NULL }, handle_hangup, "Hangup the current channel", usage_hangup , 0 }, + { { "noop", NULL }, handle_noop, "Does nothing", usage_noop , 1 }, + { { "receive", "char", NULL }, handle_recvchar, "Receives one character from channels supporting it", usage_recvchar , 0 }, + { { "receive", "text", NULL }, handle_recvtext, "Receives text from channels supporting it", usage_recvtext , 0 }, + { { "record", "file", NULL }, handle_recordfile, "Records to a given file", usage_recordfile , 0 }, + { { "say", "alpha", NULL }, handle_sayalpha, "Says a given character string", usage_sayalpha , 0 }, + { { "say", "digits", NULL }, handle_saydigits, "Says a given digit string", usage_saydigits , 0 }, + { { "say", "number", NULL }, handle_saynumber, "Says a given number", usage_saynumber , 0 }, + { { "say", "phonetic", NULL }, handle_sayphonetic, "Says a given character string with phonetics", usage_sayphonetic , 0 }, + { { "say", "date", NULL }, handle_saydate, "Says a given date", usage_saydate , 0 }, + { { "say", "time", NULL }, handle_saytime, "Says a given time", usage_saytime , 0 }, + { { "say", "datetime", NULL }, handle_saydatetime, "Says a given time as specfied by the format given", usage_saydatetime , 0 }, + { { "send", "image", NULL }, handle_sendimage, "Sends images to channels supporting it", usage_sendimage , 0 }, + { { "send", "text", NULL }, handle_sendtext, "Sends text to channels supporting it", usage_sendtext , 0 }, + { { "set", "autohangup", NULL }, handle_autohangup, "Autohangup channel in some time", usage_autohangup , 0 }, + { { "set", "callerid", NULL }, handle_setcallerid, "Sets callerid for the current channel", usage_setcallerid , 0 }, + { { "set", "context", NULL }, handle_setcontext, "Sets channel context", usage_setcontext , 0 }, + { { "set", "extension", NULL }, handle_setextension, "Changes channel extension", usage_setextension , 0 }, + { { "set", "music", NULL }, handle_setmusic, "Enable/Disable Music on hold generator", usage_setmusic , 0 }, + { { "set", "priority", NULL }, handle_setpriority, "Set channel dialplan priority", usage_setpriority , 0 }, + { { "set", "variable", NULL }, handle_setvariable, "Sets a channel variable", usage_setvariable , 1 }, + { { "stream", "file", NULL }, handle_streamfile, "Sends audio file on channel", usage_streamfile , 0 }, + { { "control", "stream", "file", NULL }, handle_controlstreamfile, "Sends audio file on channel and allows the listner to control the stream", usage_controlstreamfile , 0 }, + { { "tdd", "mode", NULL }, handle_tddmode, "Toggles TDD mode (for the deaf)", usage_tddmode , 0 }, + { { "verbose", NULL }, handle_verbose, "Logs a message to the asterisk verbose log", usage_verbose , 1 }, + { { "wait", "for", "digit", NULL }, handle_waitfordigit, "Waits for a digit to be pressed", usage_waitfordigit , 0 }, + { { "speech", "create", NULL }, handle_speechcreate, "Creates a speech object", usage_speechcreate, 0 }, + { { "speech", "set", NULL }, handle_speechset, "Sets a speech engine setting", usage_speechset, 0 }, + { { "speech", "destroy", NULL }, handle_speechdestroy, "Destroys a speech object", usage_speechdestroy, 1 }, + { { "speech", "load", "grammar", NULL }, handle_speechloadgrammar, "Loads a grammar", usage_speechloadgrammar, 0 }, + { { "speech", "unload", "grammar", NULL }, handle_speechunloadgrammar, "Unloads a grammar", usage_speechunloadgrammar, 1 }, + { { "speech", "activate", "grammar", NULL }, handle_speechactivategrammar, "Activates a grammar", usage_speechactivategrammar, 0 }, + { { "speech", "deactivate", "grammar", NULL }, handle_speechdeactivategrammar, "Deactivates a grammar", usage_speechdeactivategrammar, 0 }, + { { "speech", "recognize", NULL }, handle_speechrecognize, "Recognizes speech", usage_speechrecognize, 0 }, + { { "asyncagi", "break", NULL }, handle_asyncagi_break, "Break AsyncAGI loop", usage_break_aagi, 0 }, +}; + +static AST_RWLIST_HEAD_STATIC(agi_commands, agi_command); + +static char *help_workhorse(int fd, char *match[]) +{ + char fullcmd[80], matchstr[80]; + struct agi_command *e; + + if (match) + ast_join(matchstr, sizeof(matchstr), match); + + ast_cli(fd, "%5.5s %30.30s %s\n","Dead","Command","Description"); + AST_RWLIST_RDLOCK(&agi_commands); + AST_RWLIST_TRAVERSE(&agi_commands, e, list) { + if (!e->cmda[0]) + break; + /* Hide commands that start with '_' */ + if ((e->cmda[0])[0] == '_') + continue; + ast_join(fullcmd, sizeof(fullcmd), e->cmda); + if (match && strncasecmp(matchstr, fullcmd, strlen(matchstr))) + continue; + ast_cli(fd, "%5.5s %30.30s %s\n", e->dead ? "Yes" : "No" , fullcmd, e->summary); + } + AST_RWLIST_UNLOCK(&agi_commands); + + return CLI_SUCCESS; +} + +int ast_agi_register(struct ast_module *mod, agi_command *cmd) +{ + char fullcmd[80]; + + ast_join(fullcmd, sizeof(fullcmd), cmd->cmda); + + if (!find_command(cmd->cmda,1)) { + cmd->mod = mod; + AST_RWLIST_WRLOCK(&agi_commands); + AST_LIST_INSERT_TAIL(&agi_commands, cmd, list); + AST_RWLIST_UNLOCK(&agi_commands); + if (mod != ast_module_info->self) + ast_module_ref(ast_module_info->self); + ast_verb(2, "AGI Command '%s' registered\n",fullcmd); + return 1; + } else { + ast_log(LOG_WARNING, "Command already registered!\n"); + return 0; + } +} + +int ast_agi_unregister(struct ast_module *mod, agi_command *cmd) +{ + struct agi_command *e; + int unregistered = 0; + char fullcmd[80]; + + ast_join(fullcmd, sizeof(fullcmd), cmd->cmda); + + AST_RWLIST_WRLOCK(&agi_commands); + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&agi_commands, e, list) { + if (cmd == e) { + AST_RWLIST_REMOVE_CURRENT(list); + if (mod != ast_module_info->self) + ast_module_unref(ast_module_info->self); + unregistered=1; + break; + } + } + AST_RWLIST_TRAVERSE_SAFE_END; + AST_RWLIST_UNLOCK(&agi_commands); + if (unregistered) + ast_verb(2, "AGI Command '%s' unregistered\n",fullcmd); + else + ast_log(LOG_WARNING, "Unable to unregister command: '%s'!\n",fullcmd); + return unregistered; +} + +void ast_agi_register_multiple(struct ast_module *mod, agi_command *cmd, int len) +{ + int i; + + for (i = 0; i < len; i++) + ast_agi_register(mod, cmd + i); + +} + +void ast_agi_unregister_multiple(struct ast_module *mod, agi_command *cmd, int len) +{ + int i; + + for (i = 0; i < len; i++) + ast_agi_unregister(mod, cmd + i); +} + +static agi_command *find_command(char *cmds[], int exact) +{ + int y, match; + struct agi_command *e; + + AST_RWLIST_RDLOCK(&agi_commands); + AST_RWLIST_TRAVERSE(&agi_commands, e, list) { + if (!e->cmda[0]) + break; + /* start optimistic */ + match = 1; + for (y = 0; match && cmds[y]; y++) { + /* If there are no more words in the command (and we're looking for + an exact match) or there is a difference between the two words, + then this is not a match */ + if (!e->cmda[y] && !exact) + break; + /* don't segfault if the next part of a command doesn't exist */ + if (!e->cmda[y]) + return NULL; + if (strcasecmp(e->cmda[y], cmds[y])) + match = 0; + } + /* If more words are needed to complete the command then this is not + a candidate (unless we're looking for a really inexact answer */ + if ((exact > -1) && e->cmda[y]) + match = 0; + if (match) + return e; + } + AST_RWLIST_UNLOCK(&agi_commands); + return NULL; +} + +static int parse_args(char *s, int *max, char *argv[]) +{ + int x = 0, quoted = 0, escaped = 0, whitespace = 1; + char *cur; + + cur = s; + while(*s) { + switch(*s) { + case '"': + /* If it's escaped, put a literal quote */ + if (escaped) + goto normal; + else + quoted = !quoted; + if (quoted && whitespace) { + /* If we're starting a quote, coming off white space start a new word, too */ + argv[x++] = cur; + whitespace=0; + } + escaped = 0; + break; + case ' ': + case '\t': + if (!quoted && !escaped) { + /* If we're not quoted, mark this as whitespace, and + end the previous argument */ + whitespace = 1; + *(cur++) = '\0'; + } else + /* Otherwise, just treat it as anything else */ + goto normal; + break; + case '\\': + /* If we're escaped, print a literal, otherwise enable escaping */ + if (escaped) { + goto normal; + } else { + escaped=1; + } + break; + default: +normal: + if (whitespace) { + if (x >= MAX_ARGS -1) { + ast_log(LOG_WARNING, "Too many arguments, truncating\n"); + break; + } + /* Coming off of whitespace, start the next argument */ + argv[x++] = cur; + whitespace=0; + } + *(cur++) = *s; + escaped=0; + } + s++; + } + /* Null terminate */ + *(cur++) = '\0'; + argv[x] = NULL; + *max = x; + return 0; +} + +static int agi_handle_command(struct ast_channel *chan, AGI *agi, char *buf, int dead) +{ + char *argv[MAX_ARGS]; + int argc = MAX_ARGS, res; + agi_command *c; + const char *ami_res = "Unknown Result"; + char *ami_cmd = ast_strdupa(buf); + int command_id = ast_random(), resultcode = 200; + + manager_event(EVENT_FLAG_CALL, "AGIExec", + "SubEvent: Start\r\n" + "Channel: %s\r\n" + "CommandId: %d\r\n" + "Command: %s\r\n", chan->name, command_id, ami_cmd); + parse_args(buf, &argc, argv); + if ((c = find_command(argv, 0)) && (!dead || (dead && c->dead))) { + /* if this command wasnt registered by res_agi, be sure to usecount + the module we are using */ + if (c->mod != ast_module_info->self) + ast_module_ref(c->mod); + res = c->handler(chan, agi, argc, argv); + if (c->mod != ast_module_info->self) + ast_module_unref(c->mod); + switch (res) { + case RESULT_SHOWUSAGE: ami_res = "Usage"; resultcode = 520; break; + case AST_PBX_KEEPALIVE: ami_res = "KeepAlive"; resultcode = 210; break; + case RESULT_FAILURE: ami_res = "Failure"; resultcode = -1; break; + case RESULT_SUCCESS: ami_res = "Success"; resultcode = 200; break; + } + manager_event(EVENT_FLAG_CALL, "AGIExec", + "SubEvent: End\r\n" + "Channel: %s\r\n" + "CommandId: %d\r\n" + "Command: %s\r\n" + "ResultCode: %d\r\n" + "Result: %s\r\n", chan->name, command_id, ami_cmd, resultcode, ami_res); + switch(res) { + case RESULT_SHOWUSAGE: + ast_agi_fdprintf(chan, agi->fd, "520-Invalid command syntax. Proper usage follows:\n"); + ast_agi_fdprintf(chan, agi->fd, c->usage); + ast_agi_fdprintf(chan, agi->fd, "520 End of proper usage.\n"); + break; + case AST_PBX_KEEPALIVE: + /* We've been asked to keep alive, so do so */ + return AST_PBX_KEEPALIVE; + break; + case RESULT_FAILURE: + /* They've already given the failure. We've been hung up on so handle this + appropriately */ + return -1; + } + } else if ((c = find_command(argv, 0))) { + ast_agi_fdprintf(chan, agi->fd, "511 Command Not Permitted on a dead channel\n"); + manager_event(EVENT_FLAG_CALL, "AGIExec", + "SubEvent: End\r\n" + "Channel: %s\r\n" + "CommandId: %d\r\n" + "Command: %s\r\n" + "ResultCode: 511\r\n" + "Result: Command not permitted on a dead channel\r\n", chan->name, command_id, ami_cmd); + } else { + ast_agi_fdprintf(chan, agi->fd, "510 Invalid or unknown command\n"); + manager_event(EVENT_FLAG_CALL, "AGIExec", + "SubEvent: End\r\n" + "Channel: %s\r\n" + "CommandId: %d\r\n" + "Command: %s\r\n" + "ResultCode: 510\r\n" + "Result: Invalid or unknown command\r\n", chan->name, command_id, ami_cmd); + } + return 0; +} +static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi, int pid, int *status, int dead, int argc, char *argv[]) +{ + struct ast_channel *c; + int outfd, ms, needhup = 0; + enum agi_result returnstatus = AGI_RESULT_SUCCESS; + struct ast_frame *f; + char buf[AGI_BUF_LEN]; + char *res = NULL; + FILE *readf; + /* how many times we'll retry if ast_waitfor_nandfs will return without either + channel or file descriptor in case select is interrupted by a system call (EINTR) */ + int retry = AGI_NANDFS_RETRY; + + if (!(readf = fdopen(agi->ctrl, "r"))) { + ast_log(LOG_WARNING, "Unable to fdopen file descriptor\n"); + if (pid > -1) + kill(pid, SIGHUP); + close(agi->ctrl); + return AGI_RESULT_FAILURE; + } + setlinebuf(readf); + setup_env(chan, request, agi->fd, (agi->audio > -1), argc, argv); + for (;;) { + if (needhup) { + needhup = 0; + dead = 1; + if (pid > -1) + kill(pid, SIGHUP); + } + ms = -1; + c = ast_waitfor_nandfds(&chan, dead ? 0 : 1, &agi->ctrl, 1, NULL, &outfd, &ms); + if (c) { + retry = AGI_NANDFS_RETRY; + /* Idle the channel until we get a command */ + f = ast_read(c); + if (!f) { + ast_debug(1, "%s hungup\n", chan->name); + returnstatus = AGI_RESULT_HANGUP; + needhup = 1; + continue; + } else { + /* If it's voice, write it to the audio pipe */ + if ((agi->audio > -1) && (f->frametype == AST_FRAME_VOICE)) { + /* Write, ignoring errors */ + write(agi->audio, f->data, f->datalen); + } + ast_frfree(f); + } + } else if (outfd > -1) { + size_t len = sizeof(buf); + size_t buflen = 0; + + retry = AGI_NANDFS_RETRY; + buf[0] = '\0'; + + while (buflen < (len - 1)) { + res = fgets(buf + buflen, len, readf); + if (feof(readf)) + break; + if (ferror(readf) && ((errno != EINTR) && (errno != EAGAIN))) + break; + if (res != NULL && !agi->fast) + break; + buflen = strlen(buf); + if (buflen && buf[buflen - 1] == '\n') + break; + len -= buflen; + if (agidebug) + ast_verbose( "AGI Rx << temp buffer %s - errno %s\n", buf, strerror(errno)); + } + + if (!buf[0]) { + /* Program terminated */ + if (returnstatus && returnstatus != AST_PBX_KEEPALIVE) + returnstatus = -1; + ast_verb(3, "<%s>AGI Script %s completed, returning %d\n", chan->name, request, returnstatus); + if (pid > 0) + waitpid(pid, status, 0); + /* No need to kill the pid anymore, since they closed us */ + pid = -1; + break; + } + + /* get rid of trailing newline, if any */ + if (*buf && buf[strlen(buf) - 1] == '\n') + buf[strlen(buf) - 1] = 0; + if (agidebug) + ast_verbose("<%s>AGI Rx << %s\n", chan->name, buf); + returnstatus |= agi_handle_command(chan, agi, buf, dead); + /* If the handle_command returns -1, we need to stop */ + if ((returnstatus < 0) || (returnstatus == AST_PBX_KEEPALIVE)) { + needhup = 1; + continue; + } + } else { + if (--retry <= 0) { + ast_log(LOG_WARNING, "No channel, no fd?\n"); + returnstatus = AGI_RESULT_FAILURE; + break; + } + } + } + /* Notify process */ + if (pid > -1) { + const char *sighup = pbx_builtin_getvar_helper(chan, "AGISIGHUP"); + if (ast_strlen_zero(sighup) || !ast_false(sighup)) { + if (kill(pid, SIGHUP)) + ast_log(LOG_WARNING, "unable to send SIGHUP to AGI process %d: %s\n", pid, strerror(errno)); + } + } + fclose(readf); + return returnstatus; +} + +static char *handle_cli_agi_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct agi_command *command; + char fullcmd[80]; + + switch (cmd) { + case CLI_INIT: + e->command = "agi show"; + e->usage = + "Usage: agi show [topic]\n" + " When called with a topic as an argument, displays usage\n" + " information on the given command. If called without a\n" + " topic, it provides a list of AGI commands.\n"; + break; + case CLI_GENERATE: + return NULL; + } + if (a->argc < e->args) + return CLI_SHOWUSAGE; + if (a->argc > e->args) { + command = find_command(a->argv + e->args, 1); + if (command) { + ast_cli(a->fd, command->usage); + ast_cli(a->fd, " Runs Dead : %s\n", command->dead ? "Yes" : "No"); + } else { + if (find_command(a->argv + e->args, -1)) { + return help_workhorse(a->fd, a->argv + e->args); + } else { + ast_join(fullcmd, sizeof(fullcmd), a->argv + e->args); + ast_cli(a->fd, "No such command '%s'.\n", fullcmd); + } + } + } else { + return help_workhorse(a->fd, NULL); + } + return CLI_SUCCESS; +} + +/*! \brief Convert string to use HTML escaped characters + \note Maybe this should be a generic function? +*/ +static void write_html_escaped(FILE *htmlfile, char *str) +{ + char *cur = str; + + while(*cur) { + switch (*cur) { + case '<': + fprintf(htmlfile, "%s", "<"); + break; + case '>': + fprintf(htmlfile, "%s", ">"); + break; + case '&': + fprintf(htmlfile, "%s", "&"); + break; + case '"': + fprintf(htmlfile, "%s", """); + break; + default: + fprintf(htmlfile, "%c", *cur); + break; + } + cur++; + } + + return; +} + +static char *handle_cli_agi_dumphtml(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct agi_command *command; + char fullcmd[80]; + FILE *htmlfile; + + switch (cmd) { + case CLI_INIT: + e->command = "agi dumphtml"; + e->usage = + "Usage: agi dumphtml <filename>\n" + " Dumps the AGI command list in HTML format to the given\n" + " file.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + if (a->argc < e->args + 1) + return CLI_SHOWUSAGE; + + if (!(htmlfile = fopen(a->argv[2], "wt"))) { + ast_cli(a->fd, "Could not create file '%s'\n", a->argv[2]); + return CLI_SHOWUSAGE; + } + + fprintf(htmlfile, "<HTML>\n<HEAD>\n<TITLE>AGI Commands</TITLE>\n</HEAD>\n"); + fprintf(htmlfile, "<BODY>\n<CENTER><B><H1>AGI Commands</H1></B></CENTER>\n\n"); + fprintf(htmlfile, "<TABLE BORDER=\"0\" CELLSPACING=\"10\">\n"); + + AST_RWLIST_RDLOCK(&agi_commands); + AST_RWLIST_TRAVERSE(&agi_commands, command, list) { + char *stringp, *tempstr; + + if (!command->cmda[0]) /* end ? */ + break; + /* Hide commands that start with '_' */ + if ((command->cmda[0])[0] == '_') + continue; + ast_join(fullcmd, sizeof(fullcmd), command->cmda); + + fprintf(htmlfile, "<TR><TD><TABLE BORDER=\"1\" CELLPADDING=\"5\" WIDTH=\"100%%\">\n"); + fprintf(htmlfile, "<TR><TH ALIGN=\"CENTER\"><B>%s - %s</B></TH></TR>\n", fullcmd, command->summary); + + stringp = command->usage; + tempstr = strsep(&stringp, "\n"); + + fprintf(htmlfile, "<TR><TD ALIGN=\"CENTER\">"); + write_html_escaped(htmlfile, tempstr); + fprintf(htmlfile, "</TD></TR>\n"); + fprintf(htmlfile, "<TR><TD ALIGN=\"CENTER\">\n"); + + while ((tempstr = strsep(&stringp, "\n")) != NULL) { + write_html_escaped(htmlfile, tempstr); + fprintf(htmlfile, "<BR>\n"); + } + fprintf(htmlfile, "</TD></TR>\n"); + fprintf(htmlfile, "</TABLE></TD></TR>\n\n"); + } + AST_RWLIST_UNLOCK(&agi_commands); + fprintf(htmlfile, "</TABLE>\n</BODY>\n</HTML>\n"); + fclose(htmlfile); + ast_cli(a->fd, "AGI HTML commands dumped to: %s\n", a->argv[2]); + return CLI_SUCCESS; +} + +static int agi_exec_full(struct ast_channel *chan, void *data, int enhanced, int dead) +{ + enum agi_result res; + struct ast_module_user *u; + char buf[AGI_BUF_LEN] = "", *tmp = buf; + int fds[2], efd = -1, pid; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(arg)[MAX_ARGS]; + ); + AGI agi; + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "AGI requires an argument (script)\n"); + return -1; + } + if (dead) + ast_log(LOG_NOTICE, "Hungup channel detected, running agi in dead mode.\n"); + ast_copy_string(buf, data, sizeof(buf)); + memset(&agi, 0, sizeof(agi)); + AST_STANDARD_APP_ARGS(args, tmp); + args.argv[args.argc] = NULL; + + u = ast_module_user_add(chan); +#if 0 + /* Answer if need be */ + if (chan->_state != AST_STATE_UP) { + if (ast_answer(chan)) { + ast_module_user_remove(u); + return -1; + } + } +#endif + res = launch_script(chan, args.argv[0], args.argv, fds, enhanced ? &efd : NULL, &pid); + /* Async AGI do not require run_agi(), so just proceed if normal AGI + or Fast AGI are setup with success. */ + if (res == AGI_RESULT_SUCCESS || res == AGI_RESULT_SUCCESS_FAST) { + int status = 0; + agi.fd = fds[1]; + agi.ctrl = fds[0]; + agi.audio = efd; + agi.fast = (res == AGI_RESULT_SUCCESS_FAST) ? 1 : 0; + res = run_agi(chan, args.argv[0], &agi, pid, &status, dead, args.argc, args.argv); + /* If the fork'd process returns non-zero, set AGISTATUS to FAILURE */ + if ((res == AGI_RESULT_SUCCESS || res == AGI_RESULT_SUCCESS_FAST) && status) + res = AGI_RESULT_FAILURE; + if (fds[1] != fds[0]) + close(fds[1]); + if (efd > -1) + close(efd); + ast_unreplace_sigchld(); + } + ast_module_user_remove(u); + + switch (res) { + case AGI_RESULT_SUCCESS: + case AGI_RESULT_SUCCESS_FAST: + case AGI_RESULT_SUCCESS_ASYNC: + pbx_builtin_setvar_helper(chan, "AGISTATUS", "SUCCESS"); + break; + case AGI_RESULT_FAILURE: + pbx_builtin_setvar_helper(chan, "AGISTATUS", "FAILURE"); + break; + case AGI_RESULT_NOTFOUND: + pbx_builtin_setvar_helper(chan, "AGISTATUS", "NOTFOUND"); + break; + case AGI_RESULT_HANGUP: + pbx_builtin_setvar_helper(chan, "AGISTATUS", "HANGUP"); + return -1; + } + + return 0; +} + +static int agi_exec(struct ast_channel *chan, void *data) +{ + if (!ast_check_hangup(chan)) + return agi_exec_full(chan, data, 0, 0); + else + return agi_exec_full(chan, data, 0, 1); +} + +static int eagi_exec(struct ast_channel *chan, void *data) +{ + int readformat, res; + + if (ast_check_hangup(chan)) { + ast_log(LOG_ERROR, "If you want to run AGI on hungup channels you should use DeadAGI!\n"); + return 0; + } + readformat = chan->readformat; + if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) { + ast_log(LOG_WARNING, "Unable to set channel '%s' to linear mode\n", chan->name); + return -1; + } + res = agi_exec_full(chan, data, 1, 0); + if (!res) { + if (ast_set_read_format(chan, readformat)) { + ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(readformat)); + } + } + return res; +} + +static int deadagi_exec(struct ast_channel *chan, void *data) +{ + ast_log(LOG_WARNING, "DeadAGI has been deprecated, please use AGI in all cases!\n"); + return agi_exec(chan, data); +} + +static struct ast_cli_entry cli_agi[] = { + AST_CLI_DEFINE(handle_cli_agi_add_cmd, "Add AGI command to a channel in Async AGI"), + AST_CLI_DEFINE(handle_cli_agi_debug, "Enable/Disable AGI debugging"), + AST_CLI_DEFINE(handle_cli_agi_show, "List AGI commands or specific help"), + AST_CLI_DEFINE(handle_cli_agi_dumphtml, "Dumps a list of AGI commands in HTML format") +}; + +static int unload_module(void) +{ + ast_cli_unregister_multiple(cli_agi, sizeof(cli_agi) / sizeof(struct ast_cli_entry)); + ast_agi_unregister_multiple(ast_module_info->self, commands, sizeof(commands) / sizeof(struct agi_command)); + ast_unregister_application(eapp); + ast_unregister_application(deadapp); + ast_manager_unregister("AGI"); + return ast_unregister_application(app); +} + +static int load_module(void) +{ + ast_cli_register_multiple(cli_agi, sizeof(cli_agi) / sizeof(struct ast_cli_entry)); + ast_agi_register_multiple(ast_module_info->self, commands, sizeof(commands) / sizeof(struct agi_command)); + ast_register_application(deadapp, deadagi_exec, deadsynopsis, descrip); + ast_register_application(eapp, eagi_exec, esynopsis, descrip); + ast_manager_register2("AGI", EVENT_FLAG_CALL, action_add_agi_cmd, "Add an AGI command to execute by Async AGI", mandescr_asyncagi); + return ast_register_application(app, agi_exec, synopsis, descrip); +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Asterisk Gateway Interface (AGI)", + .load = load_module, + .unload = unload_module, + ); diff --git a/trunk/res/res_clioriginate.c b/trunk/res/res_clioriginate.c new file mode 100644 index 000000000..3d20e3ca3 --- /dev/null +++ b/trunk/res/res_clioriginate.c @@ -0,0 +1,191 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2005 - 2006, Digium, Inc. + * + * Russell Bryant <russell@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \author Russell Bryant <russell@digium.com> + * + * \brief Originate calls via the CLI + * + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$"); + +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/cli.h" +#include "asterisk/utils.h" +#include "asterisk/frame.h" + +/*! The timeout for originated calls, in seconds */ +#define TIMEOUT 30 + +/*! + * \brief orginate a call from the CLI + * \param fd file descriptor for cli + * \param chan channel to create type/data + * \param app application you want to run + * \param appdata data for application + * \retval CLI_SUCCESS on success. + * \retval CLI_SHOWUSAGE on failure. +*/ +static char *orig_app(int fd, const char *chan, const char *app, const char *appdata) +{ + char *chantech; + char *chandata; + int reason = 0; + + if (ast_strlen_zero(app)) + return CLI_SHOWUSAGE; + + chandata = ast_strdupa(chan); + + chantech = strsep(&chandata, "/"); + if (!chandata) { + ast_cli(fd, "*** No data provided after channel type! ***\n"); + return CLI_SHOWUSAGE; + } + + ast_pbx_outgoing_app(chantech, AST_FORMAT_SLINEAR, chandata, TIMEOUT * 1000, app, appdata, &reason, 1, NULL, NULL, NULL, NULL, NULL); + + return CLI_SUCCESS; +} + +/*! + * \brief orginate from extension + * \param fd file descriptor for cli + * \param chan channel to create type/data + * \param data contains exten\@context + * \retval CLI_SUCCESS on success. + * \retval CLI_SHOWUSAGE on failure. +*/ +static char *orig_exten(int fd, const char *chan, const char *data) +{ + char *chantech; + char *chandata; + char *exten = NULL; + char *context = NULL; + int reason = 0; + + chandata = ast_strdupa(chan); + + chantech = strsep(&chandata, "/"); + if (!chandata) { + ast_cli(fd, "*** No data provided after channel type! ***\n"); + return CLI_SHOWUSAGE; + } + + if (!ast_strlen_zero(data)) { + context = ast_strdupa(data); + exten = strsep(&context, "@"); + } + + if (ast_strlen_zero(exten)) + exten = "s"; + if (ast_strlen_zero(context)) + context = "default"; + + ast_pbx_outgoing_exten(chantech, AST_FORMAT_SLINEAR, chandata, TIMEOUT * 1000, context, exten, 1, &reason, 1, NULL, NULL, NULL, NULL, NULL); + + return CLI_SUCCESS; +} + +/*! + * \brief handle for orgination app or exten. + * \param e pointer to the CLI structure to initialize + * \param cmd operation to execute + * \param a structure that contains either application or extension arguments + * \retval CLI_SUCCESS on success. + * \retval CLI_SHOWUSAGE on failure. +*/ +static char *handle_orig(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + static char *choices[] = { "application", "extension", NULL }; + char *res; + switch (cmd) { + case CLI_INIT: + e->command = "originate"; + e->usage = + " There are two ways to use this command. A call can be originated between a\n" + "channel and a specific application, or between a channel and an extension in\n" + "the dialplan. This is similar to call files or the manager originate action.\n" + "Calls originated with this command are given a timeout of 30 seconds.\n\n" + + "Usage1: originate <tech/data> application <appname> [appdata]\n" + " This will originate a call between the specified channel tech/data and the\n" + "given application. Arguments to the application are optional. If the given\n" + "arguments to the application include spaces, all of the arguments to the\n" + "application need to be placed in quotation marks.\n\n" + + "Usage2: originate <tech/data> extension [exten@][context]\n" + " This will originate a call between the specified channel tech/data and the\n" + "given extension. If no context is specified, the 'default' context will be\n" + "used. If no extension is given, the 's' extension will be used.\n"; + return NULL; + case CLI_GENERATE: + if (a->pos != 2) + return NULL; + + /* ugly, can be removed when CLI entries have ast_module pointers */ + ast_module_ref(ast_module_info->self); + res = ast_cli_complete(a->word, choices, a->n); + ast_module_unref(ast_module_info->self); + + return res; + } + + if (ast_strlen_zero(a->argv[1]) || ast_strlen_zero(a->argv[2])) + return CLI_SHOWUSAGE; + + /* ugly, can be removed when CLI entries have ast_module pointers */ + ast_module_ref(ast_module_info->self); + + if (!strcasecmp("application", a->argv[2])) { + res = orig_app(a->fd, a->argv[1], a->argv[3], a->argv[4]); + } else if (!strcasecmp("extension", a->argv[2])) { + res = orig_exten(a->fd, a->argv[1], a->argv[3]); + } else + res = CLI_SHOWUSAGE; + + ast_module_unref(ast_module_info->self); + + return res; +} + +static struct ast_cli_entry cli_cliorig[] = { + AST_CLI_DEFINE(handle_orig, "Originate a call"), +}; + +/*! \brief Unload orginate module */ +static int unload_module(void) +{ + return ast_cli_unregister_multiple(cli_cliorig, ARRAY_LEN(cli_cliorig)); +} + +/*! \brief Load orginate module */ +static int load_module(void) +{ + int res; + res = ast_cli_register_multiple(cli_cliorig, ARRAY_LEN(cli_cliorig)); + return res ? AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call origination from the CLI"); diff --git a/trunk/res/res_config_curl.c b/trunk/res/res_config_curl.c new file mode 100644 index 000000000..622b8639f --- /dev/null +++ b/trunk/res/res_config_curl.c @@ -0,0 +1,503 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2008, Digium, Inc. + * + * Tilghman Lesher <res_config_curl_v1@the-tilghman.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief curl plugin for portable configuration engine + * + * \author Tilghman Lesher <res_config_curl_v1@the-tilghman.com> + * + * \extref Depends on the CURL library - http://curl.haxx.se/ + * + */ + +/*** MODULEINFO + <depend>curl</depend> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <curl/curl.h> + +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/config.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/utils.h" + +/*! + * \brief Execute a curl query and return ast_variable list + * \param url The base URL from which to retrieve data + * \param unused Not currently used + * \param ap list containing one or more field/operator/value set. + * + * \retval var on success + * \retval NULL on failure +*/ +static struct ast_variable *realtime_curl(const char *url, const char *unused, va_list ap) +{ + struct ast_str *query; + char buf1[200], buf2[200]; + const char *newparam, *newval; + char *stringp, *pair, *key; + int i; + struct ast_variable *var=NULL, *prev=NULL; + const int EncodeSpecialChars = 1; + char *buffer; + + if (!ast_custom_function_find("CURL")) { + ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n"); + return NULL; + } + + if (!(query = ast_str_create(1000))) + return NULL; + + if (!(buffer = ast_malloc(64000))) { + ast_free(query); + return NULL; + } + + ast_str_set(&query, 0, "${CURL(%s,", url); + + for (i = 0; (newparam = va_arg(ap, const char *)); i++) { + newval = va_arg(ap, const char *); + ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars); + ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars); + ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2); + } + va_end(ap); + + ast_str_append(&query, 0, ")}"); + pbx_substitute_variables_helper(NULL, query->str, buffer, sizeof(buffer)); + + /* Remove any trailing newline characters */ + if ((stringp = strchr(buffer, '\r')) || (stringp = strchr(buffer, '\n'))) + *stringp = '\0'; + + stringp = buffer; + while ((pair = strsep(&stringp, "&"))) { + key = strsep(&pair, "="); + ast_uri_decode(key); + if (pair) + ast_uri_decode(pair); + + if (!ast_strlen_zero(key)) { + if (prev) { + prev->next = ast_variable_new(key, S_OR(pair, ""), ""); + if (prev->next) + prev = prev->next; + } else + prev = var = ast_variable_new(key, S_OR(pair, ""), ""); + } + } + + ast_free(buffer); + ast_free(query); + return var; +} + +/*! + * \brief Excute an Select query and return ast_config list + * \param url + * \param unused + * \param ap list containing one or more field/operator/value set. + * + * \retval struct ast_config pointer on success + * \retval NULL on failure +*/ +static struct ast_config *realtime_multi_curl(const char *url, const char *unused, va_list ap) +{ + struct ast_str *query; + char buf1[200], buf2[200]; + const char *newparam, *newval; + char *stringp, *line, *pair, *key, *initfield = NULL; + int i, EncodeSpecialChars = 1; + struct ast_variable *var=NULL; + struct ast_config *cfg=NULL; + struct ast_category *cat=NULL; + char *buffer; + + if (!ast_custom_function_find("CURL")) { + ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n"); + return NULL; + } + + if (!(query = ast_str_create(1000))) + return NULL; + + if (!(buffer = ast_malloc(256000))) { + ast_free(query); + return NULL; + } + + ast_str_set(&query, 0, "${CURL(%s/multi,", url); + + for (i = 0; (newparam = va_arg(ap, const char *)); i++) { + newval = va_arg(ap, const char *); + if (i == 0) + initfield = ast_strdupa(newparam); + ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars); + ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars); + ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2); + } + va_end(ap); + + ast_str_append(&query, 0, ")}"); + + /* Do the CURL query */ + pbx_substitute_variables_helper(NULL, query->str, buffer, sizeof(buffer)); + + if (!(cfg = ast_config_new())) + goto exit_multi; + + /* Line oriented output */ + stringp = buffer; + while ((line = strsep(&stringp, "\r\n"))) { + if (ast_strlen_zero(line)) + continue; + + if (!(cat = ast_category_new("", "", 99999))) + continue; + + while ((pair = strsep(&line, "&"))) { + key = strsep(&pair, "="); + ast_uri_decode(key); + if (pair) + ast_uri_decode(pair); + + if (!strcasecmp(key, initfield) && pair) + ast_category_rename(cat, pair); + + if (!ast_strlen_zero(key)) { + var = ast_variable_new(key, S_OR(pair, ""), ""); + ast_variable_append(cat, var); + } + } + ast_category_append(cfg, cat); + } + +exit_multi: + ast_free(buffer); + ast_free(query); + return cfg; +} + +/*! + * \brief Execute an UPDATE query + * \param url + * \param unused + * \param keyfield where clause field + * \param lookup value of field for where clause + * \param ap list containing one or more field/value set(s). + * + * Update a database table, prepare the sql statement using keyfield and lookup + * control the number of records to change. All values to be changed are stored in ap list. + * Sub-in the values to the prepared statement and execute it. + * + * \retval number of rows affected + * \retval -1 on failure +*/ +static int update_curl(const char *url, const char *unused, const char *keyfield, const char *lookup, va_list ap) +{ + struct ast_str *query; + char buf1[200], buf2[200]; + const char *newparam, *newval; + char *stringp; + int i, rowcount = -1; + const int EncodeSpecialChars = 1; + char *buffer; + + if (!ast_custom_function_find("CURL")) { + ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n"); + return -1; + } + + if (!(query = ast_str_create(1000))) + return -1; + + if (!(buffer = ast_malloc(100))) { + ast_free(query); + return -1; + } + + ast_uri_encode(keyfield, buf1, sizeof(buf1), EncodeSpecialChars); + ast_uri_encode(lookup, buf2, sizeof(buf2), EncodeSpecialChars); + ast_str_set(&query, 0, "${CURL(%s/update?%s=%s,", url, buf1, buf2); + + for (i = 0; (newparam = va_arg(ap, const char *)); i++) { + newval = va_arg(ap, const char *); + ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars); + ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars); + ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2); + } + va_end(ap); + + ast_str_append(&query, 0, ")}"); + pbx_substitute_variables_helper(NULL, query->str, buffer, sizeof(buffer)); + + /* Line oriented output */ + stringp = buffer; + while (*stringp <= ' ') + stringp++; + sscanf(stringp, "%d", &rowcount); + + ast_free(buffer); + ast_free(query); + + if (rowcount >= 0) + return (int)rowcount; + + return -1; +} + +/*! + * \brief Execute an INSERT query + * \param database + * \param table + * \param ap list containing one or more field/value set(s) + * + * Insert a new record into database table, prepare the sql statement. + * All values to be changed are stored in ap list. + * Sub-in the values to the prepared statement and execute it. + * + * \retval number of rows affected + * \retval -1 on failure +*/ +static int store_curl(const char *url, const char *unused, va_list ap) +{ + struct ast_str *query; + char buf1[200], buf2[200]; + const char *newparam, *newval; + char *stringp; + int i, rowcount = -1; + const int EncodeSpecialChars = 1; + char *buffer; + + if (!ast_custom_function_find("CURL")) { + ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n"); + return -1; + } + + if (!(query = ast_str_create(1000))) + return -1; + + if (!(buffer = ast_malloc(100))) { + ast_free(query); + return -1; + } + + ast_str_set(&query, 0, "${CURL(%s/store,", url); + + for (i = 0; (newparam = va_arg(ap, const char *)); i++) { + newval = va_arg(ap, const char *); + ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars); + ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars); + ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2); + } + va_end(ap); + + ast_str_append(&query, 0, ")}"); + pbx_substitute_variables_helper(NULL, query->str, buffer, sizeof(buffer)); + + stringp = buffer; + while (*stringp <= ' ') + stringp++; + sscanf(stringp, "%d", &rowcount); + + ast_free(buffer); + ast_free(query); + + if (rowcount >= 0) + return (int)rowcount; + + return -1; +} + +/*! + * \brief Execute an DELETE query + * \param url + * \param unused + * \param keyfield where clause field + * \param lookup value of field for where clause + * \param ap list containing one or more field/value set(s) + * + * Delete a row from a database table, prepare the sql statement using keyfield and lookup + * control the number of records to change. Additional params to match rows are stored in ap list. + * Sub-in the values to the prepared statement and execute it. + * + * \retval number of rows affected + * \retval -1 on failure +*/ +static int destroy_curl(const char *url, const char *unused, const char *keyfield, const char *lookup, va_list ap) +{ + struct ast_str *query; + char buf1[200], buf2[200]; + const char *newparam, *newval; + char *stringp; + int i, rowcount = -1; + const int EncodeSpecialChars = 1; + char *buffer; + + if (!ast_custom_function_find("CURL")) { + ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n"); + return -1; + } + + if (!(query = ast_str_create(1000))) + return -1; + + if (!(buffer = ast_malloc(100))) { + ast_free(query); + return -1; + } + + ast_uri_encode(keyfield, buf1, sizeof(buf1), EncodeSpecialChars); + ast_uri_encode(lookup, buf2, sizeof(buf2), EncodeSpecialChars); + ast_str_set(&query, 0, "${CURL(%s/destroy,%s=%s&", url, buf1, buf2); + + for (i = 0; (newparam = va_arg(ap, const char *)); i++) { + newval = va_arg(ap, const char *); + ast_uri_encode(newparam, buf1, sizeof(buf1), EncodeSpecialChars); + ast_uri_encode(newval, buf2, sizeof(buf2), EncodeSpecialChars); + ast_str_append(&query, 0, "%s%s=%s", i > 0 ? "&" : "", buf1, buf2); + } + va_end(ap); + + ast_str_append(&query, 0, ")}"); + pbx_substitute_variables_helper(NULL, query->str, buffer, sizeof(buffer)); + + /* Line oriented output */ + stringp = buffer; + while (*stringp <= ' ') + stringp++; + sscanf(stringp, "%d", &rowcount); + + ast_free(buffer); + ast_free(query); + + if (rowcount >= 0) + return (int)rowcount; + + return -1; +} + + +static struct ast_config *config_curl(const char *url, const char *unused, const char *file, struct ast_config *cfg, struct ast_flags flags, const char *sugg_incl) +{ + struct ast_str *query; + char buf1[200]; + char *stringp, *line, *pair, *key; + int EncodeSpecialChars = 1, last_cat_metric = -1, cat_metric = -1; + struct ast_category *cat=NULL; + char *buffer, *cur_cat = ""; + char *category = "", *var_name = "", *var_val = ""; + struct ast_flags loader_flags = { 0 }; + + if (!ast_custom_function_find("CURL")) { + ast_log(LOG_ERROR, "func_curl.so must be loaded in order to use res_config_curl.so!!\n"); + return NULL; + } + + if (!(query = ast_str_create(1000))) + return NULL; + + if (!(buffer = ast_malloc(256000))) { + ast_free(query); + return NULL; + } + + ast_uri_encode(file, buf1, sizeof(buf1), EncodeSpecialChars); + ast_str_set(&query, 0, "${CURL(%s/static?file=%s)}", url, buf1); + + /* Do the CURL query */ + pbx_substitute_variables_helper(NULL, query->str, buffer, sizeof(buffer)); + + /* Line oriented output */ + stringp = buffer; + cat = ast_config_get_current_category(cfg); + + while ((line = strsep(&stringp, "\r\n"))) { + if (ast_strlen_zero(line)) + continue; + + while ((pair = strsep(&line, "&"))) { + key = strsep(&pair, "="); + ast_uri_decode(key); + if (pair) + ast_uri_decode(pair); + + if (!strcasecmp(key, "category")) + category = S_OR(pair, ""); + else if (!strcasecmp(key, "var_name")) + var_name = S_OR(pair, ""); + else if (!strcasecmp(key, "var_val")) + var_val = S_OR(pair, ""); + else if (!strcasecmp(key, "cat_metric")) + cat_metric = pair ? atoi(pair) : 0; + } + + if (!strcmp(var_name, "#include")) { + if (!ast_config_internal_load(var_val, cfg, loader_flags, "")) + return NULL; + } + + if (strcmp(category, cur_cat) || last_cat_metric != cat_metric) { + if (!(cat = ast_category_new(category, "", 99999))) + break; + cur_cat = category; + last_cat_metric = cat_metric; + ast_category_append(cfg, cat); + } + ast_variable_append(cat, ast_variable_new(var_name, var_val, "")); + } + + ast_free(buffer); + ast_free(query); + return cfg; +} + +static struct ast_config_engine curl_engine = { + .name = "curl", + .load_func = config_curl, + .realtime_func = realtime_curl, + .realtime_multi_func = realtime_multi_curl, + .store_func = store_curl, + .destroy_func = destroy_curl, + .update_func = update_curl +}; + +static int unload_module (void) +{ + ast_config_engine_deregister(&curl_engine); + ast_verb(1, "res_config_curl unloaded.\n"); + return 0; +} + +static int load_module (void) +{ + ast_config_engine_register(&curl_engine); + ast_verb(1, "res_config_curl loaded.\n"); + return 0; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Realtime Curl configuration"); diff --git a/trunk/res/res_config_odbc.c b/trunk/res/res_config_odbc.c new file mode 100644 index 000000000..57b1ba528 --- /dev/null +++ b/trunk/res/res_config_odbc.c @@ -0,0 +1,739 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * + * Copyright (C) 2004 - 2005 Anthony Minessale II <anthmct@yahoo.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief odbc+odbc plugin for portable configuration engine + * + * \author Mark Spencer <markster@digium.com> + * \author Anthony Minessale II <anthmct@yahoo.com> + * + * \arg http://www.unixodbc.org + */ + +/*** MODULEINFO + <depend>unixodbc</depend> + <depend>ltdl</depend> + <depend>res_odbc</depend> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/config.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/res_odbc.h" +#include "asterisk/utils.h" + +struct custom_prepare_struct { + const char *sql; + const char *extra; + va_list ap; +}; + +static SQLHSTMT custom_prepare(struct odbc_obj *obj, void *data) +{ + int res, x = 1; + struct custom_prepare_struct *cps = data; + const char *newparam, *newval; + SQLHSTMT stmt; + va_list ap; + + va_copy(ap, cps->ap); + + res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n"); + return NULL; + } + + res = SQLPrepare(stmt, (unsigned char *)cps->sql, SQL_NTS); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", cps->sql); + SQLFreeHandle (SQL_HANDLE_STMT, stmt); + return NULL; + } + + while ((newparam = va_arg(ap, const char *))) { + newval = va_arg(ap, const char *); + SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(newval), 0, (void *)newval, 0, NULL); + } + va_end(ap); + + if (!ast_strlen_zero(cps->extra)) + SQLBindParameter(stmt, x++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(cps->extra), 0, (void *)cps->extra, 0, NULL); + return stmt; +} + +/*! + * \brief Excute an SQL query and return ast_variable list + * \param database + * \param table + * \param ap list containing one or more field/operator/value set. + * + * Select database and preform query on table, prepare the sql statement + * Sub-in the values to the prepared statement and execute it. Return results + * as a ast_variable list. + * + * \retval var on success + * \retval NULL on failure +*/ +static struct ast_variable *realtime_odbc(const char *database, const char *table, va_list ap) +{ + struct odbc_obj *obj; + SQLHSTMT stmt; + char sql[1024]; + char coltitle[256]; + char rowdata[2048]; + char *op; + const char *newparam, *newval; + char *stringp; + char *chunk; + SQLSMALLINT collen; + int res; + int x; + struct ast_variable *var=NULL, *prev=NULL; + SQLULEN colsize; + SQLSMALLINT colcount=0; + SQLSMALLINT datatype; + SQLSMALLINT decimaldigits; + SQLSMALLINT nullable; + SQLLEN indicator; + va_list aq; + struct custom_prepare_struct cps = { .sql = sql }; + + va_copy(cps.ap, ap); + va_copy(aq, ap); + + if (!table) + return NULL; + + obj = ast_odbc_request_obj(database, 0); + + if (!obj) { + ast_log(LOG_ERROR, "No database handle available with the name of '%s' (check res_odbc.conf)\n", database); + return NULL; + } + + newparam = va_arg(aq, const char *); + if (!newparam) + return NULL; + newval = va_arg(aq, const char *); + op = !strchr(newparam, ' ') ? " =" : ""; + snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?%s", table, newparam, op, + strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : ""); + while((newparam = va_arg(aq, const char *))) { + op = !strchr(newparam, ' ') ? " =" : ""; + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?%s", newparam, op, + strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : ""); + newval = va_arg(aq, const char *); + } + va_end(aq); + + stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps); + + if (!stmt) { + ast_odbc_release_obj(obj); + return NULL; + } + + res = SQLNumResultCols(stmt, &colcount); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql); + SQLFreeHandle (SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + return NULL; + } + + res = SQLFetch(stmt); + if (res == SQL_NO_DATA) { + SQLFreeHandle (SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + return NULL; + } + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql); + SQLFreeHandle (SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + return NULL; + } + for (x = 0; x < colcount; x++) { + rowdata[0] = '\0'; + collen = sizeof(coltitle); + res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen, + &datatype, &colsize, &decimaldigits, &nullable); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql); + if (var) + ast_variables_destroy(var); + ast_odbc_release_obj(obj); + return NULL; + } + + indicator = 0; + res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), &indicator); + if (indicator == SQL_NULL_DATA) + rowdata[0] = '\0'; + else if (ast_strlen_zero(rowdata)) { + /* Because we encode the empty string for a NULL, we will encode + * actual empty strings as a string containing a single whitespace. */ + ast_copy_string(rowdata, " ", sizeof(rowdata)); + } + + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql); + if (var) + ast_variables_destroy(var); + ast_odbc_release_obj(obj); + return NULL; + } + stringp = rowdata; + while(stringp) { + chunk = strsep(&stringp, ";"); + if (!ast_strlen_zero(ast_strip(chunk))) { + if (prev) { + prev->next = ast_variable_new(coltitle, chunk, ""); + if (prev->next) + prev = prev->next; + } else + prev = var = ast_variable_new(coltitle, chunk, ""); + } + } + } + + + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + return var; +} + +/*! + * \brief Excute an Select query and return ast_config list + * \param database + * \param table + * \param ap list containing one or more field/operator/value set. + * + * Select database and preform query on table, prepare the sql statement + * Sub-in the values to the prepared statement and execute it. + * Execute this prepared query against several ODBC connected databases. + * Return results as an ast_config variable. + * + * \retval var on success + * \retval NULL on failure +*/ +static struct ast_config *realtime_multi_odbc(const char *database, const char *table, va_list ap) +{ + struct odbc_obj *obj; + SQLHSTMT stmt; + char sql[1024]; + char coltitle[256]; + char rowdata[2048]; + const char *initfield=NULL; + char *op; + const char *newparam, *newval; + char *stringp; + char *chunk; + SQLSMALLINT collen; + int res; + int x; + struct ast_variable *var=NULL; + struct ast_config *cfg=NULL; + struct ast_category *cat=NULL; + SQLULEN colsize; + SQLSMALLINT colcount=0; + SQLSMALLINT datatype; + SQLSMALLINT decimaldigits; + SQLSMALLINT nullable; + SQLLEN indicator; + struct custom_prepare_struct cps = { .sql = sql }; + va_list aq; + + va_copy(cps.ap, ap); + va_copy(aq, ap); + + if (!table) + return NULL; + + obj = ast_odbc_request_obj(database, 0); + if (!obj) + return NULL; + + newparam = va_arg(aq, const char *); + if (!newparam) { + ast_odbc_release_obj(obj); + return NULL; + } + initfield = ast_strdupa(newparam); + if ((op = strchr(initfield, ' '))) + *op = '\0'; + newval = va_arg(aq, const char *); + op = !strchr(newparam, ' ') ? " =" : ""; + snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s ?%s", table, newparam, op, + strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : ""); + while((newparam = va_arg(aq, const char *))) { + op = !strchr(newparam, ' ') ? " =" : ""; + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s ?%s", newparam, op, + strcasestr(newparam, "LIKE") && !ast_odbc_backslash_is_escape(obj) ? " ESCAPE '\\'" : ""); + newval = va_arg(aq, const char *); + } + if (initfield) + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield); + va_end(aq); + + stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps); + + if (!stmt) { + ast_odbc_release_obj(obj); + return NULL; + } + + res = SQLNumResultCols(stmt, &colcount); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + return NULL; + } + + cfg = ast_config_new(); + if (!cfg) { + ast_log(LOG_WARNING, "Out of memory!\n"); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + return NULL; + } + + while ((res=SQLFetch(stmt)) != SQL_NO_DATA) { + var = NULL; + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql); + continue; + } + cat = ast_category_new("","",99999); + if (!cat) { + ast_log(LOG_WARNING, "Out of memory!\n"); + continue; + } + for (x=0;x<colcount;x++) { + rowdata[0] = '\0'; + collen = sizeof(coltitle); + res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen, + &datatype, &colsize, &decimaldigits, &nullable); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql); + ast_category_destroy(cat); + continue; + } + + indicator = 0; + res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), &indicator); + if (indicator == SQL_NULL_DATA) + continue; + + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql); + ast_category_destroy(cat); + continue; + } + stringp = rowdata; + while(stringp) { + chunk = strsep(&stringp, ";"); + if (!ast_strlen_zero(ast_strip(chunk))) { + if (initfield && !strcmp(initfield, coltitle)) + ast_category_rename(cat, chunk); + var = ast_variable_new(coltitle, chunk, ""); + ast_variable_append(cat, var); + } + } + } + ast_category_append(cfg, cat); + } + + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + return cfg; +} + +/*! + * \brief Excute an UPDATE query + * \param database + * \param table + * \param keyfield where clause field + * \param lookup value of field for where clause + * \param ap list containing one or more field/value set(s). + * + * Update a database table, prepare the sql statement using keyfield and lookup + * control the number of records to change. All values to be changed are stored in ap list. + * Sub-in the values to the prepared statement and execute it. + * + * \retval number of rows affected + * \retval -1 on failure +*/ +static int update_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap) +{ + struct odbc_obj *obj; + SQLHSTMT stmt; + char sql[256]; + SQLLEN rowcount=0; + const char *newparam, *newval; + int res; + va_list aq; + struct custom_prepare_struct cps = { .sql = sql, .extra = lookup }; + + va_copy(cps.ap, ap); + va_copy(aq, ap); + + if (!table) + return -1; + + obj = ast_odbc_request_obj(database, 0); + if (!obj) + return -1; + + newparam = va_arg(aq, const char *); + if (!newparam) { + ast_odbc_release_obj(obj); + return -1; + } + newval = va_arg(aq, const char *); + snprintf(sql, sizeof(sql), "UPDATE %s SET %s=?", table, newparam); + while((newparam = va_arg(aq, const char *))) { + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s=?", newparam); + newval = va_arg(aq, const char *); + } + va_end(aq); + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s=?", keyfield); + + stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps); + + if (!stmt) { + ast_odbc_release_obj(obj); + return -1; + } + + res = SQLRowCount(stmt, &rowcount); + SQLFreeHandle (SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql); + return -1; + } + + if (rowcount >= 0) + return (int)rowcount; + + return -1; +} + +/*! + * \brief Excute an INSERT query + * \param database + * \param table + * \param ap list containing one or more field/value set(s) + * + * Insert a new record into database table, prepare the sql statement. + * All values to be changed are stored in ap list. + * Sub-in the values to the prepared statement and execute it. + * + * \retval number of rows affected + * \retval -1 on failure +*/ +static int store_odbc(const char *database, const char *table, va_list ap) +{ + struct odbc_obj *obj; + SQLHSTMT stmt; + char sql[256]; + char keys[256]; + char vals[256]; + SQLLEN rowcount=0; + const char *newparam, *newval; + int res; + va_list aq; + struct custom_prepare_struct cps = { .sql = sql, .extra = NULL }; + + va_copy(cps.ap, ap); + va_copy(aq, ap); + + if (!table) + return -1; + + obj = ast_odbc_request_obj(database, 0); + if (!obj) + return -1; + + newparam = va_arg(aq, const char *); + if (!newparam) { + ast_odbc_release_obj(obj); + return -1; + } + newval = va_arg(aq, const char *); + snprintf(keys, sizeof(keys), "%s", newparam); + ast_copy_string(vals, "?", sizeof(vals)); + while ((newparam = va_arg(aq, const char *))) { + snprintf(keys + strlen(keys), sizeof(keys) - strlen(keys), ", %s", newparam); + snprintf(vals + strlen(vals), sizeof(vals) - strlen(vals), ", ?"); + newval = va_arg(aq, const char *); + } + va_end(aq); + snprintf(sql, sizeof(sql), "INSERT INTO %s (%s) VALUES (%s)", table, keys, vals); + + stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps); + + if (!stmt) { + ast_odbc_release_obj(obj); + return -1; + } + + res = SQLRowCount(stmt, &rowcount); + SQLFreeHandle (SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql); + return -1; + } + + if (rowcount >= 0) + return (int)rowcount; + + return -1; +} + +/*! + * \brief Excute an DELETE query + * \param database + * \param table + * \param keyfield where clause field + * \param lookup value of field for where clause + * \param ap list containing one or more field/value set(s) + * + * Delete a row from a database table, prepare the sql statement using keyfield and lookup + * control the number of records to change. Additional params to match rows are stored in ap list. + * Sub-in the values to the prepared statement and execute it. + * + * \retval number of rows affected + * \retval -1 on failure +*/ +static int destroy_odbc(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap) +{ + struct odbc_obj *obj; + SQLHSTMT stmt; + char sql[256]; + SQLLEN rowcount=0; + const char *newparam, *newval; + int res; + va_list aq; + struct custom_prepare_struct cps = { .sql = sql, .extra = lookup }; + + va_copy(cps.ap, ap); + va_copy(aq, ap); + + if (!table) + return -1; + + obj = ast_odbc_request_obj(database, 0); + if (!obj) + return -1; + + snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE ", table); + while((newparam = va_arg(aq, const char *))) { + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=? AND ", newparam); + newval = va_arg(aq, const char *); + } + va_end(aq); + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s=?", keyfield); + + stmt = ast_odbc_prepare_and_execute(obj, custom_prepare, &cps); + + if (!stmt) { + ast_odbc_release_obj(obj); + return -1; + } + + res = SQLRowCount(stmt, &rowcount); + SQLFreeHandle (SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL Row Count error!\n[%s]\n\n", sql); + return -1; + } + + if (rowcount >= 0) + return (int)rowcount; + + return -1; +} + + +struct config_odbc_obj { + char *sql; + unsigned long cat_metric; + char category[128]; + char var_name[128]; + char var_val[1024]; /* changed from 128 to 1024 via bug 8251 */ + SQLLEN err; +}; + +static SQLHSTMT config_odbc_prepare(struct odbc_obj *obj, void *data) +{ + struct config_odbc_obj *q = data; + SQLHSTMT sth; + int res; + + res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &sth); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_verb(4, "Failure in AllocStatement %d\n", res); + return NULL; + } + + res = SQLPrepare(sth, (unsigned char *)q->sql, SQL_NTS); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_verb(4, "Error in PREPARE %d\n", res); + SQLFreeHandle(SQL_HANDLE_STMT, sth); + return NULL; + } + + SQLBindCol(sth, 1, SQL_C_ULONG, &q->cat_metric, sizeof(q->cat_metric), &q->err); + SQLBindCol(sth, 2, SQL_C_CHAR, q->category, sizeof(q->category), &q->err); + SQLBindCol(sth, 3, SQL_C_CHAR, q->var_name, sizeof(q->var_name), &q->err); + SQLBindCol(sth, 4, SQL_C_CHAR, q->var_val, sizeof(q->var_val), &q->err); + + return sth; +} + +static struct ast_config *config_odbc(const char *database, const char *table, const char *file, struct ast_config *cfg, struct ast_flags flags, const char *sugg_incl) +{ + struct ast_variable *new_v; + struct ast_category *cur_cat; + int res = 0; + struct odbc_obj *obj; + char sqlbuf[1024] = ""; + char *sql = sqlbuf; + size_t sqlleft = sizeof(sqlbuf); + unsigned int last_cat_metric = 0; + SQLSMALLINT rowcount = 0; + SQLHSTMT stmt; + char last[128] = ""; + struct config_odbc_obj q; + struct ast_flags loader_flags = { 0 }; + + memset(&q, 0, sizeof(q)); + + if (!file || !strcmp (file, "res_config_odbc.conf")) + return NULL; /* cant configure myself with myself ! */ + + obj = ast_odbc_request_obj(database, 0); + if (!obj) + return NULL; + + ast_build_string(&sql, &sqlleft, "SELECT cat_metric, category, var_name, var_val FROM %s ", table); + ast_build_string(&sql, &sqlleft, "WHERE filename='%s' AND commented=0 ", file); + ast_build_string(&sql, &sqlleft, "ORDER BY cat_metric DESC, var_metric ASC, category, var_name "); + q.sql = sqlbuf; + + stmt = ast_odbc_prepare_and_execute(obj, config_odbc_prepare, &q); + + if (!stmt) { + ast_log(LOG_WARNING, "SQL select error!\n[%s]\n\n", sql); + ast_odbc_release_obj(obj); + return NULL; + } + + res = SQLNumResultCols(stmt, &rowcount); + + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "SQL NumResultCols error!\n[%s]\n\n", sql); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + return NULL; + } + + if (!rowcount) { + ast_log(LOG_NOTICE, "found nothing\n"); + ast_odbc_release_obj(obj); + return cfg; + } + + cur_cat = ast_config_get_current_category(cfg); + + while ((res = SQLFetch(stmt)) != SQL_NO_DATA) { + if (!strcmp (q.var_name, "#include")) { + if (!ast_config_internal_load(q.var_val, cfg, loader_flags, "")) { + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + return NULL; + } + continue; + } + if (strcmp(last, q.category) || last_cat_metric != q.cat_metric) { + cur_cat = ast_category_new(q.category, "", 99999); + if (!cur_cat) { + ast_log(LOG_WARNING, "Out of memory!\n"); + break; + } + strcpy(last, q.category); + last_cat_metric = q.cat_metric; + ast_category_append(cfg, cur_cat); + } + + new_v = ast_variable_new(q.var_name, q.var_val, ""); + ast_variable_append(cur_cat, new_v); + } + + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + return cfg; +} + +static struct ast_config_engine odbc_engine = { + .name = "odbc", + .load_func = config_odbc, + .realtime_func = realtime_odbc, + .realtime_multi_func = realtime_multi_odbc, + .store_func = store_odbc, + .destroy_func = destroy_odbc, + .update_func = update_odbc +}; + +static int unload_module (void) +{ + ast_config_engine_deregister(&odbc_engine); + ast_verb(1, "res_config_odbc unloaded.\n"); + return 0; +} + +static int load_module (void) +{ + ast_config_engine_register(&odbc_engine); + ast_verb(1, "res_config_odbc loaded.\n"); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Realtime ODBC configuration", + .load = load_module, + .unload = unload_module, + ); diff --git a/trunk/res/res_config_pgsql.c b/trunk/res/res_config_pgsql.c new file mode 100644 index 000000000..81da43456 --- /dev/null +++ b/trunk/res/res_config_pgsql.c @@ -0,0 +1,1008 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Copyright (C) 1999-2005, Digium, Inc. + * + * Manuel Guesdon <mguesdon@oxymium.net> - PostgreSQL RealTime Driver Author/Adaptor + * Mark Spencer <markster@digium.com> - Asterisk Author + * Matthew Boehm <mboehm@cytelcom.com> - MySQL RealTime Driver Author + * + * res_config_pgsql.c <PostgreSQL plugin for RealTime configuration engine> + * + * v1.0 - (07-11-05) - Initial version based on res_config_mysql v2.0 + */ + +/*! \file + * + * \brief PostgreSQL plugin for Asterisk RealTime Architecture + * + * \author Mark Spencer <markster@digium.com> + * \author Manuel Guesdon <mguesdon@oxymium.net> - PostgreSQL RealTime Driver Author/Adaptor + * + * \arg http://www.postgresql.org + */ + +/*** MODULEINFO + <depend>pgsql</depend> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <libpq-fe.h> /* PostgreSQL */ + +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/config.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/utils.h" +#include "asterisk/cli.h" + +AST_MUTEX_DEFINE_STATIC(pgsql_lock); + +#define RES_CONFIG_PGSQL_CONF "res_pgsql.conf" + +PGconn *pgsqlConn = NULL; + +#define MAX_DB_OPTION_SIZE 64 + +static char dbhost[MAX_DB_OPTION_SIZE] = ""; +static char dbuser[MAX_DB_OPTION_SIZE] = ""; +static char dbpass[MAX_DB_OPTION_SIZE] = ""; +static char dbname[MAX_DB_OPTION_SIZE] = ""; +static char dbsock[MAX_DB_OPTION_SIZE] = ""; +static int dbport = 5432; +static time_t connect_time = 0; + +static int parse_config(int reload); +static int pgsql_reconnect(const char *database); +static char *handle_cli_realtime_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); + +static struct ast_cli_entry cli_realtime[] = { + AST_CLI_DEFINE(handle_cli_realtime_pgsql_status, "Shows connection information for the PostgreSQL RealTime driver"), +}; + +static struct ast_variable *realtime_pgsql(const char *database, const char *table, va_list ap) +{ + PGresult *result = NULL; + int num_rows = 0, pgerror; + char sql[256], escapebuf[513]; + char *stringp; + char *chunk; + char *op; + const char *newparam, *newval; + struct ast_variable *var = NULL, *prev = NULL; + + if (!table) { + ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n"); + return NULL; + } + + /* Get the first parameter and first value in our list of passed paramater/value pairs */ + newparam = va_arg(ap, const char *); + newval = va_arg(ap, const char *); + if (!newparam || !newval) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n"); + if (pgsqlConn) { + PQfinish(pgsqlConn); + pgsqlConn = NULL; + }; + return NULL; + } + + /* Create the first part of the query using the first parameter/value pairs we just extracted + If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ + op = strchr(newparam, ' ') ? "" : " ="; + + PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror); + if (pgerror) { + ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval); + va_end(ap); + return NULL; + } + + snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, + escapebuf); + while ((newparam = va_arg(ap, const char *))) { + newval = va_arg(ap, const char *); + if (!strchr(newparam, ' ')) + op = " ="; + else + op = ""; + + PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror); + if (pgerror) { + ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval); + va_end(ap); + return NULL; + } + + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam, + op, escapebuf); + } + va_end(ap); + + /* We now have our complete statement; Lets connect to the server and execute it. */ + ast_mutex_lock(&pgsql_lock); + if (!pgsql_reconnect(database)) { + ast_mutex_unlock(&pgsql_lock); + return NULL; + } + + if (!(result = PQexec(pgsqlConn, sql))) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql); + ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn)); + ast_mutex_unlock(&pgsql_lock); + return NULL; + } else { + ExecStatusType result_status = PQresultStatus(result); + if (result_status != PGRES_COMMAND_OK + && result_status != PGRES_TUPLES_OK + && result_status != PGRES_NONFATAL_ERROR) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql); + ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n", + PQresultErrorMessage(result), PQresStatus(result_status)); + ast_mutex_unlock(&pgsql_lock); + return NULL; + } + } + + ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, sql); + + if ((num_rows = PQntuples(result)) > 0) { + int i = 0; + int rowIndex = 0; + int numFields = PQnfields(result); + char **fieldnames = NULL; + + ast_debug(1, "PostgreSQL RealTime: Found %d rows.\n", num_rows); + + if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) { + ast_mutex_unlock(&pgsql_lock); + PQclear(result); + return NULL; + } + for (i = 0; i < numFields; i++) + fieldnames[i] = PQfname(result, i); + for (rowIndex = 0; rowIndex < num_rows; rowIndex++) { + for (i = 0; i < numFields; i++) { + stringp = PQgetvalue(result, rowIndex, i); + while (stringp) { + chunk = strsep(&stringp, ";"); + if (!ast_strlen_zero(ast_strip(chunk))) { + if (prev) { + prev->next = ast_variable_new(fieldnames[i], chunk, ""); + if (prev->next) { + prev = prev->next; + } + } else { + prev = var = ast_variable_new(fieldnames[i], chunk, ""); + } + } + } + } + } + ast_free(fieldnames); + } else { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Could not find any rows in table %s.\n", table); + } + + ast_mutex_unlock(&pgsql_lock); + PQclear(result); + + return var; +} + +static struct ast_config *realtime_multi_pgsql(const char *database, const char *table, va_list ap) +{ + PGresult *result = NULL; + int num_rows = 0, pgerror; + char sql[256], escapebuf[513]; + const char *initfield = NULL; + char *stringp; + char *chunk; + char *op; + const char *newparam, *newval; + struct ast_variable *var = NULL; + struct ast_config *cfg = NULL; + struct ast_category *cat = NULL; + + if (!table) { + ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n"); + return NULL; + } + + if (!(cfg = ast_config_new())) + return NULL; + + /* Get the first parameter and first value in our list of passed paramater/value pairs */ + newparam = va_arg(ap, const char *); + newval = va_arg(ap, const char *); + if (!newparam || !newval) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n"); + if (pgsqlConn) { + PQfinish(pgsqlConn); + pgsqlConn = NULL; + }; + return NULL; + } + + initfield = ast_strdupa(newparam); + if ((op = strchr(initfield, ' '))) { + *op = '\0'; + } + + /* Create the first part of the query using the first parameter/value pairs we just extracted + If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ + + if (!strchr(newparam, ' ')) + op = " ="; + else + op = ""; + + PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror); + if (pgerror) { + ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval); + va_end(ap); + return NULL; + } + + snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE %s%s '%s'", table, newparam, op, + escapebuf); + while ((newparam = va_arg(ap, const char *))) { + newval = va_arg(ap, const char *); + if (!strchr(newparam, ' ')) + op = " ="; + else + op = ""; + + PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror); + if (pgerror) { + ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval); + va_end(ap); + return NULL; + } + + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s%s '%s'", newparam, + op, escapebuf); + } + + if (initfield) { + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " ORDER BY %s", initfield); + } + + va_end(ap); + + /* We now have our complete statement; Lets connect to the server and execute it. */ + ast_mutex_lock(&pgsql_lock); + if (!pgsql_reconnect(database)) { + ast_mutex_unlock(&pgsql_lock); + return NULL; + } + + if (!(result = PQexec(pgsqlConn, sql))) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql); + ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn)); + ast_mutex_unlock(&pgsql_lock); + return NULL; + } else { + ExecStatusType result_status = PQresultStatus(result); + if (result_status != PGRES_COMMAND_OK + && result_status != PGRES_TUPLES_OK + && result_status != PGRES_NONFATAL_ERROR) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql); + ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n", + PQresultErrorMessage(result), PQresStatus(result_status)); + ast_mutex_unlock(&pgsql_lock); + return NULL; + } + } + + ast_debug(1, "PostgreSQL RealTime: Result=%p Query: %s\n", result, sql); + + if ((num_rows = PQntuples(result)) > 0) { + int numFields = PQnfields(result); + int i = 0; + int rowIndex = 0; + char **fieldnames = NULL; + + ast_debug(1, "PostgreSQL RealTime: Found %d rows.\n", num_rows); + + if (!(fieldnames = ast_calloc(1, numFields * sizeof(char *)))) { + ast_mutex_unlock(&pgsql_lock); + PQclear(result); + return NULL; + } + for (i = 0; i < numFields; i++) + fieldnames[i] = PQfname(result, i); + + for (rowIndex = 0; rowIndex < num_rows; rowIndex++) { + var = NULL; + if (!(cat = ast_category_new("","",99999))) + continue; + for (i = 0; i < numFields; i++) { + stringp = PQgetvalue(result, rowIndex, i); + while (stringp) { + chunk = strsep(&stringp, ";"); + if (!ast_strlen_zero(ast_strip(chunk))) { + if (initfield && !strcmp(initfield, fieldnames[i])) { + ast_category_rename(cat, chunk); + } + var = ast_variable_new(fieldnames[i], chunk, ""); + ast_variable_append(cat, var); + } + } + } + ast_category_append(cfg, cat); + } + ast_free(fieldnames); + } else { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Could not find any rows in table %s.\n", table); + } + + ast_mutex_unlock(&pgsql_lock); + PQclear(result); + + return cfg; +} + +static int update_pgsql(const char *database, const char *table, const char *keyfield, + const char *lookup, va_list ap) +{ + PGresult *result = NULL; + int numrows = 0, pgerror; + char sql[256], escapebuf[513]; + const char *newparam, *newval; + + if (!table) { + ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n"); + return -1; + } + + /* Get the first parameter and first value in our list of passed paramater/value pairs */ + newparam = va_arg(ap, const char *); + newval = va_arg(ap, const char *); + if (!newparam || !newval) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n"); + if (pgsqlConn) { + PQfinish(pgsqlConn); + pgsqlConn = NULL; + }; + return -1; + } + + /* Create the first part of the query using the first parameter/value pairs we just extracted + If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ + + PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror); + if (pgerror) { + ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval); + va_end(ap); + return -1; + } + snprintf(sql, sizeof(sql), "UPDATE %s SET %s = '%s'", table, newparam, escapebuf); + + while ((newparam = va_arg(ap, const char *))) { + newval = va_arg(ap, const char *); + + PQescapeStringConn(pgsqlConn, escapebuf, newval, (sizeof(escapebuf) - 1) / 2, &pgerror); + if (pgerror) { + ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", newval); + va_end(ap); + return -1; + } + + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ", %s = '%s'", newparam, + escapebuf); + } + va_end(ap); + + PQescapeStringConn(pgsqlConn, escapebuf, lookup, (sizeof(escapebuf) - 1) / 2, &pgerror); + if (pgerror) { + ast_log(LOG_ERROR, "Postgres detected invalid input: '%s'\n", lookup); + va_end(ap); + return -1; + } + + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " WHERE %s = '%s'", keyfield, + escapebuf); + + ast_debug(1, "PostgreSQL RealTime: Update SQL: %s\n", sql); + + /* We now have our complete statement; Lets connect to the server and execute it. */ + ast_mutex_lock(&pgsql_lock); + if (!pgsql_reconnect(database)) { + ast_mutex_unlock(&pgsql_lock); + return -1; + } + + if (!(result = PQexec(pgsqlConn, sql))) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql); + ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn)); + ast_mutex_unlock(&pgsql_lock); + return -1; + } else { + ExecStatusType result_status = PQresultStatus(result); + if (result_status != PGRES_COMMAND_OK + && result_status != PGRES_TUPLES_OK + && result_status != PGRES_NONFATAL_ERROR) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql); + ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n", + PQresultErrorMessage(result), PQresStatus(result_status)); + ast_mutex_unlock(&pgsql_lock); + return -1; + } + } + + numrows = atoi(PQcmdTuples(result)); + ast_mutex_unlock(&pgsql_lock); + + ast_debug(1, "PostgreSQL RealTime: Updated %d rows on table: %s\n", numrows, table); + + /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html + * An integer greater than zero indicates the number of rows affected + * Zero indicates that no records were updated + * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.) + */ + + if (numrows >= 0) + return (int) numrows; + + return -1; +} + +static int store_pgsql(const char *database, const char *table, va_list ap) +{ + PGresult *result = NULL; + Oid insertid; + char sql[256]; + char params[256]; + char vals[256]; + char buf[256]; + int pgresult; + const char *newparam, *newval; + + if (!table) { + ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n"); + return -1; + } + + /* Get the first parameter and first value in our list of passed paramater/value pairs */ + newparam = va_arg(ap, const char *); + newval = va_arg(ap, const char *); + if (!newparam || !newval) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Realtime storage requires at least 1 parameter and 1 value to store.\n"); + if (pgsqlConn) { + PQfinish(pgsqlConn); + pgsqlConn = NULL; + }; + return -1; + } + + /* Must connect to the server before anything else, as the escape function requires the connection handle.. */ + ast_mutex_lock(&pgsql_lock); + if (!pgsql_reconnect(database)) { + ast_mutex_unlock(&pgsql_lock); + return -1; + } + + /* Create the first part of the query using the first parameter/value pairs we just extracted + If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ + PQescapeStringConn(pgsqlConn, buf, newparam, sizeof(newparam), &pgresult); + snprintf(params, sizeof(params), "%s", buf); + PQescapeStringConn(pgsqlConn, buf, newval, sizeof(newval), &pgresult); + snprintf(vals, sizeof(vals), "'%s'", buf); + while ((newparam = va_arg(ap, const char *))) { + newval = va_arg(ap, const char *); + PQescapeStringConn(pgsqlConn, buf, newparam, sizeof(newparam), &pgresult); + snprintf(params + strlen(params), sizeof(params) - strlen(params), ", %s", buf); + PQescapeStringConn(pgsqlConn, buf, newval, sizeof(newval), &pgresult); + snprintf(vals + strlen(vals), sizeof(vals) - strlen(vals), ", '%s'", buf); + } + va_end(ap); + snprintf(sql, sizeof(sql), "INSERT INTO (%s) VALUES (%s)", params, vals); + + ast_debug(1, "PostgreSQL RealTime: Insert SQL: %s\n", sql); + + if (!(result = PQexec(pgsqlConn, sql))) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql); + ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn)); + ast_mutex_unlock(&pgsql_lock); + return -1; + } else { + ExecStatusType result_status = PQresultStatus(result); + if (result_status != PGRES_COMMAND_OK + && result_status != PGRES_TUPLES_OK + && result_status != PGRES_NONFATAL_ERROR) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql); + ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n", + PQresultErrorMessage(result), PQresStatus(result_status)); + ast_mutex_unlock(&pgsql_lock); + return -1; + } + } + + insertid = PQoidValue(result); + ast_mutex_unlock(&pgsql_lock); + + ast_debug(1, "PostgreSQL RealTime: row inserted on table: %s, id: %u\n", table, insertid); + + /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html + * An integer greater than zero indicates the number of rows affected + * Zero indicates that no records were updated + * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.) + */ + + if (insertid >= 0) + return (int) insertid; + + return -1; +} + +static int destroy_pgsql(const char *database, const char *table, const char *keyfield, const char *lookup, va_list ap) +{ + PGresult *result = NULL; + int numrows = 0; + int pgresult; + char sql[256]; + char buf[256], buf2[256]; + const char *newparam, *newval; + + if (!table) { + ast_log(LOG_WARNING, "PostgreSQL RealTime: No table specified.\n"); + return -1; + } + + /* Get the first parameter and first value in our list of passed paramater/value pairs */ + /*newparam = va_arg(ap, const char *); + newval = va_arg(ap, const char *); + if (!newparam || !newval) {*/ + if (ast_strlen_zero(keyfield) || ast_strlen_zero(lookup)) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Realtime destroy requires at least 1 parameter and 1 value to search on.\n"); + if (pgsqlConn) { + PQfinish(pgsqlConn); + pgsqlConn = NULL; + }; + return -1; + } + + /* Must connect to the server before anything else, as the escape function requires the connection handle.. */ + ast_mutex_lock(&pgsql_lock); + if (!pgsql_reconnect(database)) { + ast_mutex_unlock(&pgsql_lock); + return -1; + } + + + /* Create the first part of the query using the first parameter/value pairs we just extracted + If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */ + + PQescapeStringConn(pgsqlConn, buf, keyfield, sizeof(keyfield), &pgresult); + PQescapeStringConn(pgsqlConn, buf2, lookup, sizeof(lookup), &pgresult); + snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE %s = '%s'", table, buf, buf2); + while ((newparam = va_arg(ap, const char *))) { + newval = va_arg(ap, const char *); + PQescapeStringConn(pgsqlConn, buf, newparam, sizeof(newparam), &pgresult); + PQescapeStringConn(pgsqlConn, buf2, newval, sizeof(newval), &pgresult); + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), " AND %s = '%s'", buf, buf2); + } + va_end(ap); + + ast_debug(1, "PostgreSQL RealTime: Delete SQL: %s\n", sql); + + if (!(result = PQexec(pgsqlConn, sql))) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql); + ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn)); + ast_mutex_unlock(&pgsql_lock); + return -1; + } else { + ExecStatusType result_status = PQresultStatus(result); + if (result_status != PGRES_COMMAND_OK + && result_status != PGRES_TUPLES_OK + && result_status != PGRES_NONFATAL_ERROR) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql); + ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n", + PQresultErrorMessage(result), PQresStatus(result_status)); + ast_mutex_unlock(&pgsql_lock); + return -1; + } + } + + numrows = atoi(PQcmdTuples(result)); + ast_mutex_unlock(&pgsql_lock); + + ast_debug(1, "PostgreSQL RealTime: Deleted %d rows on table: %s\n", numrows, table); + + /* From http://dev.pgsql.com/doc/pgsql/en/pgsql-affected-rows.html + * An integer greater than zero indicates the number of rows affected + * Zero indicates that no records were updated + * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.) + */ + + if (numrows >= 0) + return (int) numrows; + + return -1; +} + + +static struct ast_config *config_pgsql(const char *database, const char *table, + const char *file, struct ast_config *cfg, + struct ast_flags flags, const char *suggested_incl) +{ + PGresult *result = NULL; + long num_rows; + struct ast_variable *new_v; + struct ast_category *cur_cat = NULL; + char sqlbuf[1024] = ""; + char *sql = sqlbuf; + size_t sqlleft = sizeof(sqlbuf); + char last[80] = ""; + int last_cat_metric = 0; + + last[0] = '\0'; + + if (!file || !strcmp(file, RES_CONFIG_PGSQL_CONF)) { + ast_log(LOG_WARNING, "PostgreSQL RealTime: Cannot configure myself.\n"); + return NULL; + } + + ast_build_string(&sql, &sqlleft, "SELECT category, var_name, var_val, cat_metric FROM %s ", table); + ast_build_string(&sql, &sqlleft, "WHERE filename='%s' and commented=0", file); + ast_build_string(&sql, &sqlleft, "ORDER BY cat_metric DESC, var_metric ASC, category, var_name "); + + ast_debug(1, "PostgreSQL RealTime: Static SQL: %s\n", sqlbuf); + + /* We now have our complete statement; Lets connect to the server and execute it. */ + ast_mutex_lock(&pgsql_lock); + if (!pgsql_reconnect(database)) { + ast_mutex_unlock(&pgsql_lock); + return NULL; + } + + if (!(result = PQexec(pgsqlConn, sqlbuf))) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql); + ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s\n", PQerrorMessage(pgsqlConn)); + ast_mutex_unlock(&pgsql_lock); + return NULL; + } else { + ExecStatusType result_status = PQresultStatus(result); + if (result_status != PGRES_COMMAND_OK + && result_status != PGRES_TUPLES_OK + && result_status != PGRES_NONFATAL_ERROR) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Failed to query database. Check debug for more info.\n"); + ast_debug(1, "PostgreSQL RealTime: Query: %s\n", sql); + ast_debug(1, "PostgreSQL RealTime: Query Failed because: %s (%s)\n", + PQresultErrorMessage(result), PQresStatus(result_status)); + ast_mutex_unlock(&pgsql_lock); + return NULL; + } + } + + if ((num_rows = PQntuples(result)) > 0) { + int rowIndex = 0; + + ast_debug(1, "PostgreSQL RealTime: Found %ld rows.\n", num_rows); + + for (rowIndex = 0; rowIndex < num_rows; rowIndex++) { + char *field_category = PQgetvalue(result, rowIndex, 0); + char *field_var_name = PQgetvalue(result, rowIndex, 1); + char *field_var_val = PQgetvalue(result, rowIndex, 2); + char *field_cat_metric = PQgetvalue(result, rowIndex, 3); + if (!strcmp(field_var_name, "#include")) { + if (!ast_config_internal_load(field_var_val, cfg, flags, "")) { + PQclear(result); + ast_mutex_unlock(&pgsql_lock); + return NULL; + } + continue; + } + + if (strcmp(last, field_category) || last_cat_metric != atoi(field_cat_metric)) { + cur_cat = ast_category_new(field_category, "", 99999); + if (!cur_cat) + break; + strcpy(last, field_category); + last_cat_metric = atoi(field_cat_metric); + ast_category_append(cfg, cur_cat); + } + new_v = ast_variable_new(field_var_name, field_var_val, ""); + ast_variable_append(cur_cat, new_v); + } + } else { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Could not find config '%s' in database.\n", file); + } + + PQclear(result); + ast_mutex_unlock(&pgsql_lock); + + return cfg; +} + +static struct ast_config_engine pgsql_engine = { + .name = "pgsql", + .load_func = config_pgsql, + .realtime_func = realtime_pgsql, + .realtime_multi_func = realtime_multi_pgsql, + .store_func = store_pgsql, + .destroy_func = destroy_pgsql, + .update_func = update_pgsql +}; + +static int load_module(void) +{ + if(!parse_config(0)) + return AST_MODULE_LOAD_DECLINE; + + ast_config_engine_register(&pgsql_engine); + ast_verb(1, "PostgreSQL RealTime driver loaded.\n"); + ast_cli_register_multiple(cli_realtime, sizeof(cli_realtime) / sizeof(struct ast_cli_entry)); + + return 0; +} + +static int unload_module(void) +{ + /* Acquire control before doing anything to the module itself. */ + ast_mutex_lock(&pgsql_lock); + + if (pgsqlConn) { + PQfinish(pgsqlConn); + pgsqlConn = NULL; + } + ast_cli_unregister_multiple(cli_realtime, sizeof(cli_realtime) / sizeof(struct ast_cli_entry)); + ast_config_engine_deregister(&pgsql_engine); + ast_verb(1, "PostgreSQL RealTime unloaded.\n"); + + /* Unlock so something else can destroy the lock. */ + ast_mutex_unlock(&pgsql_lock); + + return 0; +} + +static int reload(void) +{ + parse_config(1); + + return 0; +} + +static int parse_config(int reload) +{ + struct ast_config *config; + const char *s; + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + + if ((config = ast_config_load(RES_CONFIG_PGSQL_CONF, config_flags)) == CONFIG_STATUS_FILEUNCHANGED) + return 0; + + if (!config) { + ast_log(LOG_WARNING, "Unable to load config %s\n", RES_CONFIG_PGSQL_CONF); + return 0; + } + + ast_mutex_lock(&pgsql_lock); + + if (pgsqlConn) { + PQfinish(pgsqlConn); + pgsqlConn = NULL; + } + + if (!(s = ast_variable_retrieve(config, "general", "dbuser"))) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: No database user found, using 'asterisk' as default.\n"); + strcpy(dbuser, "asterisk"); + } else { + ast_copy_string(dbuser, s, sizeof(dbuser)); + } + + if (!(s = ast_variable_retrieve(config, "general", "dbpass"))) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: No database password found, using 'asterisk' as default.\n"); + strcpy(dbpass, "asterisk"); + } else { + ast_copy_string(dbpass, s, sizeof(dbpass)); + } + + if (!(s = ast_variable_retrieve(config, "general", "dbhost"))) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: No database host found, using localhost via socket.\n"); + dbhost[0] = '\0'; + } else { + ast_copy_string(dbhost, s, sizeof(dbhost)); + } + + if (!(s = ast_variable_retrieve(config, "general", "dbname"))) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: No database name found, using 'asterisk' as default.\n"); + strcpy(dbname, "asterisk"); + } else { + ast_copy_string(dbname, s, sizeof(dbname)); + } + + if (!(s = ast_variable_retrieve(config, "general", "dbport"))) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: No database port found, using 5432 as default.\n"); + dbport = 5432; + } else { + dbport = atoi(s); + } + + if (!ast_strlen_zero(dbhost)) { + /* No socket needed */ + } else if (!(s = ast_variable_retrieve(config, "general", "dbsock"))) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: No database socket found, using '/tmp/pgsql.sock' as default.\n"); + strcpy(dbsock, "/tmp/pgsql.sock"); + } else { + ast_copy_string(dbsock, s, sizeof(dbsock)); + } + ast_config_destroy(config); + + if (option_debug) { + if (!ast_strlen_zero(dbhost)) { + ast_debug(1, "PostgreSQL RealTime Host: %s\n", dbhost); + ast_debug(1, "PostgreSQL RealTime Port: %i\n", dbport); + } else { + ast_debug(1, "PostgreSQL RealTime Socket: %s\n", dbsock); + } + ast_debug(1, "PostgreSQL RealTime User: %s\n", dbuser); + ast_debug(1, "PostgreSQL RealTime Password: %s\n", dbpass); + ast_debug(1, "PostgreSQL RealTime DBName: %s\n", dbname); + } + + if (!pgsql_reconnect(NULL)) { + ast_log(LOG_WARNING, + "PostgreSQL RealTime: Couldn't establish connection. Check debug.\n"); + ast_debug(1, "PostgreSQL RealTime: Cannot Connect: %s\n", PQerrorMessage(pgsqlConn)); + } + + ast_verb(2, "PostgreSQL RealTime reloaded.\n"); + + /* Done reloading. Release lock so others can now use driver. */ + ast_mutex_unlock(&pgsql_lock); + + return 1; +} + +static int pgsql_reconnect(const char *database) +{ + char my_database[50]; + + ast_copy_string(my_database, S_OR(database, dbname), sizeof(my_database)); + + /* mutex lock should have been locked before calling this function. */ + + if (pgsqlConn && PQstatus(pgsqlConn) != CONNECTION_OK) { + PQfinish(pgsqlConn); + pgsqlConn = NULL; + } + + if ((!pgsqlConn) && (!ast_strlen_zero(dbhost) || !ast_strlen_zero(dbsock)) && !ast_strlen_zero(dbuser) && !ast_strlen_zero(dbpass) && !ast_strlen_zero(my_database)) { + char *connInfo = NULL; + unsigned int size = 100 + strlen(dbhost) + + strlen(dbuser) + + strlen(dbpass) + + strlen(my_database); + + if (!(connInfo = ast_malloc(size))) + return 0; + + sprintf(connInfo, "host=%s port=%d dbname=%s user=%s password=%s", + dbhost, dbport, my_database, dbuser, dbpass); + ast_debug(1, "%u connInfo=%s\n", size, connInfo); + pgsqlConn = PQconnectdb(connInfo); + ast_debug(1, "%u connInfo=%s\n", size, connInfo); + ast_free(connInfo); + connInfo = NULL; + ast_debug(1, "pgsqlConn=%p\n", pgsqlConn); + if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) { + ast_debug(1, "PostgreSQL RealTime: Successfully connected to database.\n"); + connect_time = time(NULL); + return 1; + } else { + ast_log(LOG_ERROR, + "PostgreSQL RealTime: Failed to connect database server %s on %s. Check debug for more info.\n", + dbname, dbhost); + ast_debug(1, "PostgreSQL RealTime: Cannot Connect: %s\n", PQresultErrorMessage(NULL)); + return 0; + } + } else { + ast_debug(1, "PostgreSQL RealTime: Everything is fine.\n"); + return 1; + } +} + +static char *handle_cli_realtime_pgsql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + char status[256], status2[100] = ""; + int ctime = time(NULL) - connect_time; + + switch (cmd) { + case CLI_INIT: + e->command = "realtime pgsql status"; + e->usage = + "Usage: realtime pgsql status\n" + " Shows connection information for the PostgreSQL RealTime driver\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 3) + return CLI_SHOWUSAGE; + + if (pgsqlConn && PQstatus(pgsqlConn) == CONNECTION_OK) { + if (!ast_strlen_zero(dbhost)) + snprintf(status, 255, "Connected to %s@%s, port %d", dbname, dbhost, dbport); + else if (!ast_strlen_zero(dbsock)) + snprintf(status, 255, "Connected to %s on socket file %s", dbname, dbsock); + else + snprintf(status, 255, "Connected to %s@%s", dbname, dbhost); + + if (!ast_strlen_zero(dbuser)) + snprintf(status2, 99, " with username %s", dbuser); + + if (ctime > 31536000) + ast_cli(a->fd, "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds.\n", + status, status2, ctime / 31536000, (ctime % 31536000) / 86400, + (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60); + else if (ctime > 86400) + ast_cli(a->fd, "%s%s for %d days, %d hours, %d minutes, %d seconds.\n", status, + status2, ctime / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60, + ctime % 60); + else if (ctime > 3600) + ast_cli(a->fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, status2, + ctime / 3600, (ctime % 3600) / 60, ctime % 60); + else if (ctime > 60) + ast_cli(a->fd, "%s%s for %d minutes, %d seconds.\n", status, status2, ctime / 60, + ctime % 60); + else + ast_cli(a->fd, "%s%s for %d seconds.\n", status, status2, ctime); + + return CLI_SUCCESS; + } else { + return CLI_FAILURE; + } +} + +/* needs usecount semantics defined */ +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "PostgreSQL RealTime Configuration Driver", + .load = load_module, + .unload = unload_module, + .reload = reload + ); diff --git a/trunk/res/res_config_sqlite.c b/trunk/res/res_config_sqlite.c new file mode 100644 index 000000000..fb22009b2 --- /dev/null +++ b/trunk/res/res_config_sqlite.c @@ -0,0 +1,1534 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2006, Proformatique + * + * Written by Richard Braun <rbraun@proformatique.com> + * + * Based on res_sqlite3 by Anthony Minessale II, + * and res_config_mysql by Matthew Boehm + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \page res_config_sqlite + * + * \section intro_sec Presentation + * + * res_config_sqlite is a module for the Asterisk Open Source PBX to + * support SQLite 2 databases. It can be used to fetch configuration + * from a database (static configuration files and/or using the Asterisk + * RealTime Architecture - ARA). + * It can also be used to log CDR entries. Finally, it can be used for simple + * queries in the Dialplan. Note that Asterisk already comes with a module + * named cdr_sqlite. There are two reasons for including it in res_config_sqlite: + * the first is that rewriting it was a training to learn how to write a + * simple module for Asterisk, the other is to have the same database open for + * all kinds of operations, which improves reliability and performance. + * + * There is already a module for SQLite 3 (named res_sqlite3) in the Asterisk + * addons. res_config_sqlite was developed because we, at Proformatique, are using + * PHP 4 in our embedded systems, and PHP 4 has no stable support for SQLite 3 + * at this time. We also needed RealTime support. + * + * \section conf_sec Configuration + * + * The main configuration file is res_config_sqlite.conf. It must be readable or + * res_config_sqlite will fail to start. It is suggested to use the sample file + * in this package as a starting point. The file has only one section + * named <code>general</code>. Here are the supported parameters : + * + * <dl> + * <dt><code>dbfile</code></dt> + * <dd>The absolute path to the SQLite database (the file can be non existent, + * res_config_sqlite will create it if it has the appropriate rights)</dd> + * <dt><code>config_table</code></dt> + * <dd>The table used for static configuration</dd> + * <dt><code>cdr_table</code></dt> + * <dd>The table used to store CDR entries (if ommitted, CDR support is + * disabled)</dd> + * </dl> + * + * To use res_config_sqlite for static and/or RealTime configuration, refer to the + * Asterisk documentation. The file tables.sql can be used to create the + * needed tables. + * + * \section status_sec Driver status + * + * The CLI command <code>show sqlite status</code> returns status information + * about the running driver. + * + * \section credits_sec Credits + * + * res_config_sqlite was developed by Richard Braun at the Proformatique company. + */ + +/*! + * \file + * \brief res_config_sqlite module. + */ + +/*** MODULEINFO + <depend>sqlite</depend> + ***/ + +#include "asterisk.h" +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <sqlite.h> + +#include "asterisk/pbx.h" +#include "asterisk/cdr.h" +#include "asterisk/cli.h" +#include "asterisk/lock.h" +#include "asterisk/config.h" +#include "asterisk/module.h" +#include "asterisk/linkedlists.h" + +#define MACRO_BEGIN do { +#define MACRO_END } while (0) + +#define RES_CONFIG_SQLITE_NAME "res_config_sqlite" +#define RES_CONFIG_SQLITE_DRIVER "sqlite" +#define RES_CONFIG_SQLITE_DESCRIPTION "Resource Module for SQLite 2" +#define RES_CONFIG_SQLITE_CONF_FILE "res_config_sqlite.conf" + +enum { + RES_CONFIG_SQLITE_CONFIG_ID, + RES_CONFIG_SQLITE_CONFIG_CAT_METRIC, + RES_CONFIG_SQLITE_CONFIG_VAR_METRIC, + RES_CONFIG_SQLITE_CONFIG_COMMENTED, + RES_CONFIG_SQLITE_CONFIG_FILENAME, + RES_CONFIG_SQLITE_CONFIG_CATEGORY, + RES_CONFIG_SQLITE_CONFIG_VAR_NAME, + RES_CONFIG_SQLITE_CONFIG_VAR_VAL, + RES_CONFIG_SQLITE_CONFIG_COLUMNS, +}; + +#define SET_VAR(config, to, from) \ +MACRO_BEGIN \ + int __error; \ + \ + __error = set_var(&to, #to, from->value); \ + \ + if (__error) { \ + ast_config_destroy(config); \ + unload_config(); \ + return 1; \ + } \ +MACRO_END + +/*! + * Maximum number of loops before giving up executing a query. Calls to + * sqlite_xxx() functions which can return SQLITE_BUSY or SQLITE_LOCKED + * are enclosed by RES_CONFIG_SQLITE_BEGIN and RES_CONFIG_SQLITE_END, e.g. + * <pre> + * char *errormsg; + * int error; + * + * RES_CONFIG_SQLITE_BEGIN + * error = sqlite_exec(db, query, NULL, NULL, &errormsg); + * RES_CONFIG_SQLITE_END(error) + * + * if (error) + * ...; + * </pre> + */ +#define RES_CONFIG_SQLITE_MAX_LOOPS 10 + +/*! + * Macro used before executing a query. + * + * \see RES_CONFIG_SQLITE_MAX_LOOPS. + */ +#define RES_CONFIG_SQLITE_BEGIN \ +MACRO_BEGIN \ + int __i; \ + \ + for (__i = 0; __i < RES_CONFIG_SQLITE_MAX_LOOPS; __i++) { + +/*! + * Macro used after executing a query. + * + * \see RES_CONFIG_SQLITE_MAX_LOOPS. + */ +#define RES_CONFIG_SQLITE_END(error) \ + if (error != SQLITE_BUSY && error != SQLITE_LOCKED) \ + break; \ + usleep(1000); \ + } \ +MACRO_END; + +/*! + * Structure sent to the SQLite callback function for static configuration. + * + * \see add_cfg_entry() + */ +struct cfg_entry_args { + struct ast_config *cfg; + struct ast_category *cat; + char *cat_name; + struct ast_flags flags; +}; + +/*! + * Structure sent to the SQLite callback function for RealTime configuration. + * + * \see add_rt_cfg_entry() + */ +struct rt_cfg_entry_args { + struct ast_variable *var; + struct ast_variable *last; +}; + +/*! + * Structure sent to the SQLite callback function for RealTime configuration + * (realtime_multi_handler()). + * + * \see add_rt_multi_cfg_entry() + */ +struct rt_multi_cfg_entry_args { + struct ast_config *cfg; + char *initfield; +}; + +/*! + * \brief Allocate a variable. + * \param var the address of the variable to set (it will be allocated) + * \param name the name of the variable (for error handling) + * \param value the value to store in var + * \retval 0 on success + * \retval 1 if an allocation error occurred + */ +static int set_var(char **var, const char *name, const char *value); + +/*! + * \brief Load the configuration file. + * \see unload_config() + * + * This function sets dbfile, config_table, and cdr_table. It calls + * check_vars() before returning, and unload_config() if an error occurred. + * + * \retval 0 on success + * \retval 1 if an error occurred + */ +static int load_config(void); + +/*! + * \brief Free resources related to configuration. + * \see load_config() + */ +static void unload_config(void); + +/*! + * \brief Asterisk callback function for CDR support. + * \param cdr the CDR entry Asterisk sends us. + * + * Asterisk will call this function each time a CDR entry must be logged if + * CDR support is enabled. + * + * \retval 0 on success + * \retval 1 if an error occurred + */ +static int cdr_handler(struct ast_cdr *cdr); + +/*! + * \brief SQLite callback function for static configuration. + * + * This function is passed to the SQLite engine as a callback function to + * parse a row and store it in a struct ast_config object. It relies on + * resulting rows being sorted by category. + * + * \param arg a pointer to a struct cfg_entry_args object + * \param argc number of columns + * \param argv values in the row + * \param columnNames names and types of the columns + * \retval 0 on success + * \retval 1 if an error occurred + * \see cfg_entry_args + * \see sql_get_config_table + * \see config_handler() + */ +static int add_cfg_entry(void *arg, int argc, char **argv, char **columnNames); + +/*! + * \brief Asterisk callback function for static configuration. + * + * Asterisk will call this function when it loads its static configuration, + * which usually happens at startup and reload. + * + * \param database the database to use (ignored) + * \param table the table to use + * \param file the file to load from the database + * \param cfg the struct ast_config object to use when storing variables + * \param flags Optional flags. Not used. + * \param suggested_incl suggest include. + * \retval cfg object + * \retval NULL if an error occurred + * \see add_cfg_entry() + */ +static struct ast_config * config_handler(const char *database, const char *table, const char *file, + struct ast_config *cfg, struct ast_flags flags, const char *suggested_incl); + +/*! + * \brief Helper function to parse a va_list object into 2 dynamic arrays of + * strings, parameters and values. + * + * ap must have the following format : param1 val1 param2 val2 param3 val3 ... + * arguments will be extracted to create 2 arrays: + * + * <ul> + * <li>params : param1 param2 param3 ...</li> + * <li>vals : val1 val2 val3 ...</li> + * </ul> + * + * The address of these arrays are stored in params_ptr and vals_ptr. It + * is the responsibility of the caller to release the memory of these arrays. + * It is considered an error that va_list has a null or odd number of strings. + * + * \param ap the va_list object to parse + * \param params_ptr where the address of the params array is stored + * \param vals_ptr where the address of the vals array is stored + * \retval the number of elements in the arrays (which have the same size). + * \retval 0 if an error occurred. + */ +static size_t get_params(va_list ap, const char ***params_ptr, + const char ***vals_ptr); + +/*! + * \brief SQLite callback function for RealTime configuration. + * + * This function is passed to the SQLite engine as a callback function to + * parse a row and store it in a linked list of struct ast_variable objects. + * + * \param arg a pointer to a struct rt_cfg_entry_args object + * \param argc number of columns + * \param argv values in the row + * \param columnNames names and types of the columns + * \retval 0 on success. + * \retval 1 if an error occurred. + * \see rt_cfg_entry_args + * \see realtime_handler() + */ +static int add_rt_cfg_entry(void *arg, int argc, char **argv, + char **columnNames); + +/*! + * Asterisk callback function for RealTime configuration. + * + * Asterisk will call this function each time it requires a variable + * through the RealTime architecture. ap is a list of parameters and + * values used to find a specific row, e.g one parameter "name" and + * one value "123" so that the SQL query becomes <code>SELECT * FROM + * table WHERE name = '123';</code>. + * + * \param database the database to use (ignored) + * \param table the table to use + * \param ap list of parameters and values to match + * + * \retval a linked list of struct ast_variable objects + * \retval NULL if an error occurred + * \see add_rt_cfg_entry() + */ +static struct ast_variable * realtime_handler(const char *database, + const char *table, va_list ap); + +/*! + * \brief SQLite callback function for RealTime configuration. + * + * This function performs the same actions as add_rt_cfg_entry() except + * that the rt_multi_cfg_entry_args structure is designed to store + * categories in addition to variables. + * + * \param arg a pointer to a struct rt_multi_cfg_entry_args object + * \param argc number of columns + * \param argv values in the row + * \param columnNames names and types of the columns + * \retval 0 on success. + * \retval 1 if an error occurred. + * \see rt_multi_cfg_entry_args + * \see realtime_multi_handler() + */ +static int add_rt_multi_cfg_entry(void *arg, int argc, char **argv, + char **columnNames); + +/*! + * \brief Asterisk callback function for RealTime configuration. + * + * This function performs the same actions as realtime_handler() except + * that it can store variables per category, and can return several + * categories. + * + * \param database the database to use (ignored) + * \param table the table to use + * \param ap list of parameters and values to match + * \retval a struct ast_config object storing categories and variables. + * \retval NULL if an error occurred. + * + * \see add_rt_multi_cfg_entry() + */ +static struct ast_config * realtime_multi_handler(const char *database, + const char *table, va_list ap); + +/*! + * \brief Asterisk callback function for RealTime configuration (variable + * update). + * + * Asterisk will call this function each time a variable has been modified + * internally and must be updated in the backend engine. keyfield and entity + * are used to find the row to update, e.g. <code>UPDATE table SET ... WHERE + * keyfield = 'entity';</code>. ap is a list of parameters and values with the + * same format as the other realtime functions. + * + * \param database the database to use (ignored) + * \param table the table to use + * \param keyfield the column of the matching cell + * \param entity the value of the matching cell + * \param ap list of parameters and new values to update in the database + * \retval the number of affected rows. + * \retval -1 if an error occurred. + */ +static int realtime_update_handler(const char *database, const char *table, + const char *keyfield, const char *entity, va_list ap); + +/*! + * \brief Asterisk callback function for RealTime configuration (variable + * create/store). + * + * Asterisk will call this function each time a variable has been created + * internally and must be stored in the backend engine. + * are used to find the row to update, e.g. ap is a list of parameters and + * values with the same format as the other realtime functions. + * + * \param database the database to use (ignored) + * \param table the table to use + * \param ap list of parameters and new values to insert into the database + * \retval the rowid of inserted row. + * \retval -1 if an error occurred. + */ +static int realtime_store_handler(const char *database, const char *table, + va_list ap); + +/*! + * \brief Asterisk callback function for RealTime configuration (destroys + * variable). + * + * Asterisk will call this function each time a variable has been destroyed + * internally and must be removed from the backend engine. keyfield and entity + * are used to find the row to delete, e.g. <code>DELETE FROM table WHERE + * keyfield = 'entity';</code>. ap is a list of parameters and values with the + * same format as the other realtime functions. + * + * \param database the database to use (ignored) + * \param table the table to use + * \param keyfield the column of the matching cell + * \param entity the value of the matching cell + * \param ap list of additional parameters for cell matching + * \retval the number of affected rows. + * \retval -1 if an error occurred. + */ +static int realtime_destroy_handler(const char *database, const char *table, + const char *keyfield, const char *entity, va_list ap); + +/*! + * \brief Asterisk callback function for the CLI status command. + * + * \param e CLI command + * \param cmd + * \param a CLI argument list + * \return RESULT_SUCCESS + */ +static char *handle_cli_show_sqlite_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); + +/*! The SQLite database object. */ +static sqlite *db; + +/*! Set to 1 if CDR support is enabled. */ +static int use_cdr; + +/*! Set to 1 if the CDR callback function was registered. */ +static int cdr_registered; + +/*! Set to 1 if the CLI status command callback function was registered. */ +static int cli_status_registered; + +/*! The path of the database file. */ +static char *dbfile; + +/*! The name of the static configuration table. */ +static char *config_table; + +/*! The name of the table used to store CDR entries. */ +static char *cdr_table; + +/*! + * The structure specifying all callback functions used by Asterisk for static + * and RealTime configuration. + */ +static struct ast_config_engine sqlite_engine = +{ + .name = RES_CONFIG_SQLITE_DRIVER, + .load_func = config_handler, + .realtime_func = realtime_handler, + .realtime_multi_func = realtime_multi_handler, + .store_func = realtime_store_handler, + .destroy_func = realtime_destroy_handler, + .update_func = realtime_update_handler +}; + +/*! + * The mutex used to prevent simultaneous access to the SQLite database. + */ +AST_MUTEX_DEFINE_STATIC(mutex); + +/*! + * Structure containing details and callback functions for the CLI status + * command. + */ +static struct ast_cli_entry cli_status[] = { + AST_CLI_DEFINE(handle_cli_show_sqlite_status, "Show status information about the SQLite 2 driver"), +}; + +/* + * Taken from Asterisk 1.2 cdr_sqlite.so. + */ + +/*! SQL query format to create the CDR table if non existent. */ +static char *sql_create_cdr_table = +"CREATE TABLE '%q' (\n" +" id INTEGER,\n" +" clid VARCHAR(80) NOT NULL DEFAULT '',\n" +" src VARCHAR(80) NOT NULL DEFAULT '',\n" +" dst VARCHAR(80) NOT NULL DEFAULT '',\n" +" dcontext VARCHAR(80) NOT NULL DEFAULT '',\n" +" channel VARCHAR(80) NOT NULL DEFAULT '',\n" +" dstchannel VARCHAR(80) NOT NULL DEFAULT '',\n" +" lastapp VARCHAR(80) NOT NULL DEFAULT '',\n" +" lastdata VARCHAR(80) NOT NULL DEFAULT '',\n" +" start DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',\n" +" answer DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',\n" +" end DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',\n" +" duration INT(11) NOT NULL DEFAULT 0,\n" +" billsec INT(11) NOT NULL DEFAULT 0,\n" +" disposition VARCHAR(45) NOT NULL DEFAULT '',\n" +" amaflags INT(11) NOT NULL DEFAULT 0,\n" +" accountcode VARCHAR(20) NOT NULL DEFAULT '',\n" +" uniqueid VARCHAR(32) NOT NULL DEFAULT '',\n" +" userfield VARCHAR(255) NOT NULL DEFAULT '',\n" +" PRIMARY KEY (id)\n" +");"; + +/*! SQL query format to insert a CDR entry. */ +static char *sql_add_cdr_entry = +"INSERT INTO '%q' (" +" clid," +" src," +" dst," +" dcontext," +" channel," +" dstchannel," +" lastapp," +" lastdata," +" start," +" answer," +" end," +" duration," +" billsec," +" disposition," +" amaflags," +" accountcode," +" uniqueid," +" userfield" +") VALUES (" +" '%q'," +" '%q'," +" '%q'," +" '%q'," +" '%q'," +" '%q'," +" '%q'," +" '%q'," +" datetime(%d,'unixepoch','localtime')," +" datetime(%d,'unixepoch','localtime')," +" datetime(%d,'unixepoch','localtime')," +" '%ld'," +" '%ld'," +" '%ld'," +" '%ld'," +" '%q'," +" '%q'," +" '%q'" +");"; + +/*! + * SQL query format to fetch the static configuration of a file. + * Rows must be sorted by category. + * + * \see add_cfg_entry() + */ +static char *sql_get_config_table = +"SELECT *" +" FROM '%q'" +" WHERE filename = '%q' AND commented = 0" +" ORDER BY cat_metric ASC, var_metric ASC;"; + +static int set_var(char **var, const char *name, const char *value) +{ + if (*var) + ast_free(*var); + + *var = ast_strdup(value); + + if (!*var) { + ast_log(LOG_WARNING, "Unable to allocate variable %s\n", name); + return 1; + } + + return 0; +} + +static int check_vars(void) +{ + if (!dbfile) { + ast_log(LOG_ERROR, "Undefined parameter %s\n", dbfile); + return 1; + } + + use_cdr = (cdr_table != NULL); + + return 0; +} + +static int load_config(void) +{ + struct ast_config *config; + struct ast_variable *var; + int error; + struct ast_flags config_flags = { 0 }; + + config = ast_config_load(RES_CONFIG_SQLITE_CONF_FILE, config_flags); + + if (!config) { + ast_log(LOG_ERROR, "Unable to load " RES_CONFIG_SQLITE_CONF_FILE "\n"); + return 1; + } + + for (var = ast_variable_browse(config, "general"); var; var = var->next) { + if (!strcasecmp(var->name, "dbfile")) + SET_VAR(config, dbfile, var); + else if (!strcasecmp(var->name, "config_table")) + SET_VAR(config, config_table, var); + else if (!strcasecmp(var->name, "cdr_table")) + SET_VAR(config, cdr_table, var); + else + ast_log(LOG_WARNING, "Unknown parameter : %s\n", var->name); + } + + ast_config_destroy(config); + error = check_vars(); + + if (error) { + unload_config(); + return 1; + } + + return 0; +} + +static void unload_config(void) +{ + ast_free(dbfile); + dbfile = NULL; + ast_free(config_table); + config_table = NULL; + ast_free(cdr_table); + cdr_table = NULL; +} + +static int cdr_handler(struct ast_cdr *cdr) +{ + char *query, *errormsg; + int error; + + query = sqlite_mprintf(sql_add_cdr_entry, cdr_table, cdr->clid, + cdr->src, cdr->dst, cdr->dcontext, cdr->channel, + cdr->dstchannel, cdr->lastapp, cdr->lastdata, + cdr->start.tv_sec, cdr->answer.tv_sec, + cdr->end.tv_sec, cdr->duration, cdr->billsec, + cdr->disposition, cdr->amaflags, cdr->accountcode, + cdr->uniqueid, cdr->userfield); + + if (!query) { + ast_log(LOG_WARNING, "Unable to allocate SQL query\n"); + return 1; + } + + ast_debug(1, "SQL query: %s\n", query); + + ast_mutex_lock(&mutex); + + RES_CONFIG_SQLITE_BEGIN + error = sqlite_exec(db, query, NULL, NULL, &errormsg); + RES_CONFIG_SQLITE_END(error) + + ast_mutex_unlock(&mutex); + + sqlite_freemem(query); + + if (error) { + ast_log(LOG_ERROR, "%s\n", errormsg); + sqlite_freemem(errormsg); + return 1; + } + + return 0; +} + +static int add_cfg_entry(void *arg, int argc, char **argv, char **columnNames) +{ + struct cfg_entry_args *args; + struct ast_variable *var; + + if (argc != RES_CONFIG_SQLITE_CONFIG_COLUMNS) { + ast_log(LOG_WARNING, "Corrupt table\n"); + return 1; + } + + args = arg; + + if (!strcmp(argv[RES_CONFIG_SQLITE_CONFIG_VAR_NAME], "#include")) { + struct ast_config *cfg; + char *val; + + val = argv[RES_CONFIG_SQLITE_CONFIG_VAR_VAL]; + cfg = ast_config_internal_load(val, args->cfg, args->flags, ""); + + if (!cfg) { + ast_log(LOG_WARNING, "Unable to include %s\n", val); + return 1; + } else { + args->cfg = cfg; + return 0; + } + } + + if (!args->cat_name || strcmp(args->cat_name, argv[RES_CONFIG_SQLITE_CONFIG_CATEGORY])) { + args->cat = ast_category_new(argv[RES_CONFIG_SQLITE_CONFIG_CATEGORY], "", 99999); + + if (!args->cat) { + ast_log(LOG_WARNING, "Unable to allocate category\n"); + return 1; + } + + ast_free(args->cat_name); + args->cat_name = ast_strdup(argv[RES_CONFIG_SQLITE_CONFIG_CATEGORY]); + + if (!args->cat_name) { + ast_category_destroy(args->cat); + return 1; + } + + ast_category_append(args->cfg, args->cat); + } + + var = ast_variable_new(argv[RES_CONFIG_SQLITE_CONFIG_VAR_NAME], argv[RES_CONFIG_SQLITE_CONFIG_VAR_VAL], ""); + + if (!var) { + ast_log(LOG_WARNING, "Unable to allocate variable"); + return 1; + } + + ast_variable_append(args->cat, var); + + return 0; +} + +static struct ast_config *config_handler(const char *database, const char *table, const char *file, + struct ast_config *cfg, struct ast_flags flags, const char *suggested_incl) +{ + struct cfg_entry_args args; + char *query, *errormsg; + int error; + + if (!config_table) { + if (!table) { + ast_log(LOG_ERROR, "Table name unspecified\n"); + return NULL; + } + } else + table = config_table; + + query = sqlite_mprintf(sql_get_config_table, table, file); + + if (!query) { + ast_log(LOG_WARNING, "Unable to allocate SQL query\n"); + return NULL; + } + + ast_debug(1, "SQL query: %s\n", query); + args.cfg = cfg; + args.cat = NULL; + args.cat_name = NULL; + args.flags = flags; + + ast_mutex_lock(&mutex); + + RES_CONFIG_SQLITE_BEGIN + error = sqlite_exec(db, query, add_cfg_entry, &args, &errormsg); + RES_CONFIG_SQLITE_END(error) + + ast_mutex_unlock(&mutex); + + ast_free(args.cat_name); + sqlite_freemem(query); + + if (error) { + ast_log(LOG_ERROR, "%s\n", errormsg); + sqlite_freemem(errormsg); + return NULL; + } + + return cfg; +} + +static size_t get_params(va_list ap, const char ***params_ptr, const char ***vals_ptr) +{ + const char **tmp, *param, *val, **params, **vals; + size_t params_count; + + params = NULL; + vals = NULL; + params_count = 0; + + while ((param = va_arg(ap, const char *)) && (val = va_arg(ap, const char *))) { + if (!(tmp = ast_realloc(params, (params_count + 1) * sizeof(char *)))) { + ast_free(params); + ast_free(vals); + return 0; + } + params = tmp; + + if (!(tmp = ast_realloc(vals, (params_count + 1) * sizeof(char *)))) { + ast_free(params); + ast_free(vals); + return 0; + } + vals = tmp; + + params[params_count] = param; + vals[params_count] = val; + params_count++; + } + + if (params_count > 0) { + *params_ptr = params; + *vals_ptr = vals; + } else + ast_log(LOG_WARNING, "1 parameter and 1 value at least required\n"); + + return params_count; +} + +static int add_rt_cfg_entry(void *arg, int argc, char **argv, char **columnNames) +{ + struct rt_cfg_entry_args *args; + struct ast_variable *var; + int i; + + args = arg; + + for (i = 0; i < argc; i++) { + if (!argv[i]) + continue; + + if (!(var = ast_variable_new(columnNames[i], argv[i], ""))) + return 1; + + if (!args->var) + args->var = var; + + if (!args->last) + args->last = var; + else { + args->last->next = var; + args->last = var; + } + } + + return 0; +} + +static struct ast_variable * realtime_handler(const char *database, const char *table, va_list ap) +{ + char *query, *errormsg, *op, *tmp_str; + struct rt_cfg_entry_args args; + const char **params, **vals; + size_t params_count; + int error; + + if (!table) { + ast_log(LOG_WARNING, "Table name unspecified\n"); + return NULL; + } + + params_count = get_params(ap, ¶ms, &vals); + + if (params_count == 0) + return NULL; + + op = (strchr(params[0], ' ') == NULL) ? " =" : ""; + +/* \cond DOXYGEN_CAN_PARSE_THIS */ +#undef QUERY +#define QUERY "SELECT * FROM '%q' WHERE commented = 0 AND %q%s '%q'" +/* \endcond */ + + query = sqlite_mprintf(QUERY, table, params[0], op, vals[0]); + + if (!query) { + ast_log(LOG_WARNING, "Unable to allocate SQL query\n"); + ast_free(params); + ast_free(vals); + return NULL; + } + + if (params_count > 1) { + size_t i; + + for (i = 1; i < params_count; i++) { + op = (strchr(params[i], ' ') == NULL) ? " =" : ""; + tmp_str = sqlite_mprintf("%s AND %q%s '%q'", query, params[i], op, vals[i]); + sqlite_freemem(query); + + if (!tmp_str) { + ast_log(LOG_WARNING, "Unable to reallocate SQL query\n"); + ast_free(params); + ast_free(vals); + return NULL; + } + + query = tmp_str; + } + } + + ast_free(params); + ast_free(vals); + + tmp_str = sqlite_mprintf("%s LIMIT 1;", query); + sqlite_freemem(query); + + if (!tmp_str) { + ast_log(LOG_WARNING, "Unable to reallocate SQL query\n"); + return NULL; + } + + query = tmp_str; + ast_debug(1, "SQL query: %s\n", query); + args.var = NULL; + args.last = NULL; + + ast_mutex_lock(&mutex); + + RES_CONFIG_SQLITE_BEGIN + error = sqlite_exec(db, query, add_rt_cfg_entry, &args, &errormsg); + RES_CONFIG_SQLITE_END(error) + + ast_mutex_unlock(&mutex); + + sqlite_freemem(query); + + if (error) { + ast_log(LOG_WARNING, "%s\n", errormsg); + sqlite_freemem(errormsg); + ast_variables_destroy(args.var); + return NULL; + } + + return args.var; +} + +static int add_rt_multi_cfg_entry(void *arg, int argc, char **argv, char **columnNames) +{ + struct rt_multi_cfg_entry_args *args; + struct ast_category *cat; + struct ast_variable *var; + char *cat_name; + size_t i; + + args = arg; + cat_name = NULL; + + /* + * cat_name should always be set here, since initfield is forged from + * params[0] in realtime_multi_handler(), which is a search parameter + * of the SQL query. + */ + for (i = 0; i < argc; i++) { + if (!strcmp(args->initfield, columnNames[i])) + cat_name = argv[i]; + } + + if (!cat_name) { + ast_log(LOG_ERROR, "Bogus SQL results, cat_name is NULL !\n"); + return 1; + } + + if (!(cat = ast_category_new(cat_name, "", 99999))) { + ast_log(LOG_WARNING, "Unable to allocate category\n"); + return 1; + } + + ast_category_append(args->cfg, cat); + + for (i = 0; i < argc; i++) { + if (!argv[i] || !strcmp(args->initfield, columnNames[i])) + continue; + + if (!(var = ast_variable_new(columnNames[i], argv[i], ""))) { + ast_log(LOG_WARNING, "Unable to allocate variable\n"); + return 1; + } + + ast_variable_append(cat, var); + } + + return 0; +} + +static struct ast_config *realtime_multi_handler(const char *database, + const char *table, va_list ap) +{ + char *query, *errormsg, *op, *tmp_str, *initfield; + struct rt_multi_cfg_entry_args args; + const char **params, **vals; + struct ast_config *cfg; + size_t params_count; + int error; + + if (!table) { + ast_log(LOG_WARNING, "Table name unspecified\n"); + return NULL; + } + + if (!(cfg = ast_config_new())) { + ast_log(LOG_WARNING, "Unable to allocate configuration structure\n"); + return NULL; + } + + if (!(params_count = get_params(ap, ¶ms, &vals))) { + ast_config_destroy(cfg); + return NULL; + } + + if (!(initfield = ast_strdup(params[0]))) { + ast_config_destroy(cfg); + ast_free(params); + ast_free(vals); + return NULL; + } + + tmp_str = strchr(initfield, ' '); + + if (tmp_str) + *tmp_str = '\0'; + + op = (!strchr(params[0], ' ')) ? " =" : ""; + + /* + * Asterisk sends us an already escaped string when searching for + * "exten LIKE" (uh!). Handle it separately. + */ + tmp_str = (!strcmp(vals[0], "\\_%")) ? "_%" : (char *)vals[0]; + +/* \cond DOXYGEN_CAN_PARSE_THIS */ +#undef QUERY +#define QUERY "SELECT * FROM '%q' WHERE commented = 0 AND %q%s '%q'" +/* \endcond */ + + if (!(query = sqlite_mprintf(QUERY, table, params[0], op, tmp_str))) { + ast_log(LOG_WARNING, "Unable to allocate SQL query\n"); + ast_config_destroy(cfg); + ast_free(params); + ast_free(vals); + ast_free(initfield); + return NULL; + } + + if (params_count > 1) { + size_t i; + + for (i = 1; i < params_count; i++) { + op = (!strchr(params[i], ' ')) ? " =" : ""; + tmp_str = sqlite_mprintf("%s AND %q%s '%q'", query, params[i], op, vals[i]); + sqlite_freemem(query); + + if (!tmp_str) { + ast_log(LOG_WARNING, "Unable to reallocate SQL query\n"); + ast_config_destroy(cfg); + ast_free(params); + ast_free(vals); + ast_free(initfield); + return NULL; + } + + query = tmp_str; + } + } + + ast_free(params); + ast_free(vals); + + if (!(tmp_str = sqlite_mprintf("%s ORDER BY %q;", query, initfield))) { + ast_log(LOG_WARNING, "Unable to reallocate SQL query\n"); + ast_config_destroy(cfg); + ast_free(initfield); + return NULL; + } + + sqlite_freemem(query); + query = tmp_str; + ast_debug(1, "SQL query: %s\n", query); + args.cfg = cfg; + args.initfield = initfield; + + ast_mutex_lock(&mutex); + + RES_CONFIG_SQLITE_BEGIN + error = sqlite_exec(db, query, add_rt_multi_cfg_entry, &args, &errormsg); + RES_CONFIG_SQLITE_END(error) + + ast_mutex_unlock(&mutex); + + sqlite_freemem(query); + ast_free(initfield); + + if (error) { + ast_log(LOG_WARNING, "%s\n", errormsg); + sqlite_freemem(errormsg); + ast_config_destroy(cfg); + return NULL; + } + + return cfg; +} + +static int realtime_update_handler(const char *database, const char *table, + const char *keyfield, const char *entity, va_list ap) +{ + char *query, *errormsg, *tmp_str; + const char **params, **vals; + size_t params_count; + int error, rows_num; + + if (!table) { + ast_log(LOG_WARNING, "Table name unspecified\n"); + return -1; + } + + if (!(params_count = get_params(ap, ¶ms, &vals))) + return -1; + +/* \cond DOXYGEN_CAN_PARSE_THIS */ +#undef QUERY +#define QUERY "UPDATE '%q' SET %q = '%q'" +/* \endcond */ + + if (!(query = sqlite_mprintf(QUERY, table, params[0], vals[0]))) { + ast_log(LOG_WARNING, "Unable to allocate SQL query\n"); + ast_free(params); + ast_free(vals); + return -1; + } + + if (params_count > 1) { + size_t i; + + for (i = 1; i < params_count; i++) { + tmp_str = sqlite_mprintf("%s, %q = '%q'", query, params[i], vals[i]); + sqlite_freemem(query); + + if (!tmp_str) { + ast_log(LOG_WARNING, "Unable to reallocate SQL query\n"); + ast_free(params); + ast_free(vals); + return -1; + } + + query = tmp_str; + } + } + + ast_free(params); + ast_free(vals); + + if (!(tmp_str = sqlite_mprintf("%s WHERE %q = '%q';", query, keyfield, entity))) { + ast_log(LOG_WARNING, "Unable to reallocate SQL query\n"); + return -1; + } + + sqlite_freemem(query); + query = tmp_str; + ast_debug(1, "SQL query: %s\n", query); + + ast_mutex_lock(&mutex); + + RES_CONFIG_SQLITE_BEGIN + error = sqlite_exec(db, query, NULL, NULL, &errormsg); + RES_CONFIG_SQLITE_END(error) + + if (!error) + rows_num = sqlite_changes(db); + else + rows_num = -1; + + ast_mutex_unlock(&mutex); + + sqlite_freemem(query); + + if (error) { + ast_log(LOG_WARNING, "%s\n", errormsg); + sqlite_freemem(errormsg); + } + + return rows_num; +} + +static int realtime_store_handler(const char *database, const char *table, va_list ap) { + char *errormsg, *tmp_str, *tmp_keys, *tmp_keys2, *tmp_vals, *tmp_vals2; + const char **params, **vals; + size_t params_count; + int error, rows_id; + size_t i; + + if (!table) { + ast_log(LOG_WARNING, "Table name unspecified\n"); + return -1; + } + + if (!(params_count = get_params(ap, ¶ms, &vals))) + return -1; + +/* \cond DOXYGEN_CAN_PARSE_THIS */ +#undef QUERY +#define QUERY "INSERT into '%q' (%s) VALUES (%s);" +/* \endcond */ + + tmp_keys2 = NULL; + tmp_vals2 = NULL; + for (i = 0; i < params_count; i++) { + if ( tmp_keys2 ) { + tmp_keys = sqlite_mprintf("%s, %q", tmp_keys2, params[i]); + sqlite_freemem(tmp_keys2); + } else { + tmp_keys = sqlite_mprintf("%q", params[i]); + } + if (!tmp_keys) { + ast_log(LOG_WARNING, "Unable to reallocate SQL query\n"); + ast_free(params); + ast_free(vals); + return -1; + } + + if ( tmp_vals2 ) { + tmp_vals = sqlite_mprintf("%s, '%q'", tmp_vals2, params[i]); + sqlite_freemem(tmp_vals2); + } else { + tmp_vals = sqlite_mprintf("'%q'", params[i]); + } + if (!tmp_vals) { + ast_log(LOG_WARNING, "Unable to reallocate SQL query\n"); + ast_free(params); + ast_free(vals); + return -1; + } + + + tmp_keys2 = tmp_keys; + tmp_vals2 = tmp_vals; + } + + ast_free(params); + ast_free(vals); + + if (!(tmp_str = sqlite_mprintf(QUERY, table, tmp_keys, tmp_vals))) { + ast_log(LOG_WARNING, "Unable to reallocate SQL query\n"); + return -1; + } + + sqlite_freemem(tmp_keys); + sqlite_freemem(tmp_vals); + + ast_debug(1, "SQL query: %s\n", tmp_str); + + ast_mutex_lock(&mutex); + + RES_CONFIG_SQLITE_BEGIN + error = sqlite_exec(db, tmp_str, NULL, NULL, &errormsg); + RES_CONFIG_SQLITE_END(error) + + if (!error) { + rows_id = sqlite_last_insert_rowid(db); + } else { + rows_id = -1; + } + + ast_mutex_unlock(&mutex); + + sqlite_freemem(tmp_str); + + if (error) { + ast_log(LOG_WARNING, "%s\n", errormsg); + sqlite_freemem(errormsg); + } + + return rows_id; +} + +static int realtime_destroy_handler(const char *database, const char *table, + const char *keyfield, const char *entity, va_list ap) +{ + char *query, *errormsg, *tmp_str; + const char **params, **vals; + size_t params_count; + int error, rows_num; + size_t i; + + if (!table) { + ast_log(LOG_WARNING, "Table name unspecified\n"); + return -1; + } + + if (!(params_count = get_params(ap, ¶ms, &vals))) + return -1; + +/* \cond DOXYGEN_CAN_PARSE_THIS */ +#undef QUERY +#define QUERY "DELETE FROM '%q' WHERE" +/* \endcond */ + + if (!(query = sqlite_mprintf(QUERY, table))) { + ast_log(LOG_WARNING, "Unable to allocate SQL query\n"); + ast_free(params); + ast_free(vals); + return -1; + } + + for (i = 0; i < params_count; i++) { + tmp_str = sqlite_mprintf("%s %q = '%q' AND", query, params[i], vals[i]); + sqlite_freemem(query); + + if (!tmp_str) { + ast_log(LOG_WARNING, "Unable to reallocate SQL query\n"); + ast_free(params); + ast_free(vals); + return -1; + } + + query = tmp_str; + } + + ast_free(params); + ast_free(vals); + if (!(tmp_str = sqlite_mprintf("%s %q = '%q';", query, keyfield, entity))) { + ast_log(LOG_WARNING, "Unable to reallocate SQL query\n"); + return -1; + } + sqlite_freemem(query); + query = tmp_str; + ast_debug(1, "SQL query: %s\n", query); + + ast_mutex_lock(&mutex); + + RES_CONFIG_SQLITE_BEGIN + error = sqlite_exec(db, query, NULL, NULL, &errormsg); + RES_CONFIG_SQLITE_END(error) + + if (!error) + rows_num = sqlite_changes(db); + else + rows_num = -1; + + ast_mutex_unlock(&mutex); + + sqlite_freemem(query); + + if (error) { + ast_log(LOG_WARNING, "%s\n", errormsg); + sqlite_freemem(errormsg); + } + + return rows_num; +} + +static char *handle_cli_show_sqlite_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "show sqlite status"; + e->usage = + "Usage: show sqlite status\n" + " Show status information about the SQLite 2 driver\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 3) + return CLI_SHOWUSAGE; + + ast_cli(a->fd, "SQLite database path: %s\n", dbfile); + ast_cli(a->fd, "config_table: "); + + if (!config_table) + ast_cli(a->fd, "unspecified, must be present in extconfig.conf\n"); + else + ast_cli(a->fd, "%s\n", config_table); + + ast_cli(a->fd, "cdr_table: "); + + if (!cdr_table) + ast_cli(a->fd, "unspecified, CDR support disabled\n"); + else + ast_cli(a->fd, "%s\n", cdr_table); + + return CLI_SUCCESS; +} + +static int unload_module(void) +{ + if (cli_status_registered) + ast_cli_unregister_multiple(cli_status, sizeof(cli_status) / sizeof(struct ast_cli_entry)); + + if (cdr_registered) + ast_cdr_unregister(RES_CONFIG_SQLITE_NAME); + + ast_config_engine_deregister(&sqlite_engine); + + if (db) + sqlite_close(db); + + unload_config(); + + return 0; +} + +static int load_module(void) +{ + char *errormsg; + int error; + + db = NULL; + cdr_registered = 0; + cli_status_registered = 0; + dbfile = NULL; + config_table = NULL; + cdr_table = NULL; + error = load_config(); + + if (error) + return AST_MODULE_LOAD_DECLINE; + + if (!(db = sqlite_open(dbfile, 0660, &errormsg))) { + ast_log(LOG_ERROR, "%s\n", errormsg); + sqlite_freemem(errormsg); + unload_module(); + return 1; + } + + ast_config_engine_register(&sqlite_engine); + + if (use_cdr) { + char *query; + +/* \cond DOXYGEN_CAN_PARSE_THIS */ +#undef QUERY +#define QUERY "SELECT COUNT(id) FROM %Q;" +/* \endcond */ + + query = sqlite_mprintf(QUERY, cdr_table); + + if (!query) { + ast_log(LOG_ERROR, "Unable to allocate SQL query\n"); + unload_module(); + return 1; + } + + ast_debug(1, "SQL query: %s\n", query); + + RES_CONFIG_SQLITE_BEGIN + error = sqlite_exec(db, query, NULL, NULL, &errormsg); + RES_CONFIG_SQLITE_END(error) + + sqlite_freemem(query); + + if (error) { + /* + * Unexpected error. + */ + if (error != SQLITE_ERROR) { + ast_log(LOG_ERROR, "%s\n", errormsg); + sqlite_freemem(errormsg); + unload_module(); + return 1; + } + + sqlite_freemem(errormsg); + query = sqlite_mprintf(sql_create_cdr_table, cdr_table); + + if (!query) { + ast_log(LOG_ERROR, "Unable to allocate SQL query\n"); + unload_module(); + return 1; + } + + ast_debug(1, "SQL query: %s\n", query); + + RES_CONFIG_SQLITE_BEGIN + error = sqlite_exec(db, query, NULL, NULL, &errormsg); + RES_CONFIG_SQLITE_END(error) + + sqlite_freemem(query); + + if (error) { + ast_log(LOG_ERROR, "%s\n", errormsg); + sqlite_freemem(errormsg); + unload_module(); + return 1; + } + } + + error = ast_cdr_register(RES_CONFIG_SQLITE_NAME, RES_CONFIG_SQLITE_DESCRIPTION, cdr_handler); + + if (error) { + unload_module(); + return 1; + } + + cdr_registered = 1; + } + + error = ast_cli_register_multiple(cli_status, sizeof(cli_status) / sizeof(struct ast_cli_entry)); + + if (error) { + unload_module(); + return 1; + } + + cli_status_registered = 1; + + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Realtime SQLite configuration", + .load = load_module, + .unload = unload_module, +); diff --git a/trunk/res/res_convert.c b/trunk/res/res_convert.c new file mode 100644 index 000000000..0b4d664ff --- /dev/null +++ b/trunk/res/res_convert.c @@ -0,0 +1,160 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2005, 2006, Digium, Inc. + * + * redice li <redice_li@yahoo.com> + * Russell Bryant <russell@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief file format conversion CLI command using Asterisk formats and translators + * + * \author redice li <redice_li@yahoo.com> + * \author Russell Bryant <russell@digium.com> + * + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/channel.h" +#include "asterisk/module.h" +#include "asterisk/cli.h" +#include "asterisk/file.h" + +/*! \brief Split the filename to basename and extension */ +static int split_ext(char *filename, char **name, char **ext) +{ + *name = *ext = filename; + + if ((*ext = strrchr(filename, '.'))) { + **ext = '\0'; + (*ext)++; + } + + if (ast_strlen_zero(*name) || ast_strlen_zero(*ext)) + return -1; + + return 0; +} + +/*! + * \brief Convert a file from one format to another + * \param e CLI entry + * \param cmd command number + * \param a list of cli arguments + * \retval CLI_SUCCESS on success. + * \retval CLI_SHOWUSAGE or CLI_FAILURE on failure. +*/ +static char *handle_cli_file_convert(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + char *ret = CLI_FAILURE; + struct ast_filestream *fs_in = NULL, *fs_out = NULL; + struct ast_frame *f; + struct timeval start; + int cost; + char *file_in = NULL, *file_out = NULL; + char *name_in, *ext_in, *name_out, *ext_out; + + switch (cmd) { + case CLI_INIT: + e->command = "file convert"; + e->usage = + "Usage: file convert <file_in> <file_out>\n" + " Convert from file_in to file_out. If an absolute path\n" + " is not given, the default Asterisk sounds directory\n" + " will be used.\n\n" + " Example:\n" + " file convert tt-weasels.gsm tt-weasels.ulaw\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + /* ugly, can be removed when CLI entries have ast_module pointers */ + ast_module_ref(ast_module_info->self); + + if (a->argc != 4 || ast_strlen_zero(a->argv[2]) || ast_strlen_zero(a->argv[3])) { + ret = CLI_SHOWUSAGE; + goto fail_out; + } + + file_in = ast_strdupa(a->argv[2]); + file_out = ast_strdupa(a->argv[3]); + + if (split_ext(file_in, &name_in, &ext_in)) { + ast_cli(a->fd, "'%s' is an invalid filename!\n", a->argv[2]); + goto fail_out; + } + if (!(fs_in = ast_readfile(name_in, ext_in, NULL, O_RDONLY, 0, 0))) { + ast_cli(a->fd, "Unable to open input file: %s\n", a->argv[2]); + goto fail_out; + } + + if (split_ext(file_out, &name_out, &ext_out)) { + ast_cli(a->fd, "'%s' is an invalid filename!\n", a->argv[3]); + goto fail_out; + } + if (!(fs_out = ast_writefile(name_out, ext_out, NULL, O_CREAT|O_TRUNC|O_WRONLY, 0, AST_FILE_MODE))) { + ast_cli(a->fd, "Unable to open output file: %s\n", a->argv[3]); + goto fail_out; + } + + start = ast_tvnow(); + + while ((f = ast_readframe(fs_in))) { + if (ast_writestream(fs_out, f)) { + ast_cli(a->fd, "Failed to convert %s.%s to %s.%s!\n", name_in, ext_in, name_out, ext_out); + goto fail_out; + } + } + + cost = ast_tvdiff_ms(ast_tvnow(), start); + ast_cli(a->fd, "Converted %s.%s to %s.%s in %dms\n", name_in, ext_in, name_out, ext_out, cost); + ret = CLI_SUCCESS; + +fail_out: + if (fs_out) { + ast_closestream(fs_out); + if (ret != CLI_SUCCESS) + ast_filedelete(name_out, ext_out); + } + + if (fs_in) + ast_closestream(fs_in); + + ast_module_unref(ast_module_info->self); + + return ret; +} + +static struct ast_cli_entry cli_convert[] = { + AST_CLI_DEFINE(handle_cli_file_convert, "Convert audio file") +}; + +static int unload_module(void) +{ + ast_cli_unregister_multiple(cli_convert, sizeof(cli_convert) / sizeof(struct ast_cli_entry)); + return 0; +} + +static int load_module(void) +{ + ast_cli_register_multiple(cli_convert, sizeof(cli_convert) / sizeof(struct ast_cli_entry)); + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "File format conversion CLI command"); diff --git a/trunk/res/res_crypto.c b/trunk/res/res_crypto.c new file mode 100644 index 000000000..f1a2234fd --- /dev/null +++ b/trunk/res/res_crypto.c @@ -0,0 +1,624 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Provide Cryptographic Signature capability + * + * \author Mark Spencer <markster@digium.com> + * + * \extref Uses the OpenSSL library, available at + * http://www.openssl.org/ + */ + +/*** MODULEINFO + <depend>ssl</depend> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/paths.h" /* use ast_config_AST_KEY_DIR */ +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <dirent.h> + +#include "asterisk/module.h" +#include "asterisk/crypto.h" +#include "asterisk/md5.h" +#include "asterisk/cli.h" +#include "asterisk/io.h" +#include "asterisk/lock.h" +#include "asterisk/utils.h" + +/* + * Asterisk uses RSA keys with SHA-1 message digests for its + * digital signatures. The choice of RSA is due to its higher + * throughput on verification, and the choice of SHA-1 based + * on the recently discovered collisions in MD5's compression + * algorithm and recommendations of avoiding MD5 in new schemes + * from various industry experts. + * + * We use OpenSSL to provide our crypto routines, although we never + * actually use full-up SSL + * + */ + +#define KEY_NEEDS_PASSCODE (1 << 16) + +struct ast_key { + /*! Name of entity */ + char name[80]; + /*! File name */ + char fn[256]; + /*! Key type (AST_KEY_PUB or AST_KEY_PRIV, along with flags from above) */ + int ktype; + /*! RSA structure (if successfully loaded) */ + RSA *rsa; + /*! Whether we should be deleted */ + int delme; + /*! FD for input (or -1 if no input allowed, or -2 if we needed input) */ + int infd; + /*! FD for output */ + int outfd; + /*! Last MD5 Digest */ + unsigned char digest[16]; + AST_RWLIST_ENTRY(ast_key) list; +}; + +static AST_RWLIST_HEAD_STATIC(keys, ast_key); + +/*! + * \brief setting of priv key + * \param buf + * \param size + * \param rwflag + * \param userdata + * \return length of string,-1 on failure +*/ +static int pw_cb(char *buf, int size, int rwflag, void *userdata) +{ + struct ast_key *key = (struct ast_key *)userdata; + char prompt[256]; + int res, tmp; + + if (key->infd < 0) { + /* Note that we were at least called */ + key->infd = -2; + return -1; + } + + snprintf(prompt, sizeof(prompt), ">>>> passcode for %s key '%s': ", + key->ktype == AST_KEY_PRIVATE ? "PRIVATE" : "PUBLIC", key->name); + write(key->outfd, prompt, strlen(prompt)); + memset(buf, 0, sizeof(buf)); + tmp = ast_hide_password(key->infd); + memset(buf, 0, size); + res = read(key->infd, buf, size); + ast_restore_tty(key->infd, tmp); + if (buf[strlen(buf) -1] == '\n') + buf[strlen(buf) - 1] = '\0'; + return strlen(buf); +} + +/*! + * \brief return the ast_key structure for name + * \see ast_key_get +*/ +static struct ast_key *__ast_key_get(const char *kname, int ktype) +{ + struct ast_key *key; + + AST_RWLIST_RDLOCK(&keys); + AST_RWLIST_TRAVERSE(&keys, key, list) { + if (!strcmp(kname, key->name) && + (ktype == key->ktype)) + break; + } + AST_RWLIST_UNLOCK(&keys); + + return key; +} + +/*! + * \brief load RSA key from file + * \param dir directory string + * \param fname name of file + * \param ifd incoming file descriptor + * \param ofd outgoing file descriptor + * \param not2 + * \retval key on success. + * \retval NULL on failure. +*/ +static struct ast_key *try_load_key(const char *dir, const char *fname, int ifd, int ofd, int *not2) +{ + int ktype = 0, found = 0; + char *c = NULL, ffname[256]; + unsigned char digest[16]; + FILE *f; + struct MD5Context md5; + struct ast_key *key; + static int notice = 0; + + /* Make sure its name is a public or private key */ + if ((c = strstr(fname, ".pub")) && !strcmp(c, ".pub")) + ktype = AST_KEY_PUBLIC; + else if ((c = strstr(fname, ".key")) && !strcmp(c, ".key")) + ktype = AST_KEY_PRIVATE; + else + return NULL; + + /* Get actual filename */ + snprintf(ffname, sizeof(ffname), "%s/%s", dir, fname); + + /* Open file */ + if (!(f = fopen(ffname, "r"))) { + ast_log(LOG_WARNING, "Unable to open key file %s: %s\n", ffname, strerror(errno)); + return NULL; + } + + MD5Init(&md5); + while(!feof(f)) { + /* Calculate a "whatever" quality md5sum of the key */ + char buf[256] = ""; + fgets(buf, sizeof(buf), f); + if (!feof(f)) + MD5Update(&md5, (unsigned char *) buf, strlen(buf)); + } + MD5Final(digest, &md5); + + /* Look for an existing key */ + AST_RWLIST_TRAVERSE(&keys, key, list) { + if (!strcasecmp(key->fn, ffname)) + break; + } + + if (key) { + /* If the MD5 sum is the same, and it isn't awaiting a passcode + then this is far enough */ + if (!memcmp(digest, key->digest, 16) && + !(key->ktype & KEY_NEEDS_PASSCODE)) { + fclose(f); + key->delme = 0; + return NULL; + } else { + /* Preserve keytype */ + ktype = key->ktype; + /* Recycle the same structure */ + found++; + } + } + + /* Make fname just be the normal name now */ + *c = '\0'; + if (!key) { + if (!(key = ast_calloc(1, sizeof(*key)))) { + fclose(f); + return NULL; + } + } + /* First the filename */ + ast_copy_string(key->fn, ffname, sizeof(key->fn)); + /* Then the name */ + ast_copy_string(key->name, fname, sizeof(key->name)); + key->ktype = ktype; + /* Yes, assume we're going to be deleted */ + key->delme = 1; + /* Keep the key type */ + memcpy(key->digest, digest, 16); + /* Can I/O takes the FD we're given */ + key->infd = ifd; + key->outfd = ofd; + /* Reset the file back to the beginning */ + rewind(f); + /* Now load the key with the right method */ + if (ktype == AST_KEY_PUBLIC) + key->rsa = PEM_read_RSA_PUBKEY(f, NULL, pw_cb, key); + else + key->rsa = PEM_read_RSAPrivateKey(f, NULL, pw_cb, key); + fclose(f); + if (key->rsa) { + if (RSA_size(key->rsa) == 128) { + /* Key loaded okay */ + key->ktype &= ~KEY_NEEDS_PASSCODE; + ast_verb(3, "Loaded %s key '%s'\n", key->ktype == AST_KEY_PUBLIC ? "PUBLIC" : "PRIVATE", key->name); + ast_debug(1, "Key '%s' loaded OK\n", key->name); + key->delme = 0; + } else + ast_log(LOG_NOTICE, "Key '%s' is not expected size.\n", key->name); + } else if (key->infd != -2) { + ast_log(LOG_WARNING, "Key load %s '%s' failed\n",key->ktype == AST_KEY_PUBLIC ? "PUBLIC" : "PRIVATE", key->name); + if (ofd > -1) + ERR_print_errors_fp(stderr); + else + ERR_print_errors_fp(stderr); + } else { + ast_log(LOG_NOTICE, "Key '%s' needs passcode.\n", key->name); + key->ktype |= KEY_NEEDS_PASSCODE; + if (!notice) { + if (!ast_opt_init_keys) + ast_log(LOG_NOTICE, "Add the '-i' flag to the asterisk command line if you want to automatically initialize passcodes at launch.\n"); + notice++; + } + /* Keep it anyway */ + key->delme = 0; + /* Print final notice about "init keys" when done */ + *not2 = 1; + } + + /* If this is a new key add it to the list */ + if (!found) + AST_RWLIST_INSERT_TAIL(&keys, key, list); + + return key; +} + +/*! + * \brief signs outgoing message with public key + * \see ast_sign_bin +*/ +static int __ast_sign_bin(struct ast_key *key, const char *msg, int msglen, unsigned char *dsig) +{ + unsigned char digest[20]; + unsigned int siglen = 128; + int res; + + if (key->ktype != AST_KEY_PRIVATE) { + ast_log(LOG_WARNING, "Cannot sign with a public key\n"); + return -1; + } + + /* Calculate digest of message */ + SHA1((unsigned char *)msg, msglen, digest); + + /* Verify signature */ + if (!(res = RSA_sign(NID_sha1, digest, sizeof(digest), dsig, &siglen, key->rsa))) { + ast_log(LOG_WARNING, "RSA Signature (key %s) failed\n", key->name); + return -1; + } + + if (siglen != 128) { + ast_log(LOG_WARNING, "Unexpected signature length %d, expecting %d\n", (int)siglen, (int)128); + return -1; + } + + return 0; + +} + +/*! + * \brief decrypt a message + * \see ast_decrypt_bin +*/ +static int __ast_decrypt_bin(unsigned char *dst, const unsigned char *src, int srclen, struct ast_key *key) +{ + int res, pos = 0; + + if (key->ktype != AST_KEY_PRIVATE) { + ast_log(LOG_WARNING, "Cannot decrypt with a public key\n"); + return -1; + } + + if (srclen % 128) { + ast_log(LOG_NOTICE, "Tried to decrypt something not a multiple of 128 bytes\n"); + return -1; + } + + while(srclen) { + /* Process chunks 128 bytes at a time */ + if ((res = RSA_private_decrypt(128, src, dst, key->rsa, RSA_PKCS1_OAEP_PADDING)) < 0) + return -1; + pos += res; + src += 128; + srclen -= 128; + dst += res; + } + + return pos; +} + +/*! + * \brief encrypt a message + * \see ast_encrypt_bin +*/ +static int __ast_encrypt_bin(unsigned char *dst, const unsigned char *src, int srclen, struct ast_key *key) +{ + int res, bytes, pos = 0; + + if (key->ktype != AST_KEY_PUBLIC) { + ast_log(LOG_WARNING, "Cannot encrypt with a private key\n"); + return -1; + } + + while(srclen) { + bytes = srclen; + if (bytes > 128 - 41) + bytes = 128 - 41; + /* Process chunks 128-41 bytes at a time */ + if ((res = RSA_public_encrypt(bytes, src, dst, key->rsa, RSA_PKCS1_OAEP_PADDING)) != 128) { + ast_log(LOG_NOTICE, "How odd, encrypted size is %d\n", res); + return -1; + } + src += bytes; + srclen -= bytes; + pos += res; + dst += res; + } + return pos; +} + +/*! + * \brief wrapper for __ast_sign_bin then base64 encode it + * \see ast_sign +*/ +static int __ast_sign(struct ast_key *key, char *msg, char *sig) +{ + unsigned char dsig[128]; + int siglen = sizeof(dsig), res; + + if (!(res = ast_sign_bin(key, msg, strlen(msg), dsig))) + /* Success -- encode (256 bytes max as documented) */ + ast_base64encode(sig, dsig, siglen, 256); + + return res; +} + +/*! + * \brief check signature of a message + * \see ast_check_signature_bin +*/ +static int __ast_check_signature_bin(struct ast_key *key, const char *msg, int msglen, const unsigned char *dsig) +{ + unsigned char digest[20]; + int res; + + if (key->ktype != AST_KEY_PUBLIC) { + /* Okay, so of course you really *can* but for our purposes + we're going to say you can't */ + ast_log(LOG_WARNING, "Cannot check message signature with a private key\n"); + return -1; + } + + /* Calculate digest of message */ + SHA1((unsigned char *)msg, msglen, digest); + + /* Verify signature */ + if (!(res = RSA_verify(NID_sha1, digest, sizeof(digest), (unsigned char *)dsig, 128, key->rsa))) { + ast_debug(1, "Key failed verification: %s\n", key->name); + return -1; + } + + /* Pass */ + return 0; +} + +/*! + * \brief base64 decode then sent to __ast_check_signature_bin + * \see ast_check_signature +*/ +static int __ast_check_signature(struct ast_key *key, const char *msg, const char *sig) +{ + unsigned char dsig[128]; + int res; + + /* Decode signature */ + if ((res = ast_base64decode(dsig, sig, sizeof(dsig))) != sizeof(dsig)) { + ast_log(LOG_WARNING, "Signature improper length (expect %d, got %d)\n", (int)sizeof(dsig), (int)res); + return -1; + } + + res = ast_check_signature_bin(key, msg, strlen(msg), dsig); + + return res; +} + +/*! + * \brief refresh RSA keys from file + * \param ifd file descriptor + * \param ofd file descriptor + * \return void +*/ +static void crypto_load(int ifd, int ofd) +{ + struct ast_key *key; + DIR *dir = NULL; + struct dirent *ent; + int note = 0; + + AST_RWLIST_WRLOCK(&keys); + + /* Mark all keys for deletion */ + AST_RWLIST_TRAVERSE(&keys, key, list) { + key->delme = 1; + } + + /* Load new keys */ + if ((dir = opendir(ast_config_AST_KEY_DIR))) { + while((ent = readdir(dir))) { + try_load_key(ast_config_AST_KEY_DIR, ent->d_name, ifd, ofd, ¬e); + } + closedir(dir); + } else + ast_log(LOG_WARNING, "Unable to open key directory '%s'\n", ast_config_AST_KEY_DIR); + + if (note) + ast_log(LOG_NOTICE, "Please run the command 'init keys' to enter the passcodes for the keys\n"); + + /* Delete any keys that are no longer present */ + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&keys, key, list) { + if (key->delme) { + ast_debug(1, "Deleting key %s type %d\n", key->name, key->ktype); + AST_RWLIST_REMOVE_CURRENT(list); + if (key->rsa) + RSA_free(key->rsa); + ast_free(key); + } + } + AST_RWLIST_TRAVERSE_SAFE_END; + + AST_RWLIST_UNLOCK(&keys); +} + +static void md52sum(char *sum, unsigned char *md5) +{ + int x; + for (x = 0; x < 16; x++) + sum += sprintf(sum, "%02x", *(md5++)); +} + +/*! + * \brief show the list of RSA keys + * \param e CLI command + * \param cmd + * \param a list of CLI arguments + * \return CLI_SUCCESS +*/ +static char *handle_cli_keys_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ +#define FORMAT "%-18s %-8s %-16s %-33s\n" + + struct ast_key *key; + char sum[16 * 2 + 1]; + int count_keys = 0; + + switch (cmd) { + case CLI_INIT: + e->command = "keys show"; + e->usage = + "Usage: keys show\n" + " Displays information about RSA keys known by Asterisk\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ast_cli(a->fd, FORMAT, "Key Name", "Type", "Status", "Sum"); + ast_cli(a->fd, FORMAT, "------------------", "--------", "----------------", "--------------------------------"); + + AST_RWLIST_RDLOCK(&keys); + AST_RWLIST_TRAVERSE(&keys, key, list) { + md52sum(sum, key->digest); + ast_cli(a->fd, FORMAT, key->name, + (key->ktype & 0xf) == AST_KEY_PUBLIC ? "PUBLIC" : "PRIVATE", + key->ktype & KEY_NEEDS_PASSCODE ? "[Needs Passcode]" : "[Loaded]", sum); + count_keys++; + } + AST_RWLIST_UNLOCK(&keys); + + ast_cli(a->fd, "\n%d known RSA keys.\n", count_keys); + + return CLI_SUCCESS; + +#undef FORMAT +} + +/*! + * \brief initialize all RSA keys + * \param e CLI command + * \param cmd + * \param a list of CLI arguments + * \return CLI_SUCCESS +*/ +static char *handle_cli_keys_init(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_key *key; + int ign; + char *kn, tmp[256] = ""; + + switch (cmd) { + case CLI_INIT: + e->command = "keys init"; + e->usage = + "Usage: keys init\n" + " Initializes private keys (by reading in pass code from\n" + " the user)\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 2) + return CLI_SHOWUSAGE; + + AST_RWLIST_WRLOCK(&keys); + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&keys, key, list) { + /* Reload keys that need pass codes now */ + if (key->ktype & KEY_NEEDS_PASSCODE) { + kn = key->fn + strlen(ast_config_AST_KEY_DIR) + 1; + ast_copy_string(tmp, kn, sizeof(tmp)); + try_load_key(ast_config_AST_KEY_DIR, tmp, a->fd, a->fd, &ign); + } + } + AST_RWLIST_TRAVERSE_SAFE_END + AST_RWLIST_UNLOCK(&keys); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_crypto[] = { + AST_CLI_DEFINE(handle_cli_keys_show, "Displays RSA key information"), + AST_CLI_DEFINE(handle_cli_keys_init, "Initialize RSA key passcodes") +}; + +/*! \brief initialise the res_crypto module */ +static int crypto_init(void) +{ + SSL_library_init(); + ERR_load_crypto_strings(); + ast_cli_register_multiple(cli_crypto, sizeof(cli_crypto) / sizeof(struct ast_cli_entry)); + + /* Install ourselves into stubs */ + ast_key_get = __ast_key_get; + ast_check_signature = __ast_check_signature; + ast_check_signature_bin = __ast_check_signature_bin; + ast_sign = __ast_sign; + ast_sign_bin = __ast_sign_bin; + ast_encrypt_bin = __ast_encrypt_bin; + ast_decrypt_bin = __ast_decrypt_bin; + return 0; +} + +static int reload(void) +{ + crypto_load(-1, -1); + return 0; +} + +static int load_module(void) +{ + crypto_init(); + if (ast_opt_init_keys) + crypto_load(STDIN_FILENO, STDOUT_FILENO); + else + crypto_load(-1, -1); + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + /* Can't unload this once we're loaded */ + return -1; +} + +/* needs usecount semantics defined */ +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Cryptographic Digital Signatures", + .load = load_module, + .unload = unload_module, + .reload = reload + ); diff --git a/trunk/res/res_features.c b/trunk/res/res_features.c new file mode 100644 index 000000000..d9e8ef55f --- /dev/null +++ b/trunk/res/res_features.c @@ -0,0 +1,3444 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Routines implementing call features as call pickup, parking and transfer + * + * \author Mark Spencer <markster@digium.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <pthread.h> +#include <sys/time.h> +#include <sys/signal.h> +#include <netinet/in.h> + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/causes.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/app.h" +#include "asterisk/say.h" +#include "asterisk/features.h" +#include "asterisk/musiconhold.h" +#include "asterisk/config.h" +#include "asterisk/cli.h" +#include "asterisk/manager.h" +#include "asterisk/utils.h" +#include "asterisk/adsi.h" +#include "asterisk/devicestate.h" +#include "asterisk/monitor.h" +#include "asterisk/audiohook.h" + +#define DEFAULT_PARK_TIME 45000 +#define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000 +#define DEFAULT_FEATURE_DIGIT_TIMEOUT 500 +#define DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER 15000 +#define DEFAULT_ATXFER_DROP_CALL 0 +#define DEFAULT_ATXFER_LOOP_DELAY 10000 +#define DEFAULT_ATXFER_CALLBACK_RETRIES 2 + +#define AST_MAX_WATCHERS 256 + +enum { + AST_FEATURE_FLAG_NEEDSDTMF = (1 << 0), + AST_FEATURE_FLAG_ONPEER = (1 << 1), + AST_FEATURE_FLAG_ONSELF = (1 << 2), + AST_FEATURE_FLAG_BYCALLEE = (1 << 3), + AST_FEATURE_FLAG_BYCALLER = (1 << 4), + AST_FEATURE_FLAG_BYBOTH = (3 << 3), +}; + +struct feature_group_exten { + AST_LIST_ENTRY(feature_group_exten) entry; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(exten); + ); + struct ast_call_feature *feature; +}; + +struct feature_group { + AST_LIST_ENTRY(feature_group) entry; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(gname); + ); + AST_LIST_HEAD_NOLOCK(, feature_group_exten) features; +}; + +static AST_RWLIST_HEAD_STATIC(feature_groups, feature_group); + +static char *parkedcall = "ParkedCall"; + +static int parkaddhints = 0; /*!< Add parking hints automatically */ +static int parkedcalltransfers = 0; /*!< Enable DTMF based transfers on bridge when picking up parked calls */ +static int parkedcallreparking = 0; /*!< Enable DTMF based parking on bridge when picking up parked calls */ +static int parkingtime = DEFAULT_PARK_TIME; /*!< No more than 45 seconds parked before you do something with them */ +static char parking_con[AST_MAX_EXTENSION]; /*!< Context for which parking is made accessible */ +static char parking_con_dial[AST_MAX_EXTENSION]; /*!< Context for dialback for parking (KLUDGE) */ +static char parking_ext[AST_MAX_EXTENSION]; /*!< Extension you type to park the call */ +static char pickup_ext[AST_MAX_EXTENSION]; /*!< Call pickup extension */ +static char parkmohclass[MAX_MUSICCLASS]; /*!< Music class used for parking */ +static int parking_start; /*!< First available extension for parking */ +static int parking_stop; /*!< Last available extension for parking */ + +static char courtesytone[256]; /*!< Courtesy tone */ +static int parkedplay = 0; /*!< Who to play the courtesy tone to */ +static char xfersound[256]; /*!< Call transfer sound */ +static char xferfailsound[256]; /*!< Call transfer failure sound */ + +static int parking_offset; +static int parkfindnext; + +static int adsipark; + +static int transferdigittimeout; +static int featuredigittimeout; +static int comebacktoorigin = 1; + +static int atxfernoanswertimeout; +static unsigned int atxferdropcall; +static unsigned int atxferloopdelay; +static unsigned int atxfercallbackretries; + +static char *registrar = "res_features"; /*!< Registrar for operations */ + +/* module and CLI command definitions */ +static char *synopsis = "Answer a parked call"; + +static char *descrip = "ParkedCall(exten): " +"Used to connect to a parked call. This application is always\n" +"registered internally and does not need to be explicitly added\n" +"into the dialplan, although you should include the 'parkedcalls'\n" +"context. If no extension is provided, then the first available\n" +"parked call will be acquired.\n"; + +static char *parkcall = "Park"; + +static char *synopsis2 = "Park yourself"; + +static char *descrip2 = "Park(): " +"Used to park yourself (typically in combination with a supervised\n" +"transfer to know the parking space). This application is always\n" +"registered internally and does not need to be explicitly added\n" +"into the dialplan, although you should include the 'parkedcalls'\n" +"context (or the context specified in features.conf).\n\n" +"If you set the PARKINGEXTEN variable to an extension in your\n" +"parking context, Park() will park the call on that extension, unless\n" +"it already exists. In that case, execution will continue at next\n" +"priority.\n" ; + +static struct ast_app *monitor_app = NULL; +static int monitor_ok = 1; + +static struct ast_app *mixmonitor_app = NULL; +static int mixmonitor_ok = 1; + +static struct ast_app *stopmixmonitor_app = NULL; +static int stopmixmonitor_ok = 1; + +struct parkeduser { + struct ast_channel *chan; /*!< Parking channel */ + struct timeval start; /*!< Time the parking started */ + int parkingnum; /*!< Parking lot */ + char parkingexten[AST_MAX_EXTENSION]; /*!< If set beforehand, parking extension used for this call */ + char context[AST_MAX_CONTEXT]; /*!< Where to go if our parking time expires */ + char exten[AST_MAX_EXTENSION]; + int priority; + int parkingtime; /*!< Maximum length in parking lot before return */ + int notquiteyet; + char peername[1024]; + unsigned char moh_trys; + AST_LIST_ENTRY(parkeduser) list; +}; + +static AST_LIST_HEAD_STATIC(parkinglot, parkeduser); + +static pthread_t parking_thread; + +const char *ast_parking_ext(void) +{ + return parking_ext; +} + +const char *ast_pickup_ext(void) +{ + return pickup_ext; +} + +struct ast_bridge_thread_obj +{ + struct ast_bridge_config bconfig; + struct ast_channel *chan; + struct ast_channel *peer; + unsigned int return_to_pbx:1; +}; + + + +/*! + * \brief store context, extension and priority + * \param chan, context, ext, pri +*/ +static void set_c_e_p(struct ast_channel *chan, const char *context, const char *ext, int pri) +{ + ast_copy_string(chan->context, context, sizeof(chan->context)); + ast_copy_string(chan->exten, ext, sizeof(chan->exten)); + chan->priority = pri; +} + +/*! + * \brief Check goto on transfer + * \param chan + * + * Check if channel has 'GOTO_ON_BLINDXFR' set, if not exit. + * When found make sure the types are compatible. Check if channel is valid + * if so start the new channel else hangup the call. +*/ +static void check_goto_on_transfer(struct ast_channel *chan) +{ + struct ast_channel *xferchan; + const char *val = pbx_builtin_getvar_helper(chan, "GOTO_ON_BLINDXFR"); + char *x, *goto_on_transfer; + struct ast_frame *f; + + if (ast_strlen_zero(val)) + return; + + goto_on_transfer = ast_strdupa(val); + + if (!(xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, chan->name))) + return; + + for (x = goto_on_transfer; x && *x; x++) { + if (*x == '^') + *x = '|'; + } + /* Make formats okay */ + xferchan->readformat = chan->readformat; + xferchan->writeformat = chan->writeformat; + ast_channel_masquerade(xferchan, chan); + ast_parseable_goto(xferchan, goto_on_transfer); + xferchan->_state = AST_STATE_UP; + ast_clear_flag(xferchan, AST_FLAGS_ALL); + xferchan->_softhangup = 0; + if ((f = ast_read(xferchan))) { + ast_frfree(f); + f = NULL; + ast_pbx_start(xferchan); + } else { + ast_hangup(xferchan); + } +} + +static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, struct ast_channel *transferee, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, int igncallerstate); + +/*! + * \brief bridge the call + * \param data thread bridge. + * + * Set Last Data for respective channels, reset cdr for channels + * bridge call, check if we're going back to dialplan + * if not hangup both legs of the call +*/ +static void *ast_bridge_call_thread(void *data) +{ + struct ast_bridge_thread_obj *tobj = data; + int res; + + tobj->chan->appl = !tobj->return_to_pbx ? "Transferred Call" : "ManagerBridge"; + tobj->chan->data = tobj->peer->name; + tobj->peer->appl = !tobj->return_to_pbx ? "Transferred Call" : "ManagerBridge"; + tobj->peer->data = tobj->chan->name; + + if (tobj->chan->cdr) { + ast_cdr_reset(tobj->chan->cdr, NULL); + ast_cdr_setdestchan(tobj->chan->cdr, tobj->peer->name); + } + if (tobj->peer->cdr) { + ast_cdr_reset(tobj->peer->cdr, NULL); + ast_cdr_setdestchan(tobj->peer->cdr, tobj->chan->name); + } + + ast_bridge_call(tobj->peer, tobj->chan, &tobj->bconfig); + + if (tobj->return_to_pbx) { + if (!ast_check_hangup(tobj->peer)) { + ast_log(LOG_VERBOSE, "putting peer %s into PBX again\n", tobj->peer->name); + res = ast_pbx_start(tobj->peer); + if (res != AST_PBX_SUCCESS) + ast_log(LOG_WARNING, "FAILED continuing PBX on peer %s\n", tobj->peer->name); + } else + ast_hangup(tobj->peer); + if (!ast_check_hangup(tobj->chan)) { + ast_log(LOG_VERBOSE, "putting chan %s into PBX again\n", tobj->chan->name); + res = ast_pbx_start(tobj->chan); + if (res != AST_PBX_SUCCESS) + ast_log(LOG_WARNING, "FAILED continuing PBX on chan %s\n", tobj->chan->name); + } else + ast_hangup(tobj->chan); + } else { + ast_hangup(tobj->chan); + ast_hangup(tobj->peer); + } + + ast_free(tobj); + + return NULL; +} + +/*! + * \brief create thread for the parked call + * \param data + * + * Create thread and attributes, call ast_bridge_call_thread +*/ +static void ast_bridge_call_thread_launch(void *data) +{ + pthread_t thread; + pthread_attr_t attr; + struct sched_param sched; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + ast_pthread_create(&thread, &attr,ast_bridge_call_thread, data); + pthread_attr_destroy(&attr); + memset(&sched, 0, sizeof(sched)); + pthread_setschedparam(thread, SCHED_RR, &sched); +} + +/*! + * \brief Announce call parking by ADSI + * \param chan . + * \param parkingexten . + * Create message to show for ADSI, display message. + * \retval 0 on success. + * \retval -1 on failure. +*/ +static int adsi_announce_park(struct ast_channel *chan, char *parkingexten) +{ + int res; + int justify[5] = {ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT}; + char tmp[256]; + char *message[5] = {NULL, NULL, NULL, NULL, NULL}; + + snprintf(tmp, sizeof(tmp), "Parked on %s", parkingexten); + message[0] = tmp; + res = ast_adsi_load_session(chan, NULL, 0, 1); + if (res == -1) + return res; + return ast_adsi_print(chan, message, justify, 1); +} + +/*! \brief Notify metermaids that we've changed an extension */ +static void notify_metermaids(const char *exten, char *context, enum ast_device_state state) +{ + ast_debug(4, "Notification of state change to metermaids %s@%s\n to state '%s'", + exten, context, devstate2str(state)); + + ast_devstate_changed(state, "park:%s@%s", exten, context); +} + +/*! \brief metermaids callback from devicestate.c */ +static enum ast_device_state metermaidstate(const char *data) +{ + char *context; + char *exten; + + context = ast_strdupa(data); + + exten = strsep(&context, "@"); + if (!context) + return AST_DEVICE_INVALID; + + ast_debug(4, "Checking state of exten %s in context %s\n", exten, context); + + if (!ast_exists_extension(NULL, context, exten, 1, NULL)) + return AST_DEVICE_NOT_INUSE; + + return AST_DEVICE_INUSE; +} + +/* Park a call */ +static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, int timeout, int *extout, char *orig_chan_name) +{ + struct parkeduser *pu, *cur; + int i, x = -1, parking_range; + struct ast_context *con; + const char *parkingexten; + + /* Allocate memory for parking data */ + if (!(pu = ast_calloc(1, sizeof(*pu)))) + return -1; + + /* Lock parking lot */ + AST_LIST_LOCK(&parkinglot); + /* Check for channel variable PARKINGEXTEN */ + parkingexten = pbx_builtin_getvar_helper(chan, "PARKINGEXTEN"); + if (!ast_strlen_zero(parkingexten)) { + if (ast_exists_extension(NULL, parking_con, parkingexten, 1, NULL)) { + AST_LIST_UNLOCK(&parkinglot); + ast_free(pu); + ast_log(LOG_WARNING, "Requested parking extension already exists: %s@%s\n", parkingexten, parking_con); + return 1; /* Continue execution if possible */ + } + ast_copy_string(pu->parkingexten, parkingexten, sizeof(pu->parkingexten)); + x = atoi(parkingexten); + } else { + /* Select parking space within range */ + parking_range = parking_stop - parking_start+1; + for (i = 0; i < parking_range; i++) { + x = (i + parking_offset) % parking_range + parking_start; + AST_LIST_TRAVERSE(&parkinglot, cur, list) { + if (cur->parkingnum == x) + break; + } + if (!cur) + break; + } + + if (!(i < parking_range)) { + ast_log(LOG_WARNING, "No more parking spaces\n"); + ast_free(pu); + AST_LIST_UNLOCK(&parkinglot); + return -1; + } + /* Set pointer for next parking */ + if (parkfindnext) + parking_offset = x - parking_start + 1; + } + + chan->appl = "Parked Call"; + chan->data = NULL; + + pu->chan = chan; + + /* Put the parked channel on hold if we have two different channels */ + if (chan != peer) { + ast_indicate_data(pu->chan, AST_CONTROL_HOLD, + S_OR(parkmohclass, NULL), + !ast_strlen_zero(parkmohclass) ? strlen(parkmohclass) + 1 : 0); + } + + pu->start = ast_tvnow(); + pu->parkingnum = x; + pu->parkingtime = (timeout > 0) ? timeout : parkingtime; + if (extout) + *extout = x; + + if (peer) + ast_copy_string(pu->peername, peer->name, sizeof(pu->peername)); + + /* Remember what had been dialed, so that if the parking + expires, we try to come back to the same place */ + ast_copy_string(pu->context, S_OR(chan->macrocontext, chan->context), sizeof(pu->context)); + ast_copy_string(pu->exten, S_OR(chan->macroexten, chan->exten), sizeof(pu->exten)); + pu->priority = chan->macropriority ? chan->macropriority : chan->priority; + AST_LIST_INSERT_TAIL(&parkinglot, pu, list); + + /* If parking a channel directly, don't quiet yet get parking running on it */ + if (peer == chan) + pu->notquiteyet = 1; + AST_LIST_UNLOCK(&parkinglot); + /* Wake up the (presumably select()ing) thread */ + pthread_kill(parking_thread, SIGURG); + ast_verb(2, "Parked %s on %d@%s. Will timeout back to extension [%s] %s, %d in %d seconds\n", pu->chan->name, pu->parkingnum, parking_con, pu->context, pu->exten, pu->priority, (pu->parkingtime/1000)); + + if (pu->parkingnum != -1) + snprintf(pu->parkingexten, sizeof(pu->parkingexten), "%d", x); + manager_event(EVENT_FLAG_CALL, "ParkedCall", + "Exten: %s\r\n" + "Channel: %s\r\n" + "From: %s\r\n" + "Timeout: %ld\r\n" + "CallerIDNum: %s\r\n" + "CallerIDName: %s\r\n", + pu->parkingexten, pu->chan->name, peer ? peer->name : "", + (long)pu->start.tv_sec + (long)(pu->parkingtime/1000) - (long)time(NULL), + S_OR(pu->chan->cid.cid_num, "<unknown>"), + S_OR(pu->chan->cid.cid_name, "<unknown>") + ); + + if (peer && adsipark && ast_adsi_available(peer)) { + adsi_announce_park(peer, pu->parkingexten); /* Only supports parking numbers */ + ast_adsi_unload_session(peer); + } + + con = ast_context_find(parking_con); + if (!con) + con = ast_context_create(NULL, parking_con, registrar); + if (!con) /* Still no context? Bad */ + ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con); + /* Tell the peer channel the number of the parking space */ + if (peer && ((pu->parkingnum != -1 && ast_strlen_zero(orig_chan_name)) || !strcasecmp(peer->name, orig_chan_name))) { /* Only say number if it's a number and the channel hasn't been masqueraded away */ + /* Make sure we don't start saying digits to the channel being parked */ + ast_set_flag(peer, AST_FLAG_MASQ_NOSTREAM); + ast_say_digits(peer, pu->parkingnum, "", peer->language); + ast_clear_flag(peer, AST_FLAG_MASQ_NOSTREAM); + } + if (con) { + if (!ast_add_extension2(con, 1, pu->parkingexten, 1, NULL, NULL, parkedcall, ast_strdup(pu->parkingexten), ast_free_ptr, registrar)) + notify_metermaids(pu->parkingexten, parking_con, AST_DEVICE_INUSE); + } + if (pu->notquiteyet) { + /* Wake up parking thread if we're really done */ + ast_indicate_data(pu->chan, AST_CONTROL_HOLD, + S_OR(parkmohclass, NULL), + !ast_strlen_zero(parkmohclass) ? strlen(parkmohclass) + 1 : 0); + pu->notquiteyet = 0; + pthread_kill(parking_thread, SIGURG); + } + return 0; +} + +/*! \brief Park a call */ +int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeout, int *extout) +{ + return park_call_full(chan, peer, timeout, extout, NULL); +} + +/* Park call via masquraded channel */ +int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int timeout, int *extout) +{ + struct ast_channel *chan; + struct ast_frame *f; + + /* Make a new, fake channel that we'll use to masquerade in the real one */ + if (!(chan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, rchan->accountcode, rchan->exten, rchan->context, rchan->amaflags, "Parked/%s",rchan->name))) { + ast_log(LOG_WARNING, "Unable to create parked channel\n"); + return -1; + } + + /* Make formats okay */ + chan->readformat = rchan->readformat; + chan->writeformat = rchan->writeformat; + ast_channel_masquerade(chan, rchan); + + /* Setup the extensions and such */ + set_c_e_p(chan, rchan->context, rchan->exten, rchan->priority); + + /* Make the masq execute */ + f = ast_read(chan); + if (f) + ast_frfree(f); + + ast_park_call(chan, peer, timeout, extout); + return 0; +} + + +#define FEATURE_RETURN_HANGUP -1 +#define FEATURE_RETURN_SUCCESSBREAK 0 +#define FEATURE_RETURN_PBX_KEEPALIVE AST_PBX_KEEPALIVE +#define FEATURE_RETURN_NO_HANGUP_PEER AST_PBX_NO_HANGUP_PEER +#define FEATURE_RETURN_PASSDIGITS 21 +#define FEATURE_RETURN_STOREDIGITS 22 +#define FEATURE_RETURN_SUCCESS 23 +#define FEATURE_RETURN_KEEPTRYING 24 + +#define FEATURE_SENSE_CHAN (1 << 0) +#define FEATURE_SENSE_PEER (1 << 1) + +/*! + * \brief set caller and callee according to the direction + * \param caller, callee, peer, chan, sense + * + * Detect who triggered feature and set callee/caller variables accordingly +*/ +static void set_peers(struct ast_channel **caller, struct ast_channel **callee, + struct ast_channel *peer, struct ast_channel *chan, int sense) +{ + if (sense == FEATURE_SENSE_PEER) { + *caller = peer; + *callee = chan; + } else { + *callee = peer; + *caller = chan; + } +} + +/*! + * \brief support routing for one touch call parking + * \param chan channel parking call + * \param peer channel to be parked + * \param config unsed + * \param code unused + * \param sense feature options + * + * \param data + * Setup channel, set return exten,priority to 's,1' + * answer chan, sleep chan, park call +*/ +static int builtin_parkcall(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data) +{ + struct ast_channel *parker; + struct ast_channel *parkee; + int res = 0; + struct ast_module_user *u; + + u = ast_module_user_add(chan); + + set_peers(&parker, &parkee, peer, chan, sense); + /* Setup the exten/priority to be s/1 since we don't know + where this call should return */ + strcpy(chan->exten, "s"); + chan->priority = 1; + if (chan->_state != AST_STATE_UP) + res = ast_answer(chan); + if (!res) + res = ast_safe_sleep(chan, 1000); + if (!res) + res = ast_park_call(parkee, parker, 0, NULL); + + ast_module_user_remove(u); + + if (!res) { + if (sense == FEATURE_SENSE_CHAN) + res = AST_PBX_NO_HANGUP_PEER; + else + res = AST_PBX_KEEPALIVE; + } + return res; + +} + +/*! + * \brief Monitor a channel by DTMF + * \param chan channel requesting monitor + * \param peer channel to be monitored + * \param config + * \param code + * \param sense feature options + * + * \param data + * Check monitor app enabled, setup channels, both caller/callee chans not null + * get TOUCH_MONITOR variable for filename if exists, exec monitor app. + * \retval FEATURE_RETURN_SUCCESS on success. + * \retval -1 on error. +*/ +static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data) +{ + char *caller_chan_id = NULL, *callee_chan_id = NULL, *args = NULL, *touch_filename = NULL; + int x = 0; + size_t len; + struct ast_channel *caller_chan, *callee_chan; + + if (!monitor_ok) { + ast_log(LOG_ERROR,"Cannot record the call. The monitor application is disabled.\n"); + return -1; + } + + if (!monitor_app && !(monitor_app = pbx_findapp("Monitor"))) { + monitor_ok = 0; + ast_log(LOG_ERROR,"Cannot record the call. The monitor application is disabled.\n"); + return -1; + } + + set_peers(&caller_chan, &callee_chan, peer, chan, sense); + + if (!ast_strlen_zero(courtesytone)) { + if (ast_autoservice_start(callee_chan)) + return -1; + if (ast_stream_and_wait(caller_chan, courtesytone, "")) { + ast_log(LOG_WARNING, "Failed to play courtesy tone!\n"); + ast_autoservice_stop(callee_chan); + return -1; + } + if (ast_autoservice_stop(callee_chan)) + return -1; + } + + if (callee_chan->monitor) { + ast_verb(4, "User hit '%s' to stop recording call.\n", code); + ast_monitor_stop(callee_chan, 1); + return FEATURE_RETURN_SUCCESS; + } + + if (caller_chan && callee_chan) { + const char *touch_format = pbx_builtin_getvar_helper(caller_chan, "TOUCH_MONITOR_FORMAT"); + const char *touch_monitor = pbx_builtin_getvar_helper(caller_chan, "TOUCH_MONITOR"); + const char *touch_monitor_prefix = pbx_builtin_getvar_helper(caller_chan, "TOUCH_MONITOR_PREFIX"); + + if (!touch_format) + touch_format = pbx_builtin_getvar_helper(callee_chan, "TOUCH_MONITOR_FORMAT"); + + if (!touch_monitor) + touch_monitor = pbx_builtin_getvar_helper(callee_chan, "TOUCH_MONITOR"); + + if (!touch_monitor_prefix) + touch_monitor_prefix = pbx_builtin_getvar_helper(callee_chan, "TOUCH_MONITOR_PREFIX"); + + if (touch_monitor) { + len = strlen(touch_monitor) + 50; + args = alloca(len); + touch_filename = alloca(len); + snprintf(touch_filename, len, "%s-%ld-%s", S_OR(touch_monitor_prefix, "auto"), (long)time(NULL), touch_monitor); + snprintf(args, len, "%s|%s|m", S_OR(touch_format, "wav"), touch_filename); + } else { + caller_chan_id = ast_strdupa(S_OR(caller_chan->cid.cid_num, caller_chan->name)); + callee_chan_id = ast_strdupa(S_OR(callee_chan->cid.cid_num, callee_chan->name)); + len = strlen(caller_chan_id) + strlen(callee_chan_id) + 50; + args = alloca(len); + touch_filename = alloca(len); + snprintf(touch_filename, len, "%s-%ld-%s-%s", S_OR(touch_monitor_prefix, "auto"), (long)time(NULL), caller_chan_id, callee_chan_id); + snprintf(args, len, "%s|%s|m", S_OR(touch_format, "wav"), touch_filename); + } + + for(x = 0; x < strlen(args); x++) { + if (args[x] == '/') + args[x] = '-'; + } + + ast_verb(4, "User hit '%s' to record call. filename: %s\n", code, args); + + pbx_exec(callee_chan, monitor_app, args); + pbx_builtin_setvar_helper(callee_chan, "TOUCH_MONITOR_OUTPUT", touch_filename); + pbx_builtin_setvar_helper(caller_chan, "TOUCH_MONITOR_OUTPUT", touch_filename); + + return FEATURE_RETURN_SUCCESS; + } + + ast_log(LOG_NOTICE,"Cannot record the call. One or both channels have gone away.\n"); + return -1; +} + +static int builtin_automixmonitor(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data) +{ + char *caller_chan_id = NULL, *callee_chan_id = NULL, *args = NULL, *touch_filename = NULL; + int x = 0; + size_t len; + struct ast_channel *caller_chan, *callee_chan; + const char *mixmonitor_spy_type = "MixMonitor"; + int count = 0; + + if (!mixmonitor_ok) { + ast_log(LOG_ERROR,"Cannot record the call. The mixmonitor application is disabled.\n"); + return -1; + } + + if (!(mixmonitor_app = pbx_findapp("MixMonitor"))) { + mixmonitor_ok = 0; + ast_log(LOG_ERROR,"Cannot record the call. The mixmonitor application is disabled.\n"); + return -1; + } + + set_peers(&caller_chan, &callee_chan, peer, chan, sense); + + if (!ast_strlen_zero(courtesytone)) { + if (ast_autoservice_start(callee_chan)) + return -1; + if (ast_stream_and_wait(caller_chan, courtesytone, "")) { + ast_log(LOG_WARNING, "Failed to play courtesy tone!\n"); + ast_autoservice_stop(callee_chan); + return -1; + } + if (ast_autoservice_stop(callee_chan)) + return -1; + } + + ast_channel_lock(callee_chan); + count = ast_channel_audiohook_count_by_source(callee_chan, mixmonitor_spy_type, AST_AUDIOHOOK_TYPE_SPY); + ast_channel_unlock(callee_chan); + + // This means a mixmonitor is attached to the channel, running or not is unknown. + if (count > 0) { + + ast_verb(3, "User hit '%s' to stop recording call.\n", code); + + //Make sure they are running + ast_channel_lock(callee_chan); + count = ast_channel_audiohook_count_by_source_running(callee_chan, mixmonitor_spy_type, AST_AUDIOHOOK_TYPE_SPY); + ast_channel_unlock(callee_chan); + if (count > 0) { + if (!stopmixmonitor_ok) { + ast_log(LOG_ERROR,"Cannot stop recording the call. The stopmixmonitor application is disabled.\n"); + return -1; + } + if (!(stopmixmonitor_app = pbx_findapp("StopMixMonitor"))) { + stopmixmonitor_ok = 0; + ast_log(LOG_ERROR,"Cannot stop recording the call. The stopmixmonitor application is disabled.\n"); + return -1; + } else { + pbx_exec(callee_chan, stopmixmonitor_app, ""); + return FEATURE_RETURN_SUCCESS; + } + } + + ast_log(LOG_WARNING,"Stopped MixMonitors are attached to the channel.\n"); + } + + if (caller_chan && callee_chan) { + const char *touch_format = pbx_builtin_getvar_helper(caller_chan, "TOUCH_MIXMONITOR_FORMAT"); + const char *touch_monitor = pbx_builtin_getvar_helper(caller_chan, "TOUCH_MIXMONITOR"); + + if (!touch_format) + touch_format = pbx_builtin_getvar_helper(callee_chan, "TOUCH_MIXMONITOR_FORMAT"); + + if (!touch_monitor) + touch_monitor = pbx_builtin_getvar_helper(callee_chan, "TOUCH_MIXMONITOR"); + + if (touch_monitor) { + len = strlen(touch_monitor) + 50; + args = alloca(len); + touch_filename = alloca(len); + snprintf(touch_filename, len, "auto-%ld-%s", (long)time(NULL), touch_monitor); + snprintf(args, len, "%s.%s,b", touch_filename, (touch_format) ? touch_format : "wav"); + } else { + caller_chan_id = ast_strdupa(S_OR(caller_chan->cid.cid_num, caller_chan->name)); + callee_chan_id = ast_strdupa(S_OR(callee_chan->cid.cid_num, callee_chan->name)); + len = strlen(caller_chan_id) + strlen(callee_chan_id) + 50; + args = alloca(len); + touch_filename = alloca(len); + snprintf(touch_filename, len, "auto-%ld-%s-%s", (long)time(NULL), caller_chan_id, callee_chan_id); + snprintf(args, len, "%s.%s,b", touch_filename, S_OR(touch_format, "wav")); + } + + for( x = 0; x < strlen(args); x++) { + if (args[x] == '/') + args[x] = '-'; + } + + ast_verb(3, "User hit '%s' to record call. filename: %s\n", code, touch_filename); + + pbx_exec(callee_chan, mixmonitor_app, args); + pbx_builtin_setvar_helper(callee_chan, "TOUCH_MIXMONITOR_OUTPUT", touch_filename); + pbx_builtin_setvar_helper(caller_chan, "TOUCH_MIXMONITOR_OUTPUT", touch_filename); + return FEATURE_RETURN_SUCCESS; + + } + + ast_log(LOG_NOTICE,"Cannot record the call. One or both channels have gone away.\n"); + return -1; + +} + +static int builtin_disconnect(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data) +{ + ast_verb(4, "User hit '%s' to disconnect call.\n", code); + return FEATURE_RETURN_HANGUP; +} + +static int finishup(struct ast_channel *chan) +{ + ast_indicate(chan, AST_CONTROL_UNHOLD); + + return ast_autoservice_stop(chan); +} + +/*! + * \brief Find the context for the transfer + * \param transferer + * \param transferee + * + * Grab the TRANSFER_CONTEXT, if fails try grabbing macrocontext. + * \return a context string +*/ +static const char *real_ctx(struct ast_channel *transferer, struct ast_channel *transferee) +{ + const char *s = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT"); + if (ast_strlen_zero(s)) + s = pbx_builtin_getvar_helper(transferee, "TRANSFER_CONTEXT"); + if (ast_strlen_zero(s)) /* Use the non-macro context to transfer the call XXX ? */ + s = transferer->macrocontext; + if (ast_strlen_zero(s)) + s = transferer->context; + return s; +} + +/*! + * \brief Blind transfer user to another extension + * \param chan channel to be transfered + * \param peer channel initiated blind transfer + * \param config + * \param code + * \param data + * \param sense feature options + * + * Place chan on hold, check if transferred to parkinglot extension, + * otherwise check extension exists and transfer caller. + * \retval FEATURE_RETURN_SUCCESS. + * \retval -1 on failure. +*/ +static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data) +{ + struct ast_channel *transferer; + struct ast_channel *transferee; + const char *transferer_real_context; + char xferto[256]; + int res; + + set_peers(&transferer, &transferee, peer, chan, sense); + transferer_real_context = real_ctx(transferer, transferee); + /* Start autoservice on chan while we talk to the originator */ + ast_autoservice_start(transferee); + ast_indicate(transferee, AST_CONTROL_HOLD); + + memset(xferto, 0, sizeof(xferto)); + + /* Transfer */ + res = ast_stream_and_wait(transferer, "pbx-transfer", AST_DIGIT_ANY); + if (res < 0) { + finishup(transferee); + return -1; /* error ? */ + } + if (res > 0) /* If they've typed a digit already, handle it */ + xferto[0] = (char) res; + + ast_stopstream(transferer); + res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout); + if (res < 0) { /* hangup, would be 0 for invalid and 1 for valid */ + finishup(transferee); + return res; + } + if (!strcmp(xferto, ast_parking_ext())) { + res = finishup(transferee); + if (res) + res = -1; + else if (!ast_park_call(transferee, transferer, 0, NULL)) { /* success */ + /* We return non-zero, but tell the PBX not to hang the channel when + the thread dies -- We have to be careful now though. We are responsible for + hanging up the channel, else it will never be hung up! */ + + return (transferer == peer) ? AST_PBX_KEEPALIVE : AST_PBX_NO_HANGUP_PEER; + } else { + ast_log(LOG_WARNING, "Unable to park call %s\n", transferee->name); + } + /*! \todo XXX Maybe we should have another message here instead of invalid extension XXX */ + } else if (ast_exists_extension(transferee, transferer_real_context, xferto, 1, transferer->cid.cid_num)) { + pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", transferee->name); + pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", peer->name); + res=finishup(transferee); + if (!transferer->cdr) { + transferer->cdr=ast_cdr_alloc(); + if (transferer) { + ast_cdr_init(transferer->cdr, transferer); /* initilize our channel's cdr */ + ast_cdr_start(transferer->cdr); + } + } + if (transferer->cdr) { + ast_cdr_setdestchan(transferer->cdr, transferee->name); + ast_cdr_setapp(transferer->cdr, "BLINDTRANSFER",""); + } + if (!transferee->pbx) { + /* Doh! Use our handy async_goto functions */ + ast_verb(3, "Transferring %s to '%s' (context %s) priority 1\n" + ,transferee->name, xferto, transferer_real_context); + if (ast_async_goto(transferee, transferer_real_context, xferto, 1)) + ast_log(LOG_WARNING, "Async goto failed :-(\n"); + } else { + /* Set the channel's new extension, since it exists, using transferer context */ + set_c_e_p(transferee, transferer_real_context, xferto, 0); + } + check_goto_on_transfer(transferer); + return res; + } else { + ast_verb(3, "Unable to find extension '%s' in context '%s'\n", xferto, transferer_real_context); + } + if (ast_stream_and_wait(transferer, xferfailsound, AST_DIGIT_ANY) < 0) { + finishup(transferee); + return -1; + } + ast_stopstream(transferer); + res = finishup(transferee); + if (res) { + ast_verb(2, "Hungup during autoservice stop on '%s'\n", transferee->name); + return res; + } + return FEATURE_RETURN_SUCCESS; +} + +/*! + * \brief make channels compatible + * \param c + * \param newchan + * \retval 0 on success. + * \retval -1 on failure. +*/ +static int check_compat(struct ast_channel *c, struct ast_channel *newchan) +{ + if (ast_channel_make_compatible(c, newchan) < 0) { + ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", + c->name, newchan->name); + ast_hangup(newchan); + return -1; + } + return 0; +} + +/*! + * \brief Attended transfer + * \param chan transfered user + * \param peer person transfering call + * \param config + * \param code + * \param sense feature options + * + * \param data + * Get extension to transfer to, if you cannot generate channel (or find extension) + * return to host channel. After called channel answered wait for hangup of transferer, + * bridge call between transfer peer (taking them off hold) to attended transfer channel. + * + * \return -1 on failure +*/ +static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data) +{ + struct ast_channel *transferer; + struct ast_channel *transferee; + const char *transferer_real_context; + char xferto[256] = ""; + int res; + int outstate=0; + struct ast_channel *newchan; + struct ast_channel *xferchan; + struct ast_bridge_thread_obj *tobj; + struct ast_bridge_config bconfig; + struct ast_frame *f; + int l; + + ast_debug(1, "Executing Attended Transfer %s, %s (sense=%d) \n", chan->name, peer->name, sense); + set_peers(&transferer, &transferee, peer, chan, sense); + transferer_real_context = real_ctx(transferer, transferee); + /* Start autoservice on chan while we talk to the originator */ + ast_autoservice_start(transferee); + ast_indicate(transferee, AST_CONTROL_HOLD); + + /* Transfer */ + res = ast_stream_and_wait(transferer, "pbx-transfer", AST_DIGIT_ANY); + if (res < 0) { + finishup(transferee); + return res; + } + if (res > 0) /* If they've typed a digit already, handle it */ + xferto[0] = (char) res; + + /* this is specific of atxfer */ + res = ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout); + if (res < 0) { /* hangup, would be 0 for invalid and 1 for valid */ + finishup(transferee); + return res; + } + if (res == 0) { + ast_log(LOG_WARNING, "Did not read data.\n"); + finishup(transferee); + if (ast_stream_and_wait(transferer, "beeperr", "")) + return -1; + return FEATURE_RETURN_SUCCESS; + } + + /* valid extension, res == 1 */ + if (!ast_exists_extension(transferer, transferer_real_context, xferto, 1, transferer->cid.cid_num)) { + ast_log(LOG_WARNING, "Extension %s does not exist in context %s\n",xferto,transferer_real_context); + finishup(transferee); + if (ast_stream_and_wait(transferer, "beeperr", "")) + return -1; + return FEATURE_RETURN_SUCCESS; + } + + l = strlen(xferto); + snprintf(xferto + l, sizeof(xferto) - l, "@%s/n", transferer_real_context); /* append context */ + newchan = ast_feature_request_and_dial(transferer, transferee, "Local", ast_best_codec(transferer->nativeformats), + xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name, 1); + + if (!ast_check_hangup(transferer)) { + /* Transferer is up - old behaviour */ + ast_indicate(transferer, -1); + if (!newchan) { + finishup(transferee); + /* any reason besides user requested cancel and busy triggers the failed sound */ + if (outstate != AST_CONTROL_UNHOLD && outstate != AST_CONTROL_BUSY && + ast_stream_and_wait(transferer, xferfailsound, "")) + return -1; + if (ast_stream_and_wait(transferer, xfersound, "")) + ast_log(LOG_WARNING, "Failed to play transfer sound!\n"); + return FEATURE_RETURN_SUCCESS; + } + + if (check_compat(transferer, newchan)) { + /* we do mean transferee here, NOT transferer */ + finishup(transferee); + return -1; + } + memset(&bconfig,0,sizeof(struct ast_bridge_config)); + ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT); + ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT); + res = ast_bridge_call(transferer, newchan, &bconfig); + if (ast_check_hangup(newchan) || !ast_check_hangup(transferer)) { + ast_hangup(newchan); + if (ast_stream_and_wait(transferer, xfersound, "")) + ast_log(LOG_WARNING, "Failed to play transfer sound!\n"); + finishup(transferee); + transferer->_softhangup = 0; + return FEATURE_RETURN_SUCCESS; + } + if (check_compat(transferee, newchan)) { + finishup(transferee); + return -1; + } + ast_indicate(transferee, AST_CONTROL_UNHOLD); + + if ((ast_autoservice_stop(transferee) < 0) + || (ast_waitfordigit(transferee, 100) < 0) + || (ast_waitfordigit(newchan, 100) < 0) + || ast_check_hangup(transferee) + || ast_check_hangup(newchan)) { + ast_hangup(newchan); + return -1; + } + xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name); + if (!xferchan) { + ast_hangup(newchan); + return -1; + } + /* Make formats okay */ + xferchan->readformat = transferee->readformat; + xferchan->writeformat = transferee->writeformat; + ast_channel_masquerade(xferchan, transferee); + ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority); + xferchan->_state = AST_STATE_UP; + ast_clear_flag(xferchan, AST_FLAGS_ALL); + xferchan->_softhangup = 0; + if ((f = ast_read(xferchan))) + ast_frfree(f); + newchan->_state = AST_STATE_UP; + ast_clear_flag(newchan, AST_FLAGS_ALL); + newchan->_softhangup = 0; + if (!(tobj = ast_calloc(1, sizeof(*tobj)))) { + ast_hangup(xferchan); + ast_hangup(newchan); + return -1; + } + tobj->chan = newchan; + tobj->peer = xferchan; + tobj->bconfig = *config; + + if (ast_stream_and_wait(newchan, xfersound, "")) + ast_log(LOG_WARNING, "Failed to play transfer sound!\n"); + ast_bridge_call_thread_launch(tobj); + return -1; /* XXX meaning the channel is bridged ? */ + } else if (!ast_check_hangup(transferee)) { + /* act as blind transfer */ + if (ast_autoservice_stop(transferee) < 0) { + ast_hangup(newchan); + return -1; + } + + if (!newchan) { + unsigned int tries = 0; + char *transferer_tech, *transferer_name = ast_strdupa(transferer->name); + + transferer_tech = strsep(&transferer_name, "/"); + transferer_name = strsep(&transferer_name, "-"); + + if (ast_strlen_zero(transferer_name) || ast_strlen_zero(transferer_tech)) { + ast_log(LOG_WARNING, "Transferer has invalid channel name: '%s'\n", transferer->name); + if (ast_stream_and_wait(transferee, "beeperr", "")) + return -1; + return FEATURE_RETURN_SUCCESS; + } + + ast_log(LOG_NOTICE, "We're trying to call %s/%s\n", transferer_tech, transferer_name); + newchan = ast_feature_request_and_dial(transferee, NULL, transferer_tech, ast_best_codec(transferee->nativeformats), + transferer_name, atxfernoanswertimeout, &outstate, transferee->cid.cid_num, transferee->cid.cid_name, 0); + while (!newchan && !atxferdropcall && tries < atxfercallbackretries) { + /* Trying to transfer again */ + ast_autoservice_start(transferee); + ast_indicate(transferee, AST_CONTROL_HOLD); + + newchan = ast_feature_request_and_dial(transferer, transferee, "Local", ast_best_codec(transferer->nativeformats), + xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name, 1); + if (ast_autoservice_stop(transferee) < 0) { + if (newchan) + ast_hangup(newchan); + return -1; + } + if (!newchan) { + /* Transfer failed, sleeping */ + ast_debug(1, "Sleeping for %d ms before callback.\n", atxferloopdelay); + ast_safe_sleep(transferee, atxferloopdelay); + ast_debug(1, "Trying to callback...\n"); + newchan = ast_feature_request_and_dial(transferee, NULL, transferer_tech, ast_best_codec(transferee->nativeformats), + transferer_name, atxfernoanswertimeout, &outstate, transferee->cid.cid_num, transferee->cid.cid_name, 0); + } + tries++; + } + } + if (!newchan) + return -1; + + /* newchan is up, we should prepare transferee and bridge them */ + if (check_compat(transferee, newchan)) { + finishup(transferee); + return -1; + } + ast_indicate(transferee, AST_CONTROL_UNHOLD); + + if ((ast_waitfordigit(transferee, 100) < 0) + || (ast_waitfordigit(newchan, 100) < 0) + || ast_check_hangup(transferee) + || ast_check_hangup(newchan)) { + ast_hangup(newchan); + return -1; + } + + xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name); + if (!xferchan) { + ast_hangup(newchan); + return -1; + } + /* Make formats okay */ + xferchan->readformat = transferee->readformat; + xferchan->writeformat = transferee->writeformat; + ast_channel_masquerade(xferchan, transferee); + ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority); + xferchan->_state = AST_STATE_UP; + ast_clear_flag(xferchan, AST_FLAGS_ALL); + xferchan->_softhangup = 0; + if ((f = ast_read(xferchan))) + ast_frfree(f); + newchan->_state = AST_STATE_UP; + ast_clear_flag(newchan, AST_FLAGS_ALL); + newchan->_softhangup = 0; + if (!(tobj = ast_calloc(1, sizeof(*tobj)))) { + ast_hangup(xferchan); + ast_hangup(newchan); + return -1; + } + tobj->chan = newchan; + tobj->peer = xferchan; + tobj->bconfig = *config; + + if (ast_stream_and_wait(newchan, xfersound, "")) + ast_log(LOG_WARNING, "Failed to play transfer sound!\n"); + ast_bridge_call_thread_launch(tobj); + return -1; /* XXX meaning the channel is bridged ? */ + } else { + /* Transferee hung up */ + finishup(transferee); + return -1; + } +} + +/* add atxfer and automon as undefined so you can only use em if you configure them */ +#define FEATURES_COUNT ARRAY_LEN(builtin_features) + +AST_RWLOCK_DEFINE_STATIC(features_lock); + +static struct ast_call_feature builtin_features[] = +{ + { AST_FEATURE_REDIRECT, "Blind Transfer", "blindxfer", "#", "#", builtin_blindtransfer, AST_FEATURE_FLAG_NEEDSDTMF, "" }, + { AST_FEATURE_REDIRECT, "Attended Transfer", "atxfer", "", "", builtin_atxfer, AST_FEATURE_FLAG_NEEDSDTMF, "" }, + { AST_FEATURE_AUTOMON, "One Touch Monitor", "automon", "", "", builtin_automonitor, AST_FEATURE_FLAG_NEEDSDTMF, "" }, + { AST_FEATURE_DISCONNECT, "Disconnect Call", "disconnect", "*", "*", builtin_disconnect, AST_FEATURE_FLAG_NEEDSDTMF, "" }, + { AST_FEATURE_PARKCALL, "Park Call", "parkcall", "", "", builtin_parkcall, AST_FEATURE_FLAG_NEEDSDTMF, "" }, + { AST_FEATURE_AUTOMIXMON, "One Touch MixMonitor", "automixmon", "", "", builtin_automixmonitor, AST_FEATURE_FLAG_NEEDSDTMF, "" }, +}; + + +static AST_LIST_HEAD_STATIC(feature_list,ast_call_feature); + +/*! \brief register new feature into feature_list*/ +void ast_register_feature(struct ast_call_feature *feature) +{ + if (!feature) { + ast_log(LOG_NOTICE,"You didn't pass a feature!\n"); + return; + } + + AST_LIST_LOCK(&feature_list); + AST_LIST_INSERT_HEAD(&feature_list,feature,feature_entry); + AST_LIST_UNLOCK(&feature_list); + + ast_verb(2, "Registered Feature '%s'\n",feature->sname); +} + +/*! + * \brief Add new feature group + * \param fgname feature group name. + * + * Add new feature group to the feature group list insert at head of list. + * \note This function MUST be called while feature_groups is locked. +*/ +static struct feature_group* register_group(const char *fgname) +{ + struct feature_group *fg; + + if (!fgname) { + ast_log(LOG_NOTICE, "You didn't pass a new group name!\n"); + return NULL; + } + + if (!(fg = ast_calloc(1, sizeof(*fg)))) + return NULL; + + if (ast_string_field_init(fg, 128)) { + ast_free(fg); + return NULL; + } + + ast_string_field_set(fg, gname, fgname); + + AST_LIST_INSERT_HEAD(&feature_groups, fg, entry); + + ast_verb(2, "Registered group '%s'\n", fg->gname); + + return fg; +} + +/*! + * \brief Add feature to group + * \param fg feature group + * \param exten + * \param feature feature to add. + * + * Check fg and feature specified, add feature to list + * \note This function MUST be called while feature_groups is locked. +*/ +static void register_group_feature(struct feature_group *fg, const char *exten, struct ast_call_feature *feature) +{ + struct feature_group_exten *fge; + + if (!(fge = ast_calloc(1, sizeof(*fge)))) + return; + + if (ast_string_field_init(fge, 128)) { + ast_free(fge); + return; + } + + if (!fg) { + ast_log(LOG_NOTICE, "You didn't pass a group!\n"); + return; + } + + if (!feature) { + ast_log(LOG_NOTICE, "You didn't pass a feature!\n"); + return; + } + + ast_string_field_set(fge, exten, (ast_strlen_zero(exten) ? feature->exten : exten)); + + fge->feature = feature; + + AST_LIST_INSERT_HEAD(&fg->features, fge, entry); + + ast_verb(2, "Registered feature '%s' for group '%s' at exten '%s'\n", + feature->sname, fg->gname, exten); +} + +void ast_unregister_feature(struct ast_call_feature *feature) +{ + if (!feature) + return; + + AST_LIST_LOCK(&feature_list); + AST_LIST_REMOVE(&feature_list,feature,feature_entry); + AST_LIST_UNLOCK(&feature_list); + ast_free(feature); +} + +/*! \brief Remove all features in the list */ +static void ast_unregister_features(void) +{ + struct ast_call_feature *feature; + + AST_LIST_LOCK(&feature_list); + while ((feature = AST_LIST_REMOVE_HEAD(&feature_list,feature_entry))) + ast_free(feature); + AST_LIST_UNLOCK(&feature_list); +} + +/*! \brief find a call feature by name */ +static struct ast_call_feature *find_dynamic_feature(const char *name) +{ + struct ast_call_feature *tmp; + + AST_LIST_TRAVERSE(&feature_list, tmp, feature_entry) { + if (!strcasecmp(tmp->sname, name)) + break; + } + + return tmp; +} + +/*! \brief Remove all feature groups in the list */ +static void ast_unregister_groups(void) +{ + struct feature_group *fg; + struct feature_group_exten *fge; + + AST_RWLIST_WRLOCK(&feature_groups); + while ((fg = AST_LIST_REMOVE_HEAD(&feature_groups, entry))) { + while ((fge = AST_LIST_REMOVE_HEAD(&fg->features, entry))) { + ast_string_field_free_memory(fge); + ast_free(fge); + } + + ast_string_field_free_memory(fg); + ast_free(fg); + } + AST_RWLIST_UNLOCK(&feature_groups); +} + +/*! + * \brief Find a group by name + * \param name feature name + * \retval feature group on success. + * \retval NULL on failure. +*/ +static struct feature_group *find_group(const char *name) { + struct feature_group *fg = NULL; + + AST_LIST_TRAVERSE(&feature_groups, fg, entry) { + if (!strcasecmp(fg->gname, name)) + break; + } + + return fg; +} + +void ast_rdlock_call_features(void) +{ + ast_rwlock_rdlock(&features_lock); +} + +void ast_unlock_call_features(void) +{ + ast_rwlock_unlock(&features_lock); +} + +struct ast_call_feature *ast_find_call_feature(const char *name) +{ + int x; + for (x = 0; x < FEATURES_COUNT; x++) { + if (!strcasecmp(name, builtin_features[x].sname)) + return &builtin_features[x]; + } + return NULL; +} + +/*! + * \brief exec an app by feature + * \param chan,peer,config,code,sense,data + * + * Find a feature, determine which channel activated + * \retval FEATURE_RETURN_PBX_KEEPALIVE,FEATURE_RETURN_NO_HANGUP_PEER + * \retval -1 error. + * \retval -2 when an application cannot be found. +*/ +static int feature_exec_app(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense, void *data) +{ + struct ast_app *app; + struct ast_call_feature *feature = data; + struct ast_channel *work, *idle; + int res; + + if (!feature) { /* shouldn't ever happen! */ + ast_log(LOG_NOTICE, "Found feature before, but at execing we've lost it??\n"); + return -1; + } + + if (sense == FEATURE_SENSE_CHAN) { + if (!ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLER)) + return FEATURE_RETURN_KEEPTRYING; + if (ast_test_flag(feature, AST_FEATURE_FLAG_ONSELF)) { + work = chan; + idle = peer; + } else { + work = peer; + idle = chan; + } + } else { + if (!ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLEE)) + return FEATURE_RETURN_KEEPTRYING; + if (ast_test_flag(feature, AST_FEATURE_FLAG_ONSELF)) { + work = peer; + idle = chan; + } else { + work = chan; + idle = peer; + } + } + + if (!(app = pbx_findapp(feature->app))) { + ast_log(LOG_WARNING, "Could not find application (%s)\n", feature->app); + return -2; + } + + ast_autoservice_start(idle); + + if (!ast_strlen_zero(feature->moh_class)) + ast_moh_start(idle, feature->moh_class, NULL); + + res = pbx_exec(work, app, feature->app_args); + + if (!ast_strlen_zero(feature->moh_class)) + ast_moh_stop(idle); + + ast_autoservice_stop(idle); + + if (res == AST_PBX_KEEPALIVE) + return FEATURE_RETURN_PBX_KEEPALIVE; + else if (res == AST_PBX_NO_HANGUP_PEER) + return FEATURE_RETURN_NO_HANGUP_PEER; + else if (res) + return FEATURE_RETURN_SUCCESSBREAK; + + return FEATURE_RETURN_SUCCESS; /*! \todo XXX should probably return res */ +} + +static void unmap_features(void) +{ + int x; + + ast_rwlock_wrlock(&features_lock); + for (x = 0; x < FEATURES_COUNT; x++) + strcpy(builtin_features[x].exten, builtin_features[x].default_exten); + ast_rwlock_unlock(&features_lock); +} + +static int remap_feature(const char *name, const char *value) +{ + int x, res = -1; + + ast_rwlock_wrlock(&features_lock); + for (x = 0; x < FEATURES_COUNT; x++) { + if (strcasecmp(builtin_features[x].sname, name)) + continue; + + ast_copy_string(builtin_features[x].exten, value, sizeof(builtin_features[x].exten)); + res = 0; + break; + } + ast_rwlock_unlock(&features_lock); + + return res; +} + +/*! + * \brief Check the dynamic features + * \param chan,peer,config,code,sense + * + * Lock features list, browse for code, unlock list + * \retval res on success. + * \retval -1 on failure. +*/ +static int ast_feature_interpret(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense) +{ + int x; + struct ast_flags features; + int res = FEATURE_RETURN_PASSDIGITS; + struct ast_call_feature *feature; + struct feature_group *fg = NULL; + struct feature_group_exten *fge; + const char *dynamic_features; + char *tmp, *tok; + + if (sense == FEATURE_SENSE_CHAN) { + ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL); + dynamic_features = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES"); + } + else { + ast_copy_flags(&features, &(config->features_callee), AST_FLAGS_ALL); + dynamic_features = pbx_builtin_getvar_helper(peer, "DYNAMIC_FEATURES"); + } + ast_debug(3, "Feature interpret: chan=%s, peer=%s, sense=%d, features=%d, dynamic=%s\n", chan->name, peer->name, sense, features.flags, dynamic_features); + + ast_rwlock_rdlock(&features_lock); + for (x = 0; x < FEATURES_COUNT; x++) { + if ((ast_test_flag(&features, builtin_features[x].feature_mask)) && + !ast_strlen_zero(builtin_features[x].exten)) { + /* Feature is up for consideration */ + if (!strcmp(builtin_features[x].exten, code)) { + res = builtin_features[x].operation(chan, peer, config, code, sense, NULL); + break; + } else if (!strncmp(builtin_features[x].exten, code, strlen(code))) { + if (res == FEATURE_RETURN_PASSDIGITS) + res = FEATURE_RETURN_STOREDIGITS; + } + } + } + ast_rwlock_unlock(&features_lock); + + if (ast_strlen_zero(dynamic_features)) + return res; + + tmp = ast_strdupa(dynamic_features); + + while ((tok = strsep(&tmp, "#"))) { + AST_RWLIST_RDLOCK(&feature_groups); + + fg = find_group(tok); + + if (fg) { + AST_LIST_TRAVERSE(&fg->features, fge, entry) { + if (strcasecmp(fge->exten, code)) + continue; + + res = fge->feature->operation(chan, peer, config, code, sense, fge->feature); + if (res != FEATURE_RETURN_KEEPTRYING) { + AST_RWLIST_UNLOCK(&feature_groups); + break; + } + res = FEATURE_RETURN_PASSDIGITS; + } + if (fge) + break; + } + + AST_RWLIST_UNLOCK(&feature_groups); + AST_LIST_LOCK(&feature_list); + + if(!(feature = find_dynamic_feature(tok))) { + AST_LIST_UNLOCK(&feature_list); + continue; + } + + /* Feature is up for consideration */ + if (!strcmp(feature->exten, code)) { + ast_verb(3, " Feature Found: %s exten: %s\n",feature->sname, tok); + res = feature->operation(chan, peer, config, code, sense, feature); + if (res != FEATURE_RETURN_KEEPTRYING) { + AST_LIST_UNLOCK(&feature_list); + break; + } + res = FEATURE_RETURN_PASSDIGITS; + } else if (!strncmp(feature->exten, code, strlen(code))) + res = FEATURE_RETURN_STOREDIGITS; + + AST_LIST_UNLOCK(&feature_list); + } + + return res; +} + +static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config) +{ + int x; + + ast_clear_flag(config, AST_FLAGS_ALL); + + ast_rwlock_rdlock(&features_lock); + for (x = 0; x < FEATURES_COUNT; x++) { + if (!ast_test_flag(builtin_features + x, AST_FEATURE_FLAG_NEEDSDTMF)) + continue; + + if (ast_test_flag(&(config->features_caller), builtin_features[x].feature_mask)) + ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0); + + if (ast_test_flag(&(config->features_callee), builtin_features[x].feature_mask)) + ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1); + } + ast_rwlock_unlock(&features_lock); + + if (chan && peer && !(ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_0) && ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_1))) { + const char *dynamic_features = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES"); + + if (dynamic_features) { + char *tmp = ast_strdupa(dynamic_features); + char *tok; + struct ast_call_feature *feature; + + /* while we have a feature */ + while ((tok = strsep(&tmp, "#"))) { + AST_LIST_LOCK(&feature_list); + if ((feature = find_dynamic_feature(tok)) && ast_test_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF)) { + if (ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLER)) + ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0); + if (ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLEE)) + ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1); + } + AST_LIST_UNLOCK(&feature_list); + } + } + } +} + +/*! + * \brief Get feature and dial + * \param caller,transferee,type,format,data,timeout,outstate,cid_num,cid_name,igncallerstate + * + * Request channel, set channel variables, initiate call,check if they want to disconnect + * go into loop, check if timeout has elapsed, check if person to be transfered hung up, + * check for answer break loop, set cdr return channel. + * + * \todo XXX Check - this is very similar to the code in channel.c + * \return always a channel +*/ +static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, struct ast_channel *transferee, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, int igncallerstate) +{ + int state = 0; + int cause = 0; + int to; + struct ast_channel *chan; + struct ast_channel *monitor_chans[2]; + struct ast_channel *active_channel; + int res = 0, ready = 0; + + if ((chan = ast_request(type, format, data, &cause))) { + ast_set_callerid(chan, cid_num, cid_name, cid_num); + ast_channel_inherit_variables(caller, chan); + pbx_builtin_setvar_helper(chan, "TRANSFERERNAME", caller->name); + if (!chan->cdr) { + chan->cdr=ast_cdr_alloc(); + if (chan->cdr) { + ast_cdr_init(chan->cdr, chan); /* initilize our channel's cdr */ + ast_cdr_start(chan->cdr); + } + } + + if (!ast_call(chan, data, timeout)) { + struct timeval started; + int x, len = 0; + char *disconnect_code = NULL, *dialed_code = NULL; + + ast_indicate(caller, AST_CONTROL_RINGING); + /* support dialing of the featuremap disconnect code while performing an attended tranfer */ + ast_rwlock_rdlock(&features_lock); + for (x = 0; x < FEATURES_COUNT; x++) { + if (strcasecmp(builtin_features[x].sname, "disconnect")) + continue; + + disconnect_code = builtin_features[x].exten; + len = strlen(disconnect_code) + 1; + dialed_code = alloca(len); + memset(dialed_code, 0, len); + break; + } + ast_rwlock_unlock(&features_lock); + x = 0; + started = ast_tvnow(); + to = timeout; + + ast_poll_channel_add(caller, chan); + + while (!((transferee && ast_check_hangup(transferee)) && (!igncallerstate && ast_check_hangup(caller))) && timeout && (chan->_state != AST_STATE_UP)) { + struct ast_frame *f = NULL; + + monitor_chans[0] = caller; + monitor_chans[1] = chan; + active_channel = ast_waitfor_n(monitor_chans, 2, &to); + + /* see if the timeout has been violated */ + if(ast_tvdiff_ms(ast_tvnow(), started) > timeout) { + state = AST_CONTROL_UNHOLD; + ast_log(LOG_NOTICE, "We exceeded our AT-timeout\n"); + break; /*doh! timeout*/ + } + + if (!active_channel) + continue; + + if (chan && (chan == active_channel)){ + f = ast_read(chan); + if (f == NULL) { /*doh! where'd he go?*/ + state = AST_CONTROL_HANGUP; + res = 0; + break; + } + + if (f->frametype == AST_FRAME_CONTROL || f->frametype == AST_FRAME_DTMF || f->frametype == AST_FRAME_TEXT) { + if (f->subclass == AST_CONTROL_RINGING) { + state = f->subclass; + ast_verb(3, "%s is ringing\n", chan->name); + ast_indicate(caller, AST_CONTROL_RINGING); + } else if ((f->subclass == AST_CONTROL_BUSY) || (f->subclass == AST_CONTROL_CONGESTION)) { + state = f->subclass; + ast_verb(3, "%s is busy\n", chan->name); + ast_indicate(caller, AST_CONTROL_BUSY); + ast_frfree(f); + f = NULL; + break; + } else if (f->subclass == AST_CONTROL_ANSWER) { + /* This is what we are hoping for */ + state = f->subclass; + ast_frfree(f); + f = NULL; + ready=1; + break; + } else if (f->subclass != -1) { + ast_log(LOG_NOTICE, "Don't know what to do about control frame: %d\n", f->subclass); + } + /* else who cares */ + } + + } else if (caller && (active_channel == caller)) { + f = ast_read(caller); + if (f == NULL) { /*doh! where'd he go?*/ + if (!igncallerstate) { + if (ast_check_hangup(caller) && !ast_check_hangup(chan)) { + /* make this a blind transfer */ + ready = 1; + break; + } + state = AST_CONTROL_HANGUP; + res = 0; + break; + } + } else { + + if (f->frametype == AST_FRAME_DTMF) { + dialed_code[x++] = f->subclass; + dialed_code[x] = '\0'; + if (strlen(dialed_code) == len) { + x = 0; + } else if (x && strncmp(dialed_code, disconnect_code, x)) { + x = 0; + dialed_code[x] = '\0'; + } + if (*dialed_code && !strcmp(dialed_code, disconnect_code)) { + /* Caller Canceled the call */ + state = AST_CONTROL_UNHOLD; + ast_frfree(f); + f = NULL; + break; + } + } + } + } + if (f) + ast_frfree(f); + } /* end while */ + + ast_poll_channel_del(caller, chan); + + } else + ast_log(LOG_NOTICE, "Unable to call channel %s/%s\n", type, (char *)data); + } else { + ast_log(LOG_NOTICE, "Unable to request channel %s/%s\n", type, (char *)data); + switch(cause) { + case AST_CAUSE_BUSY: + state = AST_CONTROL_BUSY; + break; + case AST_CAUSE_CONGESTION: + state = AST_CONTROL_CONGESTION; + break; + } + } + + ast_indicate(caller, -1); + if (chan && ready) { + if (chan->_state == AST_STATE_UP) + state = AST_CONTROL_ANSWER; + res = 0; + } else if(chan) { + res = -1; + ast_hangup(chan); + chan = NULL; + } else { + res = -1; + } + + if (outstate) + *outstate = state; + + if (chan && res <= 0) { + if (chan->cdr || (chan->cdr = ast_cdr_alloc())) { + char tmp[256]; + ast_cdr_init(chan->cdr, chan); + snprintf(tmp, 256, "%s/%s", type, (char *)data); + ast_cdr_setapp(chan->cdr,"Dial",tmp); + ast_cdr_update(chan); + ast_cdr_start(chan->cdr); + ast_cdr_end(chan->cdr); + /* If the cause wasn't handled properly */ + if (ast_cdr_disposition(chan->cdr,chan->hangupcause)) + ast_cdr_failed(chan->cdr); + } else { + ast_log(LOG_WARNING, "Unable to create Call Detail Record\n"); + } + } + + return chan; +} + +/*! + * \brief bridge the call and set CDR + * \param chan,peer,config + * + * Set start time, check for two channels,check if monitor on + * check for feature activation, create new CDR + * \retval res on success. + * \retval -1 on failure to bridge. +*/ +int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast_bridge_config *config) +{ + /* Copy voice back and forth between the two channels. Give the peer + the ability to transfer calls with '#<extension' syntax. */ + struct ast_frame *f; + struct ast_channel *who; + char chan_featurecode[FEATURE_MAX_LEN + 1]=""; + char peer_featurecode[FEATURE_MAX_LEN + 1]=""; + int res; + int diff; + int hasfeatures=0; + int hadfeatures=0; + struct ast_option_header *aoh; + struct ast_bridge_config backup_config; + struct ast_cdr *bridge_cdr; + + memset(&backup_config, 0, sizeof(backup_config)); + + config->start_time = ast_tvnow(); + + if (chan && peer) { + pbx_builtin_setvar_helper(chan, "BRIDGEPEER", peer->name); + pbx_builtin_setvar_helper(peer, "BRIDGEPEER", chan->name); + } else if (chan) + pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", NULL); + + if (monitor_ok) { + const char *monitor_exec; + struct ast_channel *src = NULL; + if (!monitor_app) { + if (!(monitor_app = pbx_findapp("Monitor"))) + monitor_ok=0; + } + if ((monitor_exec = pbx_builtin_getvar_helper(chan, "AUTO_MONITOR"))) + src = chan; + else if ((monitor_exec = pbx_builtin_getvar_helper(peer, "AUTO_MONITOR"))) + src = peer; + if (monitor_app && src) { + char *tmp = ast_strdupa(monitor_exec); + pbx_exec(src, monitor_app, tmp); + } + } + + set_config_flags(chan, peer, config); + config->firstpass = 1; + + /* Answer if need be */ + if (ast_answer(chan)) + return -1; + peer->appl = "Bridged Call"; + peer->data = chan->name; + + /* copy the userfield from the B-leg to A-leg if applicable */ + if (chan->cdr && peer->cdr && !ast_strlen_zero(peer->cdr->userfield)) { + char tmp[256]; + if (!ast_strlen_zero(chan->cdr->userfield)) { + snprintf(tmp, sizeof(tmp), "%s;%s", chan->cdr->userfield, peer->cdr->userfield); + ast_cdr_appenduserfield(chan, tmp); + } else + ast_cdr_setuserfield(chan, peer->cdr->userfield); + /* free the peer's cdr without ast_cdr_free complaining */ + ast_free(peer->cdr); + peer->cdr = NULL; + } + + for (;;) { + struct ast_channel *other; /* used later */ + + res = ast_channel_bridge(chan, peer, config, &f, &who); + + if (config->feature_timer) { + /* Update time limit for next pass */ + diff = ast_tvdiff_ms(ast_tvnow(), config->start_time); + config->feature_timer -= diff; + if (hasfeatures) { + /* Running on backup config, meaning a feature might be being + activated, but that's no excuse to keep things going + indefinitely! */ + if (backup_config.feature_timer && ((backup_config.feature_timer -= diff) <= 0)) { + ast_debug(1, "Timed out, realtime this time!\n"); + config->feature_timer = 0; + who = chan; + if (f) + ast_frfree(f); + f = NULL; + res = 0; + } else if (config->feature_timer <= 0) { + /* Not *really* out of time, just out of time for + digits to come in for features. */ + ast_debug(1, "Timed out for feature!\n"); + if (!ast_strlen_zero(peer_featurecode)) { + ast_dtmf_stream(chan, peer, peer_featurecode, 0, 0); + memset(peer_featurecode, 0, sizeof(peer_featurecode)); + } + if (!ast_strlen_zero(chan_featurecode)) { + ast_dtmf_stream(peer, chan, chan_featurecode, 0, 0); + memset(chan_featurecode, 0, sizeof(chan_featurecode)); + } + if (f) + ast_frfree(f); + hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode); + if (!hasfeatures) { + /* Restore original (possibly time modified) bridge config */ + memcpy(config, &backup_config, sizeof(struct ast_bridge_config)); + memset(&backup_config, 0, sizeof(backup_config)); + } + hadfeatures = hasfeatures; + /* Continue as we were */ + continue; + } else if (!f) { + /* The bridge returned without a frame and there is a feature in progress. + * However, we don't think the feature has quite yet timed out, so just + * go back into the bridge. */ + continue; + } + } else { + if (config->feature_timer <=0) { + /* We ran out of time */ + config->feature_timer = 0; + who = chan; + if (f) + ast_frfree(f); + f = NULL; + res = 0; + } + } + } + if (res < 0) { + if (!ast_test_flag(chan, AST_FLAG_ZOMBIE) && !ast_test_flag(peer, AST_FLAG_ZOMBIE) && !ast_check_hangup(chan) && !ast_check_hangup(peer)) + ast_log(LOG_WARNING, "Bridge failed on channels %s and %s\n", chan->name, peer->name); + return -1; + } + + if (!f || (f->frametype == AST_FRAME_CONTROL && + (f->subclass == AST_CONTROL_HANGUP || f->subclass == AST_CONTROL_BUSY || + f->subclass == AST_CONTROL_CONGESTION))) { + res = -1; + break; + } + /* many things should be sent to the 'other' channel */ + other = (who == chan) ? peer : chan; + if (f->frametype == AST_FRAME_CONTROL) { + switch (f->subclass) { + case AST_CONTROL_RINGING: + case AST_CONTROL_FLASH: + case -1: + ast_indicate(other, f->subclass); + break; + case AST_CONTROL_HOLD: + case AST_CONTROL_UNHOLD: + ast_indicate_data(other, f->subclass, f->data, f->datalen); + break; + case AST_CONTROL_OPTION: + aoh = f->data; + /* Forward option Requests */ + if (aoh && aoh->flag == AST_OPTION_FLAG_REQUEST) { + ast_channel_setoption(other, ntohs(aoh->option), aoh->data, + f->datalen - sizeof(struct ast_option_header), 0); + } + break; + } + } else if (f->frametype == AST_FRAME_DTMF_BEGIN) { + /* eat it */ + } else if (f->frametype == AST_FRAME_DTMF) { + char *featurecode; + int sense; + + hadfeatures = hasfeatures; + /* This cannot overrun because the longest feature is one shorter than our buffer */ + if (who == chan) { + sense = FEATURE_SENSE_CHAN; + featurecode = chan_featurecode; + } else { + sense = FEATURE_SENSE_PEER; + featurecode = peer_featurecode; + } + /*! append the event to featurecode. we rely on the string being zero-filled, and + * not overflowing it. + * \todo XXX how do we guarantee the latter ? + */ + featurecode[strlen(featurecode)] = f->subclass; + /* Get rid of the frame before we start doing "stuff" with the channels */ + ast_frfree(f); + f = NULL; + config->feature_timer = backup_config.feature_timer; + res = ast_feature_interpret(chan, peer, config, featurecode, sense); + switch(res) { + case FEATURE_RETURN_PASSDIGITS: + ast_dtmf_stream(other, who, featurecode, 0, 0); + /* Fall through */ + case FEATURE_RETURN_SUCCESS: + memset(featurecode, 0, sizeof(chan_featurecode)); + break; + } + if (res >= FEATURE_RETURN_PASSDIGITS) { + res = 0; + } else + break; + hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode); + if (hadfeatures && !hasfeatures) { + /* Restore backup */ + memcpy(config, &backup_config, sizeof(struct ast_bridge_config)); + memset(&backup_config, 0, sizeof(struct ast_bridge_config)); + } else if (hasfeatures) { + if (!hadfeatures) { + /* Backup configuration */ + memcpy(&backup_config, config, sizeof(struct ast_bridge_config)); + /* Setup temporary config options */ + config->play_warning = 0; + ast_clear_flag(&(config->features_caller), AST_FEATURE_PLAY_WARNING); + ast_clear_flag(&(config->features_callee), AST_FEATURE_PLAY_WARNING); + config->warning_freq = 0; + config->warning_sound = NULL; + config->end_sound = NULL; + config->start_sound = NULL; + config->firstpass = 0; + } + config->start_time = ast_tvnow(); + config->feature_timer = featuredigittimeout; + ast_debug(1, "Set time limit to %ld\n", config->feature_timer); + } + } + if (f) + ast_frfree(f); + + } + /* arrange the cdrs */ + bridge_cdr = ast_cdr_alloc(); + if (bridge_cdr) { + if (chan->cdr && peer->cdr) { /* both of them? merge */ + ast_cdr_init(bridge_cdr,chan); /* seems more logicaller to use the destination as a base, but, really, it's random */ + ast_cdr_start(bridge_cdr); /* now is the time to start */ + + /* absorb the channel cdr */ + ast_cdr_merge(bridge_cdr, chan->cdr); + if (!ast_test_flag(chan->cdr, AST_CDR_FLAG_LOCKED)) + ast_cdr_discard(chan->cdr); /* if locked cdrs are in chan, they are taken over in the merge */ + + /* absorb the peer cdr */ + ast_cdr_merge(bridge_cdr, peer->cdr); + if (!ast_test_flag(peer->cdr, AST_CDR_FLAG_LOCKED)) + ast_cdr_discard(peer->cdr); /* if locked cdrs are in peer, they are taken over in the merge */ + + peer->cdr = NULL; + chan->cdr = bridge_cdr; /* make this available to the rest of the world via the chan while the call is in progress */ + } else if (chan->cdr) { + /* take the cdr from the channel - literally */ + ast_cdr_init(bridge_cdr,chan); + /* absorb this data */ + ast_cdr_merge(bridge_cdr, chan->cdr); + if (!ast_test_flag(chan->cdr, AST_CDR_FLAG_LOCKED)) + ast_cdr_discard(chan->cdr); /* if locked cdrs are in chan, they are taken over in the merge */ + chan->cdr = bridge_cdr; /* make this available to the rest of the world via the chan while the call is in progress */ + } else if (peer->cdr) { + /* take the cdr from the peer - literally */ + ast_cdr_init(bridge_cdr,peer); + /* absorb this data */ + ast_cdr_merge(bridge_cdr, peer->cdr); + if (!ast_test_flag(peer->cdr, AST_CDR_FLAG_LOCKED)) + ast_cdr_discard(peer->cdr); /* if locked cdrs are in chan, they are taken over in the merge */ + peer->cdr = NULL; + peer->cdr = bridge_cdr; /* make this available to the rest of the world via the chan while the call is in progress */ + } else { + /* make up a new cdr */ + ast_cdr_init(bridge_cdr,chan); /* eh, just pick one of them */ + chan->cdr = bridge_cdr; /* */ + } + if (ast_strlen_zero(bridge_cdr->dstchannel)) { + if (strcmp(bridge_cdr->channel, peer->name) != 0) + ast_cdr_setdestchan(bridge_cdr, peer->name); + else + ast_cdr_setdestchan(bridge_cdr, chan->name); + } + } + return res; +} + +/*! \brief Output parking event to manager */ +static void post_manager_event(const char *s, struct parkeduser *pu) +{ + manager_event(EVENT_FLAG_CALL, s, + "Exten: %s\r\n" + "Channel: %s\r\n" + "CallerIDNum: %s\r\n" + "CallerIDName: %s\r\n\r\n", + pu->parkingexten, + pu->chan->name, + S_OR(pu->chan->cid.cid_num, "<unknown>"), + S_OR(pu->chan->cid.cid_name, "<unknown>") + ); +} + +/*! + * \brief Take care of parked calls and unpark them if needed + * \param ignore unused var. + * + * Start inf loop, lock parking lot, check if any parked channels have gone above timeout + * if so, remove channel from parking lot and return it to the extension that parked it. + * Check if parked channel decided to hangup, wait until next FD via select(). +*/ +static void *do_parking_thread(void *ignore) +{ + char parkingslot[AST_MAX_EXTENSION]; + fd_set rfds, efds; /* results from previous select, to be preserved across loops. */ + + FD_ZERO(&rfds); + FD_ZERO(&efds); + + for (;;) { + struct parkeduser *pu; + int ms = -1; /* select timeout, uninitialized */ + int max = -1; /* max fd, none there yet */ + fd_set nrfds, nefds; /* args for the next select */ + FD_ZERO(&nrfds); + FD_ZERO(&nefds); + + AST_LIST_LOCK(&parkinglot); + AST_LIST_TRAVERSE_SAFE_BEGIN(&parkinglot, pu, list) { + struct ast_channel *chan = pu->chan; /* shorthand */ + int tms; /* timeout for this item */ + int x; /* fd index in channel */ + struct ast_context *con; + + if (pu->notquiteyet) /* Pretend this one isn't here yet */ + continue; + tms = ast_tvdiff_ms(ast_tvnow(), pu->start); + if (tms > pu->parkingtime) { + ast_indicate(chan, AST_CONTROL_UNHOLD); + /* Get chan, exten from derived kludge */ + if (pu->peername[0]) { + char *peername = ast_strdupa(pu->peername); + char *cp = strrchr(peername, '-'); + char peername_flat[AST_MAX_EXTENSION]; /* using something like Zap/52 for an extension name is NOT a good idea */ + int i; + + if (cp) + *cp = 0; + ast_copy_string(peername_flat,peername,sizeof(peername_flat)); + for(i=0; peername_flat[i] && i < AST_MAX_EXTENSION; i++) { + if (peername_flat[i] == '/') + peername_flat[i]= '0'; + } + con = ast_context_find(parking_con_dial); + if (!con) { + con = ast_context_create(NULL, parking_con_dial, registrar); + if (!con) + ast_log(LOG_ERROR, "Parking dial context '%s' does not exist and unable to create\n", parking_con_dial); + } + if (con) { + char returnexten[AST_MAX_EXTENSION]; + snprintf(returnexten, sizeof(returnexten), "%s,,t", peername); + ast_add_extension2(con, 1, peername_flat, 1, NULL, NULL, "Dial", ast_strdup(returnexten), ast_free_ptr, registrar); + } + if (comebacktoorigin) { + set_c_e_p(chan, parking_con_dial, peername_flat, 1); + } else { + ast_log(LOG_WARNING, "now going to parkedcallstimeout,s,1 | ps is %d\n",pu->parkingnum); + snprintf(parkingslot, sizeof(parkingslot), "%d", pu->parkingnum); + pbx_builtin_setvar_helper(pu->chan, "PARKINGSLOT", parkingslot); + set_c_e_p(chan, "parkedcallstimeout", peername_flat, 1); + } + } else { + /* They've been waiting too long, send them back to where they came. Theoretically they + should have their original extensions and such, but we copy to be on the safe side */ + set_c_e_p(chan, pu->context, pu->exten, pu->priority); + } + + post_manager_event("ParkedCallTimeOut", pu); + + ast_verb(2, "Timeout for %s parked on %d. Returning to %s,%s,%d\n", chan->name, pu->parkingnum, chan->context, chan->exten, chan->priority); + /* Start up the PBX, or hang them up */ + if (ast_pbx_start(chan)) { + ast_log(LOG_WARNING, "Unable to restart the PBX for user on '%s', hanging them up...\n", chan->name); + ast_hangup(chan); + } + /* And take them out of the parking lot */ + AST_LIST_REMOVE_CURRENT(list); + con = ast_context_find(parking_con); + if (con) { + if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL)) + ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n"); + else + notify_metermaids(pu->parkingexten, parking_con, AST_DEVICE_NOT_INUSE); + } else + ast_log(LOG_WARNING, "Whoa, no parking context?\n"); + ast_free(pu); + } else { /* still within parking time, process descriptors */ + for (x = 0; x < AST_MAX_FDS; x++) { + struct ast_frame *f; + + if (chan->fds[x] == -1 || (!FD_ISSET(chan->fds[x], &rfds) && !FD_ISSET(chan->fds[x], &efds))) + continue; /* nothing on this descriptor */ + + if (FD_ISSET(chan->fds[x], &efds)) + ast_set_flag(chan, AST_FLAG_EXCEPTION); + else + ast_clear_flag(chan, AST_FLAG_EXCEPTION); + chan->fdno = x; + + /* See if they need servicing */ + f = ast_read(chan); + if (!f || (f->frametype == AST_FRAME_CONTROL && f->subclass == AST_CONTROL_HANGUP)) { + if (f) + ast_frfree(f); + post_manager_event("ParkedCallGiveUp", pu); + + /* There's a problem, hang them up*/ + ast_verb(2, "%s got tired of being parked\n", chan->name); + ast_hangup(chan); + /* And take them out of the parking lot */ + AST_LIST_REMOVE_CURRENT(list); + con = ast_context_find(parking_con); + if (con) { + if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL)) + ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n"); + else + notify_metermaids(pu->parkingexten, parking_con, AST_DEVICE_NOT_INUSE); + } else + ast_log(LOG_WARNING, "Whoa, no parking context?\n"); + ast_free(pu); + break; + } else { + /*! \todo XXX Maybe we could do something with packets, like dial "0" for operator or something XXX */ + ast_frfree(f); + if (pu->moh_trys < 3 && !chan->generatordata) { + ast_debug(1, "MOH on parked call stopped by outside source. Restarting.\n"); + ast_indicate_data(chan, AST_CONTROL_HOLD, + S_OR(parkmohclass, NULL), + !ast_strlen_zero(parkmohclass) ? strlen(parkmohclass) + 1 : 0); + pu->moh_trys++; + } + goto std; /*! \todo XXX Ick: jumping into an else statement??? XXX */ + } + + } /* end for */ + if (x >= AST_MAX_FDS) { +std: for (x=0; x<AST_MAX_FDS; x++) { /* mark fds for next round */ + if (chan->fds[x] > -1) { + FD_SET(chan->fds[x], &nrfds); + FD_SET(chan->fds[x], &nefds); + if (chan->fds[x] > max) + max = chan->fds[x]; + } + } + /* Keep track of our shortest wait */ + if (tms < ms || ms < 0) + ms = tms; + } + } + } /* end while */ + AST_LIST_TRAVERSE_SAFE_END + AST_LIST_UNLOCK(&parkinglot); + rfds = nrfds; + efds = nefds; + { + struct timeval tv = ast_samp2tv(ms, 1000); + /* Wait for something to happen */ + ast_select(max + 1, &rfds, NULL, &efds, (ms > -1) ? &tv : NULL); + } + pthread_testcancel(); + } + return NULL; /* Never reached */ +} + +/*! \brief Park a call */ +static int park_call_exec(struct ast_channel *chan, void *data) +{ + /* Cache the original channel name in case we get masqueraded in the middle + * of a park--it is still theoretically possible for a transfer to happen before + * we get here, but it is _really_ unlikely */ + char *orig_chan_name = ast_strdupa(chan->name); + char orig_exten[AST_MAX_EXTENSION]; + int orig_priority = chan->priority; + + /* Data is unused at the moment but could contain a parking + lot context eventually */ + int res = 0; + + ast_copy_string(orig_exten, chan->exten, sizeof(orig_exten)); + + /* Setup the exten/priority to be s/1 since we don't know + where this call should return */ + strcpy(chan->exten, "s"); + chan->priority = 1; + /* Answer if call is not up */ + if (chan->_state != AST_STATE_UP) + res = ast_answer(chan); + /* Sleep to allow VoIP streams to settle down */ + if (!res) + res = ast_safe_sleep(chan, 1000); + /* Park the call */ + if (!res) { + res = park_call_full(chan, chan, 0, NULL, orig_chan_name); + /* Continue on in the dialplan */ + if (res == 1) { + ast_copy_string(chan->exten, orig_exten, sizeof(chan->exten)); + chan->priority = orig_priority; + res = 0; + } else if (!res) + res = AST_PBX_KEEPALIVE; + } + + return res; +} + +/*! \brief Pickup parked call */ +static int park_exec(struct ast_channel *chan, void *data) +{ + int res = 0; + struct ast_channel *peer=NULL; + struct parkeduser *pu; + struct ast_context *con; + int park = 0; + struct ast_bridge_config config; + + if (data) + park = atoi((char *)data); + + AST_LIST_LOCK(&parkinglot); + AST_LIST_TRAVERSE_SAFE_BEGIN(&parkinglot, pu, list) { + if (!data || pu->parkingnum == park) { + AST_LIST_REMOVE_CURRENT(list); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END + AST_LIST_UNLOCK(&parkinglot); + + if (pu) { + peer = pu->chan; + con = ast_context_find(parking_con); + if (con) { + if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL)) + ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n"); + else + notify_metermaids(pu->parkingexten, parking_con, AST_DEVICE_NOT_INUSE); + } else + ast_log(LOG_WARNING, "Whoa, no parking context?\n"); + + manager_event(EVENT_FLAG_CALL, "UnParkedCall", + "Exten: %s\r\n" + "Channel: %s\r\n" + "From: %s\r\n" + "CallerIDNum: %s\r\n" + "CallerIDName: %s\r\n", + pu->parkingexten, pu->chan->name, chan->name, + S_OR(pu->chan->cid.cid_num, "<unknown>"), + S_OR(pu->chan->cid.cid_name, "<unknown>") + ); + + ast_free(pu); + } + /* JK02: it helps to answer the channel if not already up */ + if (chan->_state != AST_STATE_UP) + ast_answer(chan); + + if (peer) { + /* Play a courtesy to the source(s) configured to prefix the bridge connecting */ + + if (!ast_strlen_zero(courtesytone)) { + int error = 0; + ast_indicate(peer, AST_CONTROL_UNHOLD); + if (parkedplay == 0) { + error = ast_stream_and_wait(chan, courtesytone, ""); + } else if (parkedplay == 1) { + error = ast_stream_and_wait(peer, courtesytone, ""); + } else if (parkedplay == 2) { + if (!ast_streamfile(chan, courtesytone, chan->language) && + !ast_streamfile(peer, courtesytone, chan->language)) { + /*! \todo XXX we would like to wait on both! */ + res = ast_waitstream(chan, ""); + if (res >= 0) + res = ast_waitstream(peer, ""); + if (res < 0) + error = 1; + } + } + if (error) { + ast_log(LOG_WARNING, "Failed to play courtesy tone!\n"); + ast_hangup(peer); + return -1; + } + } else + ast_indicate(peer, AST_CONTROL_UNHOLD); + + res = ast_channel_make_compatible(chan, peer); + if (res < 0) { + ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", chan->name, peer->name); + ast_hangup(peer); + return -1; + } + /* This runs sorta backwards, since we give the incoming channel control, as if it + were the person called. */ + ast_verb(3, "Channel %s connected to parked call %d\n", chan->name, park); + + pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", peer->name); + ast_cdr_setdestchan(chan->cdr, peer->name); + memset(&config, 0, sizeof(struct ast_bridge_config)); + if ((parkedcalltransfers == AST_FEATURE_FLAG_BYCALLEE) || (parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH)) + ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT); + if ((parkedcalltransfers == AST_FEATURE_FLAG_BYCALLER) || (parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH)) + ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT); + if ((parkedcallreparking == AST_FEATURE_FLAG_BYCALLEE) || (parkedcallreparking == AST_FEATURE_FLAG_BYBOTH)) + ast_set_flag(&(config.features_callee), AST_FEATURE_PARKCALL); + if ((parkedcallreparking == AST_FEATURE_FLAG_BYCALLER) || (parkedcallreparking == AST_FEATURE_FLAG_BYBOTH)) + ast_set_flag(&(config.features_caller), AST_FEATURE_PARKCALL); + res = ast_bridge_call(chan, peer, &config); + + pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", peer->name); + ast_cdr_setdestchan(chan->cdr, peer->name); + + /* Simulate the PBX hanging up */ + if (res != AST_PBX_NO_HANGUP_PEER) + ast_hangup(peer); + return res; + } else { + /*! \todo XXX Play a message XXX */ + if (ast_stream_and_wait(chan, "pbx-invalidpark", "")) + ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", "pbx-invalidpark", chan->name); + ast_verb(3, "Channel %s tried to talk to nonexistent parked call %d\n", chan->name, park); + res = -1; + } + + return res; +} + +/*! + * \brief CLI command to list configured features + * \param e + * \param cmd + * \param a + * + * \retval CLI_SUCCESS on success. + * \retval NULL when tab completion is used. + */ +static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { + int i; + struct ast_call_feature *feature; + char format[] = "%-25s %-7s %-7s\n"; + + switch (cmd) { + + case CLI_INIT: + e->command = "features show"; + e->usage = + "Usage: features show\n" + " Lists configured features\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ast_cli(a->fd, format, "Builtin Feature", "Default", "Current"); + ast_cli(a->fd, format, "---------------", "-------", "-------"); + + ast_cli(a->fd, format, "Pickup", "*8", ast_pickup_ext()); /* default hardcoded above, so we'll hardcode it here */ + + ast_rwlock_rdlock(&features_lock); + for (i = 0; i < FEATURES_COUNT; i++) + ast_cli(a->fd, format, builtin_features[i].fname, builtin_features[i].default_exten, builtin_features[i].exten); + ast_rwlock_unlock(&features_lock); + + ast_cli(a->fd, "\n"); + ast_cli(a->fd, format, "Dynamic Feature", "Default", "Current"); + ast_cli(a->fd, format, "---------------", "-------", "-------"); + if (AST_LIST_EMPTY(&feature_list)) + ast_cli(a->fd, "(none)\n"); + else { + AST_LIST_LOCK(&feature_list); + AST_LIST_TRAVERSE(&feature_list, feature, feature_entry) + ast_cli(a->fd, format, feature->sname, "no def", feature->exten); + AST_LIST_UNLOCK(&feature_list); + } + ast_cli(a->fd, "\nCall parking\n"); + ast_cli(a->fd, "------------\n"); + ast_cli(a->fd,"%-20s: %s\n", "Parking extension", parking_ext); + ast_cli(a->fd,"%-20s: %s\n", "Parking context", parking_con); + ast_cli(a->fd,"%-20s: %d-%d\n", "Parked call extensions", parking_start, parking_stop); + ast_cli(a->fd,"\n"); + + return CLI_SUCCESS; +} + +static char mandescr_bridge[] = +"Description: Bridge together two channels already in the PBX\n" +"Variables: ( Headers marked with * are required )\n" +" *Channel1: Channel to Bridge to Channel2\n" +" *Channel2: Channel to Bridge to Channel1\n" +" Tone: (Yes|No) Play courtesy tone to Channel 2\n" +"\n"; + +/*! + * \brief Actual bridge + * \param chan + * \param tmpchan + * + * Stop hold music, lock both channels, masq channels, + * after bridge return channel to next priority. +*/ +static void do_bridge_masquerade(struct ast_channel *chan, struct ast_channel *tmpchan) +{ + ast_moh_stop(chan); + ast_channel_lock(chan); + ast_setstate(tmpchan, chan->_state); + tmpchan->readformat = chan->readformat; + tmpchan->writeformat = chan->writeformat; + ast_channel_masquerade(tmpchan, chan); + ast_channel_lock(tmpchan); + ast_do_masquerade(tmpchan); + /* when returning from bridge, the channel will continue at the next priority */ + ast_explicit_goto(tmpchan, chan->context, chan->exten, chan->priority + 1); + ast_channel_unlock(tmpchan); + ast_channel_unlock(chan); +} + +/*! + * \brief Bridge channels together + * \param s + * \param m + * + * Make sure valid channels were specified, + * send errors if any of the channels could not be found/locked, answer channels if needed, + * create the placeholder channels and grab the other channels + * make the channels compatible, send error if we fail doing so + * setup the bridge thread object and start the bridge. + * + * \retval 0 on success or on incorrect use. + * \retval 1 on failure to bridge channels. +*/ +static int action_bridge(struct mansession *s, const struct message *m) +{ + const char *channela = astman_get_header(m, "Channel1"); + const char *channelb = astman_get_header(m, "Channel2"); + const char *playtone = astman_get_header(m, "Tone"); + struct ast_channel *chana = NULL, *chanb = NULL; + struct ast_channel *tmpchana = NULL, *tmpchanb = NULL; + struct ast_bridge_thread_obj *tobj = NULL; + + /* make sure valid channels were specified */ + if (!ast_strlen_zero(channela) && !ast_strlen_zero(channelb)) { + chana = ast_get_channel_by_name_prefix_locked(channela, strlen(channela)); + chanb = ast_get_channel_by_name_prefix_locked(channelb, strlen(channelb)); + if (chana) + ast_channel_unlock(chana); + if (chanb) + ast_channel_unlock(chanb); + + /* send errors if any of the channels could not be found/locked */ + if (!chana) { + char buf[256]; + snprintf(buf, sizeof(buf), "Channel1 does not exists: %s", channela); + astman_send_error(s, m, buf); + return 0; + } + if (!chanb) { + char buf[256]; + snprintf(buf, sizeof(buf), "Channel2 does not exists: %s", channelb); + astman_send_error(s, m, buf); + return 0; + } + } else { + astman_send_error(s, m, "Missing channel parameter in request"); + return 0; + } + + /* Answer the channels if needed */ + if (chana->_state != AST_STATE_UP) + ast_answer(chana); + if (chanb->_state != AST_STATE_UP) + ast_answer(chanb); + + /* create the placeholder channels and grab the other channels */ + if (!(tmpchana = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, + NULL, NULL, 0, "Bridge/%s", chana->name))) { + astman_send_error(s, m, "Unable to create temporary channel!"); + return 1; + } + + if (!(tmpchanb = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, + NULL, NULL, 0, "Bridge/%s", chanb->name))) { + astman_send_error(s, m, "Unable to create temporary channels!"); + ast_channel_free(tmpchana); + return 1; + } + + do_bridge_masquerade(chana, tmpchana); + do_bridge_masquerade(chanb, tmpchanb); + + /* make the channels compatible, send error if we fail doing so */ + if (ast_channel_make_compatible(tmpchana, tmpchanb)) { + ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for manager bridge\n", tmpchana->name, tmpchanb->name); + astman_send_error(s, m, "Could not make channels compatible for manager bridge"); + ast_hangup(tmpchana); + ast_hangup(tmpchanb); + return 1; + } + + /* setup the bridge thread object and start the bridge */ + if (!(tobj = ast_calloc(1, sizeof(*tobj)))) { + ast_log(LOG_WARNING, "Unable to spawn a new bridge thread on %s and %s: %s\n", tmpchana->name, tmpchanb->name, strerror(errno)); + astman_send_error(s, m, "Unable to spawn a new bridge thread"); + ast_hangup(tmpchana); + ast_hangup(tmpchanb); + return 1; + } + + tobj->chan = tmpchana; + tobj->peer = tmpchanb; + tobj->return_to_pbx = 1; + + if (ast_true(playtone)) { + if (!ast_strlen_zero(xfersound) && !ast_streamfile(tmpchanb, xfersound, tmpchanb->language)) { + if (ast_waitstream(tmpchanb, "") < 0) + ast_log(LOG_WARNING, "Failed to play a courtesy tone on chan %s\n", tmpchanb->name); + } + } + + ast_bridge_call_thread_launch(tobj); + + astman_send_ack(s, m, "Launched bridge thread with success"); + + return 0; +} + +/*! + * \brief CLI command to list parked calls + * \param e + * \param cmd + * \param a + * + * Check right usage, lock parking lot, display parked calls, unlock parking lot list. + * \retval CLI_SUCCESS on success. + * \retval CLI_SHOWUSAGE on incorrect number of arguments. + * \retval NULL when tab completion is used. +*/ +static char *handle_parkedcalls(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct parkeduser *cur; + int numparked = 0; + + switch (cmd) { + case CLI_INIT: + e->command = "parkedcalls show"; + e->usage = + "Usage: parkedcalls show\n" + " List currently parked calls\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc > e->args) + return CLI_SHOWUSAGE; + + ast_cli(a->fd, "%4s %25s (%-15s %-12s %-4s) %-6s \n", "Num", "Channel" + , "Context", "Extension", "Pri", "Timeout"); + + AST_LIST_LOCK(&parkinglot); + AST_LIST_TRAVERSE(&parkinglot, cur, list) { + ast_cli(a->fd, "%-10.10s %25s (%-15s %-12s %-4d) %6lds\n" + ,cur->parkingexten, cur->chan->name, cur->context, cur->exten + ,cur->priority, cur->start.tv_sec + (cur->parkingtime/1000) - time(NULL)); + + numparked++; + } + AST_LIST_UNLOCK(&parkinglot); + ast_cli(a->fd, "%d parked call%s.\n", numparked, ESS(numparked)); + + + return CLI_SUCCESS; +} + +static char *handle_parkedcalls_deprecated(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + char *res = handle_parkedcalls(e, cmd, a); + if (cmd == CLI_INIT) + e->command = "show parkedcalls"; + return res; +} + +static struct ast_cli_entry cli_show_parkedcalls_deprecated = AST_CLI_DEFINE(handle_parkedcalls_deprecated, "List currently parked calls."); + +static struct ast_cli_entry cli_features[] = { + AST_CLI_DEFINE(handle_feature_show, "Lists configured features"), + AST_CLI_DEFINE(handle_parkedcalls, "List currently parked calls", .deprecate_cmd = &cli_show_parkedcalls_deprecated), +}; + +/*! + * \brief Dump parking lot status + * \param s + * \param m + * + * Lock parking lot, iterate list and append parked calls status, unlock parking lot. + * \return Always RESULT_SUCCESS +*/ +static int manager_parking_status(struct mansession *s, const struct message *m) +{ + struct parkeduser *cur; + const char *id = astman_get_header(m, "ActionID"); + char idText[256] = ""; + + if (!ast_strlen_zero(id)) + snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id); + + astman_send_ack(s, m, "Parked calls will follow"); + + AST_LIST_LOCK(&parkinglot); + + AST_LIST_TRAVERSE(&parkinglot, cur, list) { + astman_append(s, "Event: ParkedCall\r\n" + "Exten: %d\r\n" + "Channel: %s\r\n" + "From: %s\r\n" + "Timeout: %ld\r\n" + "CallerIDNum: %s\r\n" + "CallerIDName: %s\r\n" + "%s" + "\r\n", + cur->parkingnum, cur->chan->name, cur->peername, + (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - (long) time(NULL), + S_OR(cur->chan->cid.cid_num, ""), /* XXX in other places it is <unknown> */ + S_OR(cur->chan->cid.cid_name, ""), + idText); + } + + astman_append(s, + "Event: ParkedCallsComplete\r\n" + "%s" + "\r\n",idText); + + AST_LIST_UNLOCK(&parkinglot); + + return RESULT_SUCCESS; +} + +static char mandescr_park[] = +"Description: Park a channel.\n" +"Variables: (Names marked with * are required)\n" +" *Channel: Channel name to park\n" +" *Channel2: Channel to announce park info to (and return to if timeout)\n" +" Timeout: Number of milliseconds to wait before callback.\n"; + +/*! + * \brief Create manager event for parked calls + * \param s + * \param m + * + * Get channels involved in park, create event. + * \return Always 0 +*/ +static int manager_park(struct mansession *s, const struct message *m) +{ + const char *channel = astman_get_header(m, "Channel"); + const char *channel2 = astman_get_header(m, "Channel2"); + const char *timeout = astman_get_header(m, "Timeout"); + char buf[BUFSIZ]; + int to = 0; + int res = 0; + int parkExt = 0; + struct ast_channel *ch1, *ch2; + + if (ast_strlen_zero(channel)) { + astman_send_error(s, m, "Channel not specified"); + return 0; + } + + if (ast_strlen_zero(channel2)) { + astman_send_error(s, m, "Channel2 not specified"); + return 0; + } + + ch1 = ast_get_channel_by_name_locked(channel); + if (!ch1) { + snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel); + astman_send_error(s, m, buf); + return 0; + } + + ch2 = ast_get_channel_by_name_locked(channel2); + if (!ch2) { + snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel2); + astman_send_error(s, m, buf); + ast_channel_unlock(ch1); + return 0; + } + + if (!ast_strlen_zero(timeout)) { + sscanf(timeout, "%d", &to); + } + + res = ast_masq_park_call(ch1, ch2, to, &parkExt); + if (!res) { + ast_softhangup(ch2, AST_SOFTHANGUP_EXPLICIT); + astman_send_ack(s, m, "Park successful"); + } else { + astman_send_error(s, m, "Park failure"); + } + + ast_channel_unlock(ch1); + ast_channel_unlock(ch2); + + return 0; +} + +/*! + * \brief Pickup a call + * \param chan channel that initiated pickup. + * + * Walk list of channels, checking it is not itself, channel is pbx one, + * check that the callgroup for both channels are the same and the channel is ringing. + * Answer calling channel, flag channel as answered on queue, masq channels together. +*/ +int ast_pickup_call(struct ast_channel *chan) +{ + struct ast_channel *cur = NULL; + int res = -1; + + while ((cur = ast_channel_walk_locked(cur)) != NULL) { + if (!cur->pbx && + (cur != chan) && + (chan->pickupgroup & cur->callgroup) && + ((cur->_state == AST_STATE_RINGING) || + (cur->_state == AST_STATE_RING))) { + break; + } + ast_channel_unlock(cur); + } + if (cur) { + ast_debug(1, "Call pickup on chan '%s' by '%s'\n",cur->name, chan->name); + res = ast_answer(chan); + if (res) + ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan->name); + res = ast_queue_control(chan, AST_CONTROL_ANSWER); + if (res) + ast_log(LOG_WARNING, "Unable to queue answer on '%s'\n", chan->name); + res = ast_channel_masquerade(cur, chan); + if (res) + ast_log(LOG_WARNING, "Unable to masquerade '%s' into '%s'\n", chan->name, cur->name); /* Done */ + ast_channel_unlock(cur); + } else { + ast_debug(1, "No call pickup possible...\n"); + } + return res; +} + +/*! + * \brief Add parking hints for all defined parking lots + * \param context + * \param start starting parkinglot number + * \param stop ending parkinglot number +*/ +static void park_add_hints(char *context, int start, int stop) +{ + int numext; + char device[AST_MAX_EXTENSION]; + char exten[10]; + + for (numext = start; numext <= stop; numext++) { + snprintf(exten, sizeof(exten), "%d", numext); + snprintf(device, sizeof(device), "park:%s@%s", exten, context); + ast_add_extension(context, 1, exten, PRIORITY_HINT, NULL, NULL, device, NULL, NULL, registrar); + } +} + + +static int load_config(void) +{ + int start = 0, end = 0; + int res; + int i; + struct ast_context *con = NULL; + struct ast_config *cfg = NULL; + struct ast_variable *var = NULL; + struct feature_group *fg = NULL; + struct ast_flags config_flags = { 0 }; + char old_parking_ext[AST_MAX_EXTENSION]; + char old_parking_con[AST_MAX_EXTENSION] = ""; + char *ctg; + static const char *categories[] = { + /* Categories in features.conf that are not + * to be parsed as group categories + */ + "general", + "featuremap", + "applicationmap" + }; + + if (!ast_strlen_zero(parking_con)) { + strcpy(old_parking_ext, parking_ext); + strcpy(old_parking_con, parking_con); + } + + /* Reset to defaults */ + strcpy(parking_con, "parkedcalls"); + strcpy(parking_con_dial, "park-dial"); + strcpy(parking_ext, "700"); + strcpy(pickup_ext, "*8"); + strcpy(parkmohclass, "default"); + courtesytone[0] = '\0'; + strcpy(xfersound, "beep"); + strcpy(xferfailsound, "pbx-invalid"); + parking_start = 701; + parking_stop = 750; + parkfindnext = 0; + adsipark = 0; + comebacktoorigin = 1; + parkaddhints = 0; + parkedcalltransfers = 0; + parkedcallreparking = 0; + + transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT; + featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT; + atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER; + atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY; + atxferdropcall = DEFAULT_ATXFER_DROP_CALL; + atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES; + + cfg = ast_config_load("features.conf", config_flags); + if (!cfg) { + ast_log(LOG_WARNING,"Could not load features.conf\n"); + return AST_MODULE_LOAD_DECLINE; + } + for (var = ast_variable_browse(cfg, "general"); var; var = var->next) { + if (!strcasecmp(var->name, "parkext")) { + ast_copy_string(parking_ext, var->value, sizeof(parking_ext)); + } else if (!strcasecmp(var->name, "context")) { + ast_copy_string(parking_con, var->value, sizeof(parking_con)); + } else if (!strcasecmp(var->name, "parkingtime")) { + if ((sscanf(var->value, "%d", &parkingtime) != 1) || (parkingtime < 1)) { + ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value); + parkingtime = DEFAULT_PARK_TIME; + } else + parkingtime = parkingtime * 1000; + } else if (!strcasecmp(var->name, "parkpos")) { + if (sscanf(var->value, "%d-%d", &start, &end) != 2) { + ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers at line %d of features.conf\n", var->lineno); + } else { + parking_start = start; + parking_stop = end; + } + } else if (!strcasecmp(var->name, "findslot")) { + parkfindnext = (!strcasecmp(var->value, "next")); + } else if (!strcasecmp(var->name, "parkinghints")) { + parkaddhints = ast_true(var->value); + } else if (!strcasecmp(var->name, "parkedcalltransfers")) { + if (!strcasecmp(var->value, "both")) + parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH; + else if (!strcasecmp(var->value, "caller")) + parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER; + else if (!strcasecmp(var->value, "callee")) + parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE; + } else if (!strcasecmp(var->name, "parkedcallreparking")) { + if (!strcasecmp(var->value, "both")) + parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH; + else if (!strcasecmp(var->value, "caller")) + parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER; + else if (!strcasecmp(var->value, "callee")) + parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE; + } else if (!strcasecmp(var->name, "adsipark")) { + adsipark = ast_true(var->value); + } else if (!strcasecmp(var->name, "transferdigittimeout")) { + if ((sscanf(var->value, "%d", &transferdigittimeout) != 1) || (transferdigittimeout < 1)) { + ast_log(LOG_WARNING, "%s is not a valid transferdigittimeout\n", var->value); + transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT; + } else + transferdigittimeout = transferdigittimeout * 1000; + } else if (!strcasecmp(var->name, "featuredigittimeout")) { + if ((sscanf(var->value, "%d", &featuredigittimeout) != 1) || (featuredigittimeout < 1)) { + ast_log(LOG_WARNING, "%s is not a valid featuredigittimeout\n", var->value); + featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT; + } + } else if (!strcasecmp(var->name, "atxfernoanswertimeout")) { + if ((sscanf(var->value, "%d", &atxfernoanswertimeout) != 1) || (atxfernoanswertimeout < 1)) { + ast_log(LOG_WARNING, "%s is not a valid atxfernoanswertimeout\n", var->value); + atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER; + } else + atxfernoanswertimeout = atxfernoanswertimeout * 1000; + } else if (!strcasecmp(var->name, "atxferloopdelay")) { + if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) { + ast_log(LOG_WARNING, "%s is not a valid atxferloopdelay\n", var->value); + atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY; + } else + atxferloopdelay *= 1000; + } else if (!strcasecmp(var->name, "atxferdropcall")) { + atxferdropcall = ast_true(var->value); + } else if (!strcasecmp(var->name, "atxfercallbackretries")) { + if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) { + ast_log(LOG_WARNING, "%s is not a valid atxfercallbackretries\n", var->value); + atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES; + } + } else if (!strcasecmp(var->name, "courtesytone")) { + ast_copy_string(courtesytone, var->value, sizeof(courtesytone)); + } else if (!strcasecmp(var->name, "parkedplay")) { + if (!strcasecmp(var->value, "both")) + parkedplay = 2; + else if (!strcasecmp(var->value, "parked")) + parkedplay = 1; + else + parkedplay = 0; + } else if (!strcasecmp(var->name, "xfersound")) { + ast_copy_string(xfersound, var->value, sizeof(xfersound)); + } else if (!strcasecmp(var->name, "xferfailsound")) { + ast_copy_string(xferfailsound, var->value, sizeof(xferfailsound)); + } else if (!strcasecmp(var->name, "pickupexten")) { + ast_copy_string(pickup_ext, var->value, sizeof(pickup_ext)); + } else if (!strcasecmp(var->name, "comebacktoorigin")) { + comebacktoorigin = ast_true(var->value); + } else if (!strcasecmp(var->name, "parkedmusicclass")) { + ast_copy_string(parkmohclass, var->value, sizeof(parkmohclass)); + } + } + + unmap_features(); + for (var = ast_variable_browse(cfg, "featuremap"); var; var = var->next) { + if (remap_feature(var->name, var->value)) + ast_log(LOG_NOTICE, "Unknown feature '%s'\n", var->name); + } + + /* Map a key combination to an application*/ + ast_unregister_features(); + for (var = ast_variable_browse(cfg, "applicationmap"); var; var = var->next) { + char *tmp_val = ast_strdupa(var->value); + char *exten, *activateon, *activatedby, *app, *app_args, *moh_class; + struct ast_call_feature *feature; + + /* strsep() sets the argument to NULL if match not found, and it + * is safe to use it with a NULL argument, so we don't check + * between calls. + */ + exten = strsep(&tmp_val,","); + activatedby = strsep(&tmp_val,","); + app = strsep(&tmp_val,","); + app_args = strsep(&tmp_val,","); + moh_class = strsep(&tmp_val,","); + + activateon = strsep(&activatedby, "/"); + + /*! \todo XXX var_name or app_args ? */ + if (ast_strlen_zero(app) || ast_strlen_zero(exten) || ast_strlen_zero(activateon) || ast_strlen_zero(var->name)) { + ast_log(LOG_NOTICE, "Please check the feature Mapping Syntax, either extension, name, or app aren't provided %s %s %s %s\n", + app, exten, activateon, var->name); + continue; + } + + AST_LIST_LOCK(&feature_list); + if ((feature = find_dynamic_feature(var->name))) { + AST_LIST_UNLOCK(&feature_list); + ast_log(LOG_WARNING, "Dynamic Feature '%s' specified more than once!\n", var->name); + continue; + } + AST_LIST_UNLOCK(&feature_list); + + if (!(feature = ast_calloc(1, sizeof(*feature)))) + continue; + + ast_copy_string(feature->sname, var->name, FEATURE_SNAME_LEN); + ast_copy_string(feature->app, app, FEATURE_APP_LEN); + ast_copy_string(feature->exten, exten, FEATURE_EXTEN_LEN); + + if (app_args) + ast_copy_string(feature->app_args, app_args, FEATURE_APP_ARGS_LEN); + + if (moh_class) + ast_copy_string(feature->moh_class, moh_class, FEATURE_MOH_LEN); + + ast_copy_string(feature->exten, exten, sizeof(feature->exten)); + feature->operation = feature_exec_app; + ast_set_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF); + + /* Allow caller and calle to be specified for backwards compatability */ + if (!strcasecmp(activateon, "self") || !strcasecmp(activateon, "caller")) + ast_set_flag(feature, AST_FEATURE_FLAG_ONSELF); + else if (!strcasecmp(activateon, "peer") || !strcasecmp(activateon, "callee")) + ast_set_flag(feature, AST_FEATURE_FLAG_ONPEER); + else { + ast_log(LOG_NOTICE, "Invalid 'ActivateOn' specification for feature '%s'," + " must be 'self', or 'peer'\n", var->name); + continue; + } + + if (ast_strlen_zero(activatedby)) + ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH); + else if (!strcasecmp(activatedby, "caller")) + ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLER); + else if (!strcasecmp(activatedby, "callee")) + ast_set_flag(feature, AST_FEATURE_FLAG_BYCALLEE); + else if (!strcasecmp(activatedby, "both")) + ast_set_flag(feature, AST_FEATURE_FLAG_BYBOTH); + else { + ast_log(LOG_NOTICE, "Invalid 'ActivatedBy' specification for feature '%s'," + " must be 'caller', or 'callee', or 'both'\n", var->name); + continue; + } + + ast_register_feature(feature); + + ast_verb(2, "Mapping Feature '%s' to app '%s(%s)' with code '%s'\n", var->name, app, app_args, exten); + } + + ast_unregister_groups(); + AST_RWLIST_WRLOCK(&feature_groups); + + ctg = NULL; + while ((ctg = ast_category_browse(cfg, ctg))) { + for (i = 0; i < ARRAY_LEN(categories); i++) { + if (!strcasecmp(categories[i], ctg)) + break; + } + + if (i < ARRAY_LEN(categories)) + continue; + + if (!(fg = register_group(ctg))) + continue; + + for (var = ast_variable_browse(cfg, ctg); var; var = var->next) { + struct ast_call_feature *feature; + + AST_LIST_LOCK(&feature_list); + if(!(feature = find_dynamic_feature(var->name)) && + !(feature = ast_find_call_feature(var->name))) { + AST_LIST_UNLOCK(&feature_list); + ast_log(LOG_WARNING, "Feature '%s' was not found.\n", var->name); + continue; + } + AST_LIST_UNLOCK(&feature_list); + + register_group_feature(fg, var->value, feature); + } + } + + AST_RWLIST_UNLOCK(&feature_groups); + + ast_config_destroy(cfg); + + /* Remove the old parking extension */ + if (!ast_strlen_zero(old_parking_con) && (con = ast_context_find(old_parking_con))) { + if(ast_context_remove_extension2(con, old_parking_ext, 1, registrar)) + notify_metermaids(old_parking_ext, old_parking_con, AST_DEVICE_NOT_INUSE); + ast_debug(1, "Removed old parking extension %s@%s\n", old_parking_ext, old_parking_con); + } + + if (!(con = ast_context_find(parking_con)) && !(con = ast_context_create(NULL, parking_con, registrar))) { + ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con); + return -1; + } + res = ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, NULL, NULL, registrar); + if (parkaddhints) + park_add_hints(parking_con, parking_start, parking_stop); + if (!res) + notify_metermaids(ast_parking_ext(), parking_con, AST_DEVICE_INUSE); + return res; + +} + +static char *app_bridge = "Bridge"; +static char *bridge_synopsis = "Bridge two channels"; +static char *bridge_descrip = +"Usage: Bridge(channel[,options])\n" +" Allows the ability to bridge two channels via the dialplan.\n" +"The current channel is bridged to the specified 'channel'.\n" +" Options:\n" +" p - Play a courtesy tone to 'channel'.\n" +"This application sets the following channel variable upon completion:\n" +" BRIDGERESULT The result of the bridge attempt as a text string, one of\n" +" SUCCESS | FAILURE | LOOP | NONEXISTENT | INCOMPATIBLE\n"; + +enum { + BRIDGE_OPT_PLAYTONE = (1 << 0), +}; + +AST_APP_OPTIONS(bridge_exec_options, BEGIN_OPTIONS + AST_APP_OPTION('p', BRIDGE_OPT_PLAYTONE) +END_OPTIONS ); + +/*! + * \brief Bridge channels + * \param chan + * \param data channel to bridge with. + * + * Split data, check we aren't bridging with ourself, check valid channel, + * answer call if not already, check compatible channels, setup bridge config + * now bridge call, if transfered party hangs up return to PBX extension. +*/ +static int bridge_exec(struct ast_channel *chan, void *data) +{ + struct ast_channel *current_dest_chan, *final_dest_chan; + char *tmp_data = NULL; + struct ast_flags opts = { 0, }; + struct ast_bridge_config bconfig = { { 0, }, }; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(dest_chan); + AST_APP_ARG(options); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "Bridge require at least 1 argument specifying the other end of the bridge\n"); + return -1; + } + + tmp_data = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, tmp_data); + if (!ast_strlen_zero(args.options)) + ast_app_parse_options(bridge_exec_options, &opts, NULL, args.options); + + /* avoid bridge with ourselves */ + if (!strncmp(chan->name, args.dest_chan, + strlen(chan->name) < strlen(args.dest_chan) ? + strlen(chan->name) : strlen(args.dest_chan))) { + ast_log(LOG_WARNING, "Unable to bridge channel %s with itself\n", chan->name); + manager_event(EVENT_FLAG_CALL, "BridgeExec", + "Response: Failed\r\n" + "Reason: Unable to bridge channel to itself\r\n" + "Channel1: %s\r\n" + "Channel2: %s\r\n", + chan->name, args.dest_chan); + pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "LOOP"); + return 0; + } + + /* make sure we have a valid end point */ + if (!(current_dest_chan = ast_get_channel_by_name_prefix_locked(args.dest_chan, + strlen(args.dest_chan)))) { + ast_log(LOG_WARNING, "Bridge failed because channel %s does not exists or we " + "cannot get its lock\n", args.dest_chan); + manager_event(EVENT_FLAG_CALL, "BridgeExec", + "Response: Failed\r\n" + "Reason: Cannot grab end point\r\n" + "Channel1: %s\r\n" + "Channel2: %s\r\n", chan->name, args.dest_chan); + pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "NONEXISTENT"); + return 0; + } + ast_channel_unlock(current_dest_chan); + + /* answer the channel if needed */ + if (current_dest_chan->_state != AST_STATE_UP) + ast_answer(current_dest_chan); + + /* try to allocate a place holder where current_dest_chan will be placed */ + if (!(final_dest_chan = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, + NULL, NULL, 0, "Bridge/%s", current_dest_chan->name))) { + ast_log(LOG_WARNING, "Cannot create placeholder channel for chan %s\n", args.dest_chan); + manager_event(EVENT_FLAG_CALL, "BridgeExec", + "Response: Failed\r\n" + "Reason: cannot create placeholder\r\n" + "Channel1: %s\r\n" + "Channel2: %s\r\n", chan->name, args.dest_chan); + } + do_bridge_masquerade(current_dest_chan, final_dest_chan); + + /* now current_dest_chan is a ZOMBIE and with softhangup set to 1 and final_dest_chan is our end point */ + /* try to make compatible, send error if we fail */ + if (ast_channel_make_compatible(chan, final_dest_chan) < 0) { + ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", chan->name, final_dest_chan->name); + manager_event(EVENT_FLAG_CALL, "BridgeExec", + "Response: Failed\r\n" + "Reason: Could not make channels compatible for bridge\r\n" + "Channel1: %s\r\n" + "Channel2: %s\r\n", chan->name, final_dest_chan->name); + ast_hangup(final_dest_chan); /* may be we should return this channel to the PBX? */ + pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "INCOMPATIBLE"); + return 0; + } + + /* Report that the bridge will be successfull */ + manager_event(EVENT_FLAG_CALL, "BridgeExec", + "Response: Success\r\n" + "Channel1: %s\r\n" + "Channel2: %s\r\n", chan->name, final_dest_chan->name); + + /* we have 2 valid channels to bridge, now it is just a matter of setting up the bridge config and starting the bridge */ + if (ast_test_flag(&opts, BRIDGE_OPT_PLAYTONE) && !ast_strlen_zero(xfersound)) { + if (!ast_streamfile(final_dest_chan, xfersound, final_dest_chan->language)) { + if (ast_waitstream(final_dest_chan, "") < 0) + ast_log(LOG_WARNING, "Failed to play courtesy tone on %s\n", final_dest_chan->name); + } + } + + /* do the bridge */ + ast_bridge_call(chan, final_dest_chan, &bconfig); + + /* the bridge has ended, set BRIDGERESULT to SUCCESS. If the other channel has not been hung up, return it to the PBX */ + pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "SUCCESS"); + if (!ast_check_hangup(final_dest_chan)) { + ast_debug(1, "starting new PBX in %s,%s,%d for chan %s\n", + final_dest_chan->context, final_dest_chan->exten, + final_dest_chan->priority, final_dest_chan->name); + + if (ast_pbx_start(final_dest_chan) != AST_PBX_SUCCESS) { + ast_log(LOG_WARNING, "FAILED continuing PBX on dest chan %s\n", final_dest_chan->name); + ast_hangup(final_dest_chan); + } else + ast_debug(1, "SUCCESS continuing PBX on chan %s\n", final_dest_chan->name); + } else { + ast_debug(1, "hangup chan %s since the other endpoint has hung up\n", final_dest_chan->name); + ast_hangup(final_dest_chan); + } + + return 0; +} + +static int reload(void) +{ + return load_config(); +} + +static int load_module(void) +{ + int res; + + ast_register_application(app_bridge, bridge_exec, bridge_synopsis, bridge_descrip); + + memset(parking_ext, 0, sizeof(parking_ext)); + memset(parking_con, 0, sizeof(parking_con)); + + if ((res = load_config())) + return res; + ast_cli_register_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry)); + ast_pthread_create(&parking_thread, NULL, do_parking_thread, NULL); + res = ast_register_application(parkedcall, park_exec, synopsis, descrip); + if (!res) + res = ast_register_application(parkcall, park_call_exec, synopsis2, descrip2); + if (!res) { + ast_manager_register("ParkedCalls", 0, manager_parking_status, "List parked calls"); + ast_manager_register2("Park", EVENT_FLAG_CALL, manager_park, + "Park a channel", mandescr_park); + ast_manager_register2("Bridge", EVENT_FLAG_CALL, action_bridge, "Bridge two channels already in the PBX", mandescr_bridge); + } + + res |= ast_devstate_prov_add("Park", metermaidstate); + + return res; +} + + +static int unload_module(void) +{ + struct ast_context *con; + ast_manager_unregister("ParkedCalls"); + ast_manager_unregister("Bridge"); + ast_manager_unregister("Park"); + ast_cli_unregister_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry)); + ast_unregister_application(parkcall); + ast_unregister_application(app_bridge); + ast_devstate_prov_del("Park"); + con = ast_context_find(parking_con); + if (con) + ast_context_destroy(con, registrar); + con = ast_context_find(parking_con_dial); + if (con) + ast_context_destroy(con, registrar); + return ast_unregister_application(parkedcall); +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Call Features Resource", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); diff --git a/trunk/res/res_indications.c b/trunk/res/res_indications.c new file mode 100644 index 000000000..a957b7828 --- /dev/null +++ b/trunk/res/res_indications.c @@ -0,0 +1,419 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2002, Pauline Middelink + * + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file res_indications.c + * + * \brief Load the indications + * + * \author Pauline Middelink <middelink@polyware.nl> + * + * Load the country specific dialtones into the asterisk PBX. + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <ctype.h> +#include <sys/stat.h> + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/cli.h" +#include "asterisk/config.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/indications.h" +#include "asterisk/utils.h" + +/* Globals */ +static const char config[] = "indications.conf"; + +char *playtones_desc= +" PlayTones(arg): Plays a tone list. Execution will continue with the next step immediately,\n" +"while the tones continue to play.\n" +"Arg is either the tone name defined in the indications.conf configuration file, or a directly\n" +"specified list of frequencies and durations.\n" +"See the sample indications.conf for a description of the specification of a tonelist.\n\n" +"Use the StopPlayTones application to stop the tones playing. \n"; + +/* + * Implementation of functions provided by this module + */ + +/*! + * \brief Add a country to indication + * \param e the ast_cli_entry for this CLI command + * \param cmd the reason we are being called + * \param a the arguments being passed to us + */ +static char *handle_cli_indication_add(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ind_tone_zone *tz; + int created_country = 0; + + switch (cmd) { + case CLI_INIT: + e->command = "indication add"; + e->usage = + "Usage: indication add <country> <indication> \"<tonelist>\"\n" + " Add the given indication to the country.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 5) + return CLI_SHOWUSAGE; + + tz = ast_get_indication_zone(a->argv[2]); + if (!tz) { + /* country does not exist, create it */ + ast_log(LOG_NOTICE, "Country '%s' does not exist, creating it.\n", a->argv[2]); + + if (!(tz = ast_calloc(1, sizeof(*tz)))) { + return CLI_FAILURE; + } + ast_copy_string(tz->country, a->argv[2], sizeof(tz->country)); + if (ast_register_indication_country(tz)) { + ast_log(LOG_WARNING, "Unable to register new country\n"); + ast_free(tz); + return CLI_FAILURE; + } + created_country = 1; + } + if (ast_register_indication(tz, a->argv[3], a->argv[4])) { + ast_log(LOG_WARNING, "Unable to register indication %s/%s\n", a->argv[2], a->argv[3]); + if (created_country) + ast_unregister_indication_country(a->argv[2]); + return CLI_FAILURE; + } + return CLI_SUCCESS; +} + +/*! + * \brief Remove a country from indication + * \param e the ast_cli_entry for this CLI command + * \param cmd the reason we are being called + * \param a the arguments being passed to us + */ +static char *handle_cli_indication_remove(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ind_tone_zone *tz; + + switch (cmd) { + case CLI_INIT: + e->command = "indication remove"; + e->usage = + "Usage: indication remove <country> <indication>\n" + " Remove the given indication from the country.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 3 && a->argc != 4) + return CLI_SHOWUSAGE; + + if (a->argc == 3) { + /* remove entiry country */ + if (ast_unregister_indication_country(a->argv[2])) { + ast_log(LOG_WARNING, "Unable to unregister indication country %s\n", a->argv[2]); + return CLI_FAILURE; + } + return CLI_SUCCESS; + } + + tz = ast_get_indication_zone(a->argv[2]); + if (!tz) { + ast_log(LOG_WARNING, "Unable to unregister indication %s/%s, country does not exists\n", a->argv[2], a->argv[3]); + return CLI_FAILURE; + } + if (ast_unregister_indication(tz, a->argv[3])) { + ast_log(LOG_WARNING, "Unable to unregister indication %s/%s\n", a->argv[2], a->argv[3]); + return CLI_FAILURE; + } + return CLI_SUCCESS; +} + +/*! + * \brief Show the current indications + * \param e the ast_cli_entry for this CLI command + * \param cmd the reason we are being called + * \param a the arguments being passed to us + */ +static char *handle_cli_indication_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ind_tone_zone *tz = NULL; + char buf[256]; + int found_country = 0; + + switch (cmd) { + case CLI_INIT: + e->command = "indication show"; + e->usage = + "Usage: indication show [<country> ...]\n" + " Display either a condensed for of all country/indications, or the\n" + " indications for the specified countries.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc == 2) { + /* no arguments, show a list of countries */ + ast_cli(a->fd, "Country Alias Description\n"); + ast_cli(a->fd, "===========================\n"); + while ((tz = ast_walk_indications(tz))) + ast_cli(a->fd, "%-7.7s %-7.7s %s\n", tz->country, tz->alias, tz->description); + return CLI_SUCCESS; + } + /* there was a request for specific country(ies), lets humor them */ + while ((tz = ast_walk_indications(tz))) { + int i, j; + for (i = 2; i < a->argc; i++) { + if (strcasecmp(tz->country, a->argv[i]) == 0 && !tz->alias[0]) { + struct ind_tone_zone_sound* ts; + if (!found_country) { + found_country = 1; + ast_cli(a->fd, "Country Indication PlayList\n"); + ast_cli(a->fd, "=====================================\n"); + } + j = snprintf(buf, sizeof(buf), "%-7.7s %-15.15s ", tz->country, "<ringcadence>"); + for (i = 0; i < tz->nrringcadence; i++) { + j += snprintf(buf + j, sizeof(buf) - j, "%d,", tz->ringcadence[i]); + } + if (tz->nrringcadence) + j--; + ast_copy_string(buf + j, "\n", sizeof(buf) - j); + ast_cli(a->fd, buf); + for (ts = tz->tones; ts; ts = ts->next) + ast_cli(a->fd, "%-7.7s %-15.15s %s\n", tz->country, ts->name, ts->data); + break; + } + } + } + if (!found_country) + ast_cli(a->fd, "No countries matched your criteria.\n"); + return CLI_SUCCESS; +} + +/*! + * \brief play tone for indication country + * \param chan ast_channel to play the sounds back to + * \param data contains tone to play + */ +static int handle_playtones(struct ast_channel *chan, void *data) +{ + struct ind_tone_zone_sound *ts; + int res; + + if (!data || !((char*)data)[0]) { + ast_log(LOG_NOTICE,"Nothing to play\n"); + return -1; + } + ts = ast_get_indication_tone(chan->zone, (const char*)data); + if (ts && ts->data[0]) + res = ast_playtones_start(chan, 0, ts->data, 0); + else + res = ast_playtones_start(chan, 0, (const char*)data, 0); + if (res) + ast_log(LOG_NOTICE,"Unable to start playtones\n"); + return res; +} + +/*! + * \brief Stop tones playing + * \param chan + * \param data + */ +static int handle_stopplaytones(struct ast_channel *chan, void *data) +{ + ast_playtones_stop(chan); + return 0; +} + +/*! \brief load indications module */ +static int ind_load_module(int reload) +{ + struct ast_config *cfg; + struct ast_variable *v; + char *cxt; + char *c; + struct ind_tone_zone *tones; + const char *country = NULL; + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + + /* that the following cast is needed, is yuk! */ + /* yup, checked it out. It is NOT written to. */ + cfg = ast_config_load((char *)config, config_flags); + if (!cfg) + return -1; + else if (cfg == CONFIG_STATUS_FILEUNCHANGED) + return 0; + + if (reload) + ast_unregister_indication_country(NULL); + + /* Use existing config to populate the Indication table */ + cxt = ast_category_browse(cfg, NULL); + while(cxt) { + /* All categories but "general" are considered countries */ + if (!strcasecmp(cxt, "general")) { + cxt = ast_category_browse(cfg, cxt); + continue; + } + if (!(tones = ast_calloc(1, sizeof(*tones)))) { + ast_config_destroy(cfg); + return -1; + } + ast_copy_string(tones->country,cxt,sizeof(tones->country)); + + v = ast_variable_browse(cfg, cxt); + while(v) { + if (!strcasecmp(v->name, "description")) { + ast_copy_string(tones->description, v->value, sizeof(tones->description)); + } else if ((!strcasecmp(v->name,"ringcadence"))||(!strcasecmp(v->name,"ringcadance"))) { + char *ring,*rings = ast_strdupa(v->value); + c = rings; + ring = strsep(&c,","); + while (ring) { + int *tmp, val; + if (!isdigit(ring[0]) || (val=atoi(ring))==-1) { + ast_log(LOG_WARNING,"Invalid ringcadence given '%s' at line %d.\n",ring,v->lineno); + ring = strsep(&c,","); + continue; + } + if (!(tmp = ast_realloc(tones->ringcadence, (tones->nrringcadence + 1) * sizeof(int)))) { + ast_config_destroy(cfg); + return -1; + } + tones->ringcadence = tmp; + tmp[tones->nrringcadence] = val; + tones->nrringcadence++; + /* next item */ + ring = strsep(&c,","); + } + } else if (!strcasecmp(v->name,"alias")) { + char *countries = ast_strdupa(v->value); + c = countries; + country = strsep(&c,","); + while (country) { + struct ind_tone_zone* azone; + if (!(azone = ast_calloc(1, sizeof(*azone)))) { + ast_config_destroy(cfg); + return -1; + } + ast_copy_string(azone->country, country, sizeof(azone->country)); + ast_copy_string(azone->alias, cxt, sizeof(azone->alias)); + if (ast_register_indication_country(azone)) { + ast_log(LOG_WARNING, "Unable to register indication alias at line %d.\n",v->lineno); + ast_free(tones); + } + /* next item */ + country = strsep(&c,","); + } + } else { + /* add tone to country */ + struct ind_tone_zone_sound *ps,*ts; + for (ps=NULL,ts=tones->tones; ts; ps=ts, ts=ts->next) { + if (strcasecmp(v->name,ts->name)==0) { + /* already there */ + ast_log(LOG_NOTICE,"Duplicate entry '%s', skipped.\n",v->name); + goto out; + } + } + /* not there, add it to the back */ + if (!(ts = ast_malloc(sizeof(*ts)))) { + ast_config_destroy(cfg); + return -1; + } + ts->next = NULL; + ts->name = ast_strdup(v->name); + ts->data = ast_strdup(v->value); + if (ps) + ps->next = ts; + else + tones->tones = ts; + } +out: v = v->next; + } + if (tones->description[0] || tones->alias[0] || tones->tones) { + if (ast_register_indication_country(tones)) { + ast_log(LOG_WARNING, "Unable to register indication at line %d.\n",v->lineno); + ast_free(tones); + } + } else ast_free(tones); + + cxt = ast_category_browse(cfg, cxt); + } + + /* determine which country is the default */ + country = ast_variable_retrieve(cfg,"general","country"); + if (!country || !*country || ast_set_indication_country(country)) + ast_log(LOG_WARNING,"Unable to set the default country (for indication tones)\n"); + + ast_config_destroy(cfg); + return 0; +} + +/*! \brief CLI entries for commands provided by this module */ +static struct ast_cli_entry cli_indications[] = { + AST_CLI_DEFINE(handle_cli_indication_add, "Add the given indication to the country"), + AST_CLI_DEFINE(handle_cli_indication_remove, "Remove the given indication from the country"), + AST_CLI_DEFINE(handle_cli_indication_show, "Display a list of all countries/indications") +}; + +/*! \brief Unload indicators module */ +static int unload_module(void) +{ + /* remove the registed indications... */ + ast_unregister_indication_country(NULL); + + /* and the functions */ + ast_cli_unregister_multiple(cli_indications, sizeof(cli_indications) / sizeof(struct ast_cli_entry)); + ast_unregister_application("PlayTones"); + ast_unregister_application("StopPlayTones"); + return 0; +} + + +/*! \brief Load indications module */ +static int load_module(void) +{ + if (ind_load_module(0)) + return AST_MODULE_LOAD_DECLINE; + ast_cli_register_multiple(cli_indications, sizeof(cli_indications) / sizeof(struct ast_cli_entry)); + ast_register_application("PlayTones", handle_playtones, "Play a tone list", playtones_desc); + ast_register_application("StopPlayTones", handle_stopplaytones, "Stop playing a tone list"," StopPlayTones(): Stop playing a tone list"); + + return AST_MODULE_LOAD_SUCCESS; +} + +/*! \brief Reload indications module */ +static int reload(void) +{ + return ind_load_module(1); +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Region-specific tones", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); diff --git a/trunk/res/res_jabber.c b/trunk/res/res_jabber.c new file mode 100644 index 000000000..bd12d2977 --- /dev/null +++ b/trunk/res/res_jabber.c @@ -0,0 +1,3015 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Matt O'Gorman <mogorman@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * \brief A resource for interfacing asterisk directly as a client + * or a component to a jabber compliant server. + * + * \extref Iksemel http://code.google.com/p/iksemel/ + * + * \todo If you unload this module, chan_gtalk/jingle will be dead. How do we handle that? + * + */ + +/*** MODULEINFO + <depend>iksemel</depend> + <use>openssl</use> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <iksemel.h> + +#include "asterisk/channel.h" +#include "asterisk/jabber.h" +#include "asterisk/file.h" +#include "asterisk/config.h" +#include "asterisk/callerid.h" +#include "asterisk/lock.h" +#include "asterisk/cli.h" +#include "asterisk/app.h" +#include "asterisk/pbx.h" +#include "asterisk/md5.h" +#include "asterisk/acl.h" +#include "asterisk/utils.h" +#include "asterisk/module.h" +#include "asterisk/astobj.h" +#include "asterisk/astdb.h" +#include "asterisk/manager.h" + +#define JABBER_CONFIG "jabber.conf" + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +/*-- Forward declarations */ +static void aji_buddy_destroy(struct aji_buddy *obj); +static void aji_client_destroy(struct aji_client *obj); +static int aji_send_exec(struct ast_channel *chan, void *data); +static int aji_status_exec(struct ast_channel *chan, void *data); +static int aji_is_secure(struct aji_client *client); +static int aji_start_tls(struct aji_client *client); +static int aji_tls_handshake(struct aji_client *client); +static int aji_io_recv(struct aji_client *client, char *buffer, size_t buf_len, int timeout); +static int aji_recv(struct aji_client *client, int timeout); +static int aji_send_header(struct aji_client *client, const char *to); +static int aji_send_raw(struct aji_client *client, const char *xmlstr); +static void aji_log_hook(void *data, const char *xmpp, size_t size, int is_incoming); +static int aji_start_sasl(struct aji_client *client, enum ikssasltype type, char *username, char *pass); +static int aji_act_hook(void *data, int type, iks *node); +static void aji_handle_iq(struct aji_client *client, iks *node); +static void aji_handle_message(struct aji_client *client, ikspak *pak); +static void aji_handle_presence(struct aji_client *client, ikspak *pak); +static void aji_handle_subscribe(struct aji_client *client, ikspak *pak); +static void *aji_recv_loop(void *data); +static int aji_initialize(struct aji_client *client); +static int aji_client_connect(void *data, ikspak *pak); +static void aji_set_presence(struct aji_client *client, char *to, char *from, int level, char *desc); +static char *aji_do_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); +static char *aji_do_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); +static char *aji_no_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); +static char *aji_show_clients(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); +static char *aji_show_buddies(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); +static char *aji_test(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); +static int aji_create_client(char *label, struct ast_variable *var, int debug); +static int aji_create_buddy(char *label, struct aji_client *client); +static int aji_reload(int reload); +static int aji_load_config(int reload); +static void aji_pruneregister(struct aji_client *client); +static int aji_filter_roster(void *data, ikspak *pak); +static int aji_get_roster(struct aji_client *client); +static int aji_client_info_handler(void *data, ikspak *pak); +static int aji_dinfo_handler(void *data, ikspak *pak); +static int aji_ditems_handler(void *data, ikspak *pak); +static int aji_register_query_handler(void *data, ikspak *pak); +static int aji_register_approve_handler(void *data, ikspak *pak); +static int aji_reconnect(struct aji_client *client); +static iks *jabber_make_auth(iksid * id, const char *pass, const char *sid); +/* No transports in this version */ +/* +static int aji_create_transport(char *label, struct aji_client *client); +static int aji_register_transport(void *data, ikspak *pak); +static int aji_register_transport2(void *data, ikspak *pak); +*/ + +static struct ast_cli_entry aji_cli[] = { + AST_CLI_DEFINE(aji_do_debug, "Enable jabber debugging"), + AST_CLI_DEFINE(aji_no_debug, "Disable Jabber debug"), + AST_CLI_DEFINE(aji_do_reload, "Reload Jabber configuration"), + AST_CLI_DEFINE(aji_show_clients, "Show state of clients and components"), + AST_CLI_DEFINE(aji_show_buddies, "Show buddy lists of our clients"), + AST_CLI_DEFINE(aji_test, "Shows roster, but is generally used for mog's debugging."), +}; + +static char *app_ajisend = "JabberSend"; + +static char *ajisend_synopsis = "JabberSend(jabber,screenname,message)"; + +static char *ajisend_descrip = +"JabberSend(Jabber,ScreenName,Message)\n" +" Jabber - Client or transport Asterisk uses to connect to Jabber\n" +" ScreenName - User Name to message.\n" +" Message - Message to be sent to the buddy\n"; + +static char *app_ajistatus = "JabberStatus"; + +static char *ajistatus_synopsis = "JabberStatus(Jabber,ScreenName,Variable)"; + +static char *ajistatus_descrip = +"JabberStatus(Jabber,ScreenName,Variable)\n" +" Jabber - Client or transport Asterisk uses to connect to Jabber\n" +" ScreenName - User Name to retrieve status from.\n" +" Variable - Variable to store presence in will be 1-6.\n" +" In order, Online, Chatty, Away, XAway, DND, Offline\n" +" If not in roster variable will = 7\n"; + +struct aji_client_container clients; +struct aji_capabilities *capabilities = NULL; + +/*! \brief Global flags, initialized to default values */ +static struct ast_flags globalflags = { AJI_AUTOPRUNE | AJI_AUTOREGISTER }; + +/*! + * \brief Deletes the aji_client data structure. + * \param obj aji_client The structure we will delete. + * \return void. + */ +static void aji_client_destroy(struct aji_client *obj) +{ + struct aji_message *tmp; + ASTOBJ_CONTAINER_DESTROYALL(&obj->buddies, aji_buddy_destroy); + ASTOBJ_CONTAINER_DESTROY(&obj->buddies); + iks_filter_delete(obj->f); + iks_parser_delete(obj->p); + iks_stack_delete(obj->stack); + AST_LIST_LOCK(&obj->messages); + while ((tmp = AST_LIST_REMOVE_HEAD(&obj->messages, list))) { + if (tmp->from) + ast_free(tmp->from); + if (tmp->message) + ast_free(tmp->message); + } + AST_LIST_HEAD_DESTROY(&obj->messages); + ast_free(obj); +} + +/*! + * \brief Deletes the aji_buddy data structure. + * \param obj aji_buddy The structure we will delete. + * \return void. + */ +static void aji_buddy_destroy(struct aji_buddy *obj) +{ + struct aji_resource *tmp; + + while ((tmp = obj->resources)) { + obj->resources = obj->resources->next; + ast_free(tmp->description); + ast_free(tmp); + } + + ast_free(obj); +} + +/*! + * \brief Find version in XML stream and populate our capabilities list + * \param node the node attribute in the caps element we'll look for or add to + * our list + * \param version the version attribute in the caps element we'll look for or + * add to our list + * \param pak struct The XML stanza we're processing + * \return a pointer to the added or found aji_version structure + */ +static struct aji_version *aji_find_version(char *node, char *version, ikspak *pak) +{ + struct aji_capabilities *list = NULL; + struct aji_version *res = NULL; + + list = capabilities; + + if(!node) + node = pak->from->full; + if(!version) + version = "none supplied."; + while(list) { + if(!strcasecmp(list->node, node)) { + res = list->versions; + while(res) { + if(!strcasecmp(res->version, version)) + return res; + res = res->next; + } + /* Specified version not found. Let's add it to + this node in our capabilities list */ + if(!res) { + res = ast_malloc(sizeof(*res)); + if(!res) { + ast_log(LOG_ERROR, "Out of memory!\n"); + return NULL; + } + res->jingle = 0; + res->parent = list; + ast_copy_string(res->version, version, sizeof(res->version)); + res->next = list->versions; + list->versions = res; + return res; + } + } + list = list->next; + } + /* Specified node not found. Let's add it our capabilities list */ + if(!list) { + list = ast_malloc(sizeof(*list)); + if(!list) { + ast_log(LOG_ERROR, "Out of memory!\n"); + return NULL; + } + res = ast_malloc(sizeof(*res)); + if(!res) { + ast_log(LOG_ERROR, "Out of memory!\n"); + ast_free(list); + return NULL; + } + ast_copy_string(list->node, node, sizeof(list->node)); + ast_copy_string(res->version, version, sizeof(res->version)); + res->jingle = 0; + res->parent = list; + res->next = NULL; + list->versions = res; + list->next = capabilities; + capabilities = list; + } + return res; +} +/*! + * \brief Find the aji_resource we want + * \param buddy aji_buddy A buddy + * \param name + * \return aji_resource object +*/ +static struct aji_resource *aji_find_resource(struct aji_buddy *buddy, char *name) +{ + struct aji_resource *res = NULL; + if (!buddy || !name) + return res; + res = buddy->resources; + while (res) { + if (!strcasecmp(res->resource, name)) { + break; + } + res = res->next; + } + return res; +} + +/*! + * \brief Jabber GTalk function + * \param node iks + * \return 1 on success, 0 on failure. +*/ +static int gtalk_yuck(iks *node) +{ + if (iks_find_with_attrib(node, "c", "node", "http://www.google.com/xmpp/client/caps")) + return 1; + return 0; +} + +/*! + * \brief Setup the authentication struct + * \param id iksid + * \param pass password + * \param sid + * \return x iks +*/ +static iks *jabber_make_auth(iksid * id, const char *pass, const char *sid) +{ + iks *x, *y; + x = iks_new("iq"); + iks_insert_attrib(x, "type", "set"); + y = iks_insert(x, "query"); + iks_insert_attrib(y, "xmlns", IKS_NS_AUTH); + iks_insert_cdata(iks_insert(y, "username"), id->user, 0); + iks_insert_cdata(iks_insert(y, "resource"), id->resource, 0); + if (sid) { + char buf[41]; + char sidpass[100]; + snprintf(sidpass, sizeof(sidpass), "%s%s", sid, pass); + ast_sha1_hash(buf, sidpass); + iks_insert_cdata(iks_insert(y, "digest"), buf, 0); + } else { + iks_insert_cdata(iks_insert(y, "password"), pass, 0); + } + return x; +} + +/*! + * \brief Dial plan function status(). puts the status of watched user + into a channel variable. + * \param chan ast_channel + * \param data + * \return 0 on success, -1 on error + */ +static int aji_status_exec(struct ast_channel *chan, void *data) +{ + struct aji_client *client = NULL; + struct aji_buddy *buddy = NULL; + struct aji_resource *r = NULL; + char *s = NULL; + int stat = 7; + char status[2]; + static int deprecation_warning = 0; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(sender); + AST_APP_ARG(jid); + AST_APP_ARG(variable); + ); + AST_DECLARE_APP_ARGS(jid, + AST_APP_ARG(screenname); + AST_APP_ARG(resource); + ); + + if (deprecation_warning++ % 10 == 0) + ast_log(LOG_WARNING, "JabberStatus is deprecated. Please use the JABBER_STATUS dialplan function in the future.\n"); + + if (!data) { + ast_log(LOG_ERROR, "Usage: JabberStatus(<sender>,<screenname>[/<resource>],<varname>\n"); + return 0; + } + s = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, s); + + if (args.argc != 3) { + ast_log(LOG_ERROR, "JabberStatus() requires 3 arguments.\n"); + return -1; + } + + AST_NONSTANDARD_APP_ARGS(jid, args.jid, '/'); + + if (!(client = ast_aji_get_client(args.sender))) { + ast_log(LOG_WARNING, "Could not find sender connection: '%s'\n", args.sender); + return -1; + } + buddy = ASTOBJ_CONTAINER_FIND(&client->buddies, jid.screenname); + if (!buddy) { + ast_log(LOG_WARNING, "Could not find buddy in list: '%s'\n", jid.screenname); + return -1; + } + r = aji_find_resource(buddy, jid.resource); + if (!r && buddy->resources) + r = buddy->resources; + if (!r) + ast_log(LOG_NOTICE, "Resource '%s' of buddy '%s' was not found\n", jid.resource, jid.screenname); + else + stat = r->status; + snprintf(status, sizeof(status), "%d", stat); + pbx_builtin_setvar_helper(chan, args.variable, status); + return 0; +} + +static int acf_jabberstatus_read(struct ast_channel *chan, const char *name, char *data, char *buf, size_t buflen) +{ + struct aji_client *client = NULL; + struct aji_buddy *buddy = NULL; + struct aji_resource *r = NULL; + int stat = 7; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(sender); + AST_APP_ARG(jid); + ); + AST_DECLARE_APP_ARGS(jid, + AST_APP_ARG(screenname); + AST_APP_ARG(resource); + ); + + if (!data) { + ast_log(LOG_ERROR, "Usage: JABBER_STATUS(<sender>,<screenname>[/<resource>])\n"); + return 0; + } + AST_STANDARD_APP_ARGS(args, data); + + if (args.argc != 2) { + ast_log(LOG_ERROR, "JABBER_STATUS requires 2 arguments.\n"); + return -1; + } + + AST_NONSTANDARD_APP_ARGS(jid, args.jid, '/'); + + if (!(client = ast_aji_get_client(args.sender))) { + ast_log(LOG_WARNING, "Could not find sender connection: '%s'\n", args.sender); + return -1; + } + buddy = ASTOBJ_CONTAINER_FIND(&client->buddies, jid.screenname); + if (!buddy) { + ast_log(LOG_WARNING, "Could not find buddy in list: '%s'\n", jid.screenname); + return -1; + } + r = aji_find_resource(buddy, jid.resource); + if (!r && buddy->resources) + r = buddy->resources; + if (!r) + ast_log(LOG_NOTICE, "Resource %s of buddy %s was not found.\n", jid.resource, jid.screenname); + else + stat = r->status; + snprintf(buf, buflen, "%d", stat); + return 0; +} + +static struct ast_custom_function jabberstatus_function = { + .name = "JABBER_STATUS", + .synopsis = "Retrieve buddy status", + .syntax = "JABBER_STATUS(<sender>,<buddy>[/<resource>])", + .read = acf_jabberstatus_read, + .desc = +"Retrieves the numeric status associated with the specified buddy. If the\n" +"buddy does not exist in the buddylist, returns 7.\n", +}; + +/*! + * \brief Dial plan function to send a message. + * \param chan ast_channel + * \param data Data is sender|reciever|message. + * \return 0 on success,-1 on error. + */ +static int aji_send_exec(struct ast_channel *chan, void *data) +{ + struct aji_client *client = NULL; + char *s; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(sender); + AST_APP_ARG(recipient); + AST_APP_ARG(message); + ); + + if (!data) { + ast_log(LOG_ERROR, "Usage: JabberSend(<sender>,<recipient>,<message>)\n"); + return 0; + } + s = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, s); + if (args.argc < 3) { + ast_log(LOG_ERROR, "JabberSend requires 3 arguments: '%s'\n", (char *) data); + return -1; + } + + if (!(client = ast_aji_get_client(args.sender))) { + ast_log(LOG_WARNING, "Could not find sender connection: '%s'\n", args.sender); + return -1; + } + if (strchr(args.recipient, '@') && !ast_strlen_zero(args.message)) + ast_aji_send_chat(client, args.recipient, args.message); + return 0; +} + +/*! + * \brief Tests whether the connection is secured or not + * \return 0 if the connection is not secured + */ +static int aji_is_secure(struct aji_client *client) +{ +#ifdef HAVE_OPENSSL + return client->stream_flags & SECURE; +#else + return 0; +#endif +} + + +/*! + * \brief Starts the TLS procedure + * \param client the configured XMPP client we use to connect to a XMPP server + * \return IKS_OK on success, an error code if sending failed, IKS_NET_TLSFAIL + * if OpenSSL is not installed + */ +static int aji_start_tls(struct aji_client *client) +{ + int ret; +#ifndef HAVE_OPENSSL + return IKS_NET_TLSFAIL; +#endif + /* This is sent not encrypted */ + ret = iks_send_raw(client->p, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"); + if (ret) + return ret; + client->stream_flags |= TRY_SECURE; + + return IKS_OK; +} + +/*! + * \brief TLS handshake, OpenSSL initialization + * \param client the configured XMPP client we use to connect to a XMPP server + * \return IKS_OK on success, IKS_NET_TLSFAIL on failure + */ +static int aji_tls_handshake(struct aji_client *client) +{ + int ret; + int sock; + +#ifndef HAVE_OPENSSL + return IKS_NET_TLSFAIL; +#endif + + ast_debug(1, "Starting TLS handshake\n"); + + /* Load encryption, hashing algorithms and error strings */ + SSL_library_init(); + SSL_load_error_strings(); + + /* Choose an SSL/TLS protocol version, create SSL_CTX */ + client->ssl_method = SSLv3_method(); + client->ssl_context = SSL_CTX_new(client->ssl_method); + if (!client->ssl_context) + return IKS_NET_TLSFAIL; + + /* Create new SSL session */ + client->ssl_session = SSL_new(client->ssl_context); + if (!client->ssl_session) + return IKS_NET_TLSFAIL; + + /* Enforce TLS on our XMPP connection */ + sock = iks_fd(client->p); + ret = SSL_set_fd(client->ssl_session, sock); + if (!ret) + return IKS_NET_TLSFAIL; + + /* Perform SSL handshake */ + ret = SSL_connect(client->ssl_session); + if (!ret) + return IKS_NET_TLSFAIL; + + client->stream_flags &= (~TRY_SECURE); + client->stream_flags |= SECURE; + + /* Sent over the established TLS connection */ + ret = aji_send_header(client, client->jid->server); + if (ret != IKS_OK) + return IKS_NET_TLSFAIL; + + ast_debug(1, "TLS started with server\n"); + + return IKS_OK; +} + +/*! + * \brief Secured or unsecured IO socket receiving function + * \param client the configured XMPP client we use to connect to a XMPP server + * \param buffer the reception buffer + * \param buf_len the size of the buffer + * \param timeout the select timer + * \return the number of read bytes on success, 0 on timeout expiration, + * -1 on error + */ +static int aji_io_recv(struct aji_client *client, char *buffer, size_t buf_len, int timeout) +{ + int sock; + fd_set fds; + struct timeval tv, *tvptr = NULL; + int len, res; + +#ifdef HAVE_OPENSSL + if (aji_is_secure(client)) { + sock = SSL_get_fd(client->ssl_session); + if (sock < 0) + return -1; + } else +#endif /* HAVE_OPENSSL */ + sock = iks_fd(client->p); + + memset(&tv, 0, sizeof(struct timeval)); + FD_ZERO(&fds); + FD_SET(sock, &fds); + tv.tv_sec = timeout; + + /* NULL value for tvptr makes ast_select wait indefinitely */ + tvptr = (timeout != -1) ? &tv : NULL; + + /* ast_select emulates linux behaviour in terms of timeout handling */ + res = ast_select(sock + 1, &fds, NULL, NULL, tvptr); + if (res > 0) { +#ifdef HAVE_OPENSSL + if (aji_is_secure(client)) { + len = SSL_read(client->ssl_session, buffer, buf_len); + } else +#endif /* HAVE_OPENSSL */ + len = recv(sock, buffer, buf_len, 0); + + if (len > 0) { + return len; + } else if (len <= 0) { + return -1; + } + } + return res; +} + +/*! + * \brief Tries to receive data from the Jabber server + * \param client the configured XMPP client we use to connect to a XMPP server + * \param timeout the timeout value + * This function receives (encrypted or unencrypted) data from the XMPP server, + * and passes it to the parser. + * \return IKS_OK on success, IKS_NET_RWERR on IO error, IKS_NET_NOCONN, if no + * connection available, IKS_NET_EXPIRED on timeout expiration + */ +static int aji_recv (struct aji_client *client, int timeout) +{ + int len, ret; + char buf[NET_IO_BUF_SIZE -1]; + + memset(buf, 0, sizeof(buf)); + + while (1) { + len = aji_io_recv(client, buf, NET_IO_BUF_SIZE - 1, timeout); + if (len < 0) return IKS_NET_RWERR; + if (len == 0) return IKS_NET_EXPIRED; + buf[len] = '\0'; + + /* Log the message here, because iksemel's logHook is + unaccessible */ + aji_log_hook(client, buf, len, 1); + + ret = iks_parse(client->p, buf, len, 0); + if (ret != IKS_OK) { + return ret; + } + } + return IKS_OK; +} + +/*! + * \brief Sends XMPP header to the server + * \param client the configured XMPP client we use to connect to a XMPP server + * \param to the target XMPP server + * \return IKS_OK on success, any other value on failure + */ +static int aji_send_header(struct aji_client *client, const char *to) +{ + char *msg; + int len, err; + + len = 91 + strlen(client->name_space) + 6 + strlen(to) + 16 + 1; + msg = iks_malloc(len); + if (!msg) + return IKS_NOMEM; + sprintf(msg, "<?xml version='1.0'?>" + "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='" + "%s' to='%s' version='1.0'>", client->name_space, to); + err = aji_send_raw(client, msg); + iks_free(msg); + if (err != IKS_OK) + return err; + + return IKS_OK; +} + +/*! + * \brief Wraps raw sending + * \param client the configured XMPP client we use to connect to a XMPP server + * \param x the XMPP packet to send + * \return IKS_OK on success, any other value on failure + */ +int ast_aji_send(struct aji_client *client, iks *x) +{ + return aji_send_raw(client, iks_string(iks_stack(x), x)); +} + +/*! + * \brief Sends an XML string over an XMPP connection + * \param client the configured XMPP client we use to connect to a XMPP server + * \param xmlstr the XML string to send + * The XML data is sent whether the connection is secured or not. In the + * latter case, we just call iks_send_raw(). + * \return IKS_OK on success, any other value on failure + */ +static int aji_send_raw(struct aji_client *client, const char *xmlstr) +{ + int ret; +#ifdef HAVE_OPENSSL + int len = strlen(xmlstr); + + if (aji_is_secure(client)) { + ret = SSL_write(client->ssl_session, xmlstr, len); + if (ret) { + /* Log the message here, because iksemel's logHook is + unaccessible */ + aji_log_hook(client, xmlstr, len, 0); + return IKS_OK; + } + } +#endif + /* If needed, data will be sent unencrypted, and logHook will + be called inside iks_send_raw */ + ret = iks_send_raw(client->p, xmlstr); + if (ret != IKS_OK) + return ret; + + return IKS_OK; +} + +/*! + * \brief the debug loop. + * \param data void + * \param xmpp xml data as string + * \param size size of string + * \param is_incoming direction of packet 1 for inbound 0 for outbound. + */ +static void aji_log_hook(void *data, const char *xmpp, size_t size, int is_incoming) +{ + struct aji_client *client = ASTOBJ_REF((struct aji_client *) data); + + if (!ast_strlen_zero(xmpp)) + manager_event(EVENT_FLAG_USER, "JabberEvent", "Account: %s\r\nPacket: %s\r\n", client->name, xmpp); + + if (client->debug) { + if (is_incoming) + ast_verbose("\nJABBER: %s INCOMING: %s\n", client->name, xmpp); + else { + if( strlen(xmpp) == 1) { + if(option_debug > 2 && xmpp[0] == ' ') + ast_verbose("\nJABBER: Keep alive packet\n"); + } else + ast_verbose("\nJABBER: %s OUTGOING: %s\n", client->name, xmpp); + } + + } + ASTOBJ_UNREF(client, aji_client_destroy); +} + +/*! + * \brief A wrapper function for iks_start_sasl + * \param client the configured XMPP client we use to connect to a XMPP server + * \param type the SASL authentication type. Supported types are PLAIN and MD5 + * \param username + * \param pass password. + * + * \return IKS_OK on success, IKSNET_NOTSUPP on failure. + */ +static int aji_start_sasl(struct aji_client *client, enum ikssasltype type, char *username, char *pass) +{ + iks *x = NULL; + int len; + char *s; + char *base64; + + /* trigger SASL DIGEST-MD5 only over an unsecured connection. + iks_start_sasl is an iksemel API function and relies on GnuTLS, + whereas we use OpenSSL */ + if ((type & IKS_STREAM_SASL_MD5) && !aji_is_secure(client)) + return iks_start_sasl(client->p, IKS_SASL_DIGEST_MD5, username, pass); + if (!(type & IKS_STREAM_SASL_PLAIN)) { + ast_log(LOG_ERROR, "Server does not support SASL PLAIN authentication\n"); + return IKS_NET_NOTSUPP; + } + + x = iks_new("auth"); + if (!x) { + ast_log(LOG_ERROR, "Out of memory.\n"); + return IKS_NET_NOTSUPP; + } + + iks_insert_attrib(x, "xmlns", IKS_NS_XMPP_SASL); + len = strlen(username) + strlen(pass) + 3; + s = alloca(len); + base64 = alloca((len + 2) * 4 / 3); + iks_insert_attrib(x, "mechanism", "PLAIN"); + snprintf(s, len, "%c%s%c%s", 0, username, 0, pass); + + /* exclude the NULL training byte from the base64 encoding operation + as some XMPP servers will refuse it. + The format for authentication is [authzid]\0authcid\0password + not [authzid]\0authcid\0password\0 */ + ast_base64encode(base64, (const unsigned char *) s, len - 1, (len + 2) * 4 / 3); + iks_insert_cdata(x, base64, 0); + ast_aji_send(client, x); + iks_delete(x); + + return IKS_OK; +} + +/*! + * \brief The action hook parses the inbound packets, constantly running. + * \param data aji client structure + * \param type type of packet + * \param node the actual packet. + * \return IKS_OK or IKS_HOOK . + */ +static int aji_act_hook(void *data, int type, iks *node) +{ + struct aji_client *client = ASTOBJ_REF((struct aji_client *) data); + ikspak *pak = NULL; + iks *auth = NULL; + int features = 0; + + if(!node) { + ast_log(LOG_ERROR, "aji_act_hook was called with out a packet\n"); /* most likely cause type is IKS_NODE_ERROR lost connection */ + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_HOOK; + } + + if (client->state == AJI_DISCONNECTING) { + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_HOOK; + } + + pak = iks_packet(node); + + if (!client->component) { /*client */ + switch (type) { + case IKS_NODE_START: + if (client->usetls && !aji_is_secure(client)) { + if (aji_start_tls(client) == IKS_NET_TLSFAIL) { + ast_log(LOG_ERROR, "OpenSSL not installed. You need to install OpenSSL on this system\n"); + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_HOOK; + } + + break; + } + if (!client->usesasl) { + iks_filter_add_rule(client->f, aji_client_connect, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_ID, client->mid, IKS_RULE_DONE); + auth = jabber_make_auth(client->jid, client->password, iks_find_attrib(node, "id")); + if (auth) { + iks_insert_attrib(auth, "id", client->mid); + iks_insert_attrib(auth, "to", client->jid->server); + ast_aji_increment_mid(client->mid); + ast_aji_send(client, auth); + iks_delete(auth); + } else + ast_log(LOG_ERROR, "Out of memory.\n"); + } + break; + + case IKS_NODE_NORMAL: + if (client->stream_flags & TRY_SECURE) { + if (!strcmp("proceed", iks_name(node))) { + return aji_tls_handshake(client); + } + } + + if (!strcmp("stream:features", iks_name(node))) { + features = iks_stream_features(node); + if (client->usesasl) { + if (client->usetls && !aji_is_secure(client)) + break; + if (client->authorized) { + if (features & IKS_STREAM_BIND) { + iks_filter_add_rule(client->f, aji_client_connect, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_DONE); + auth = iks_make_resource_bind(client->jid); + if (auth) { + iks_insert_attrib(auth, "id", client->mid); + ast_aji_increment_mid(client->mid); + ast_aji_send(client, auth); + iks_delete(auth); + } else { + ast_log(LOG_ERROR, "Out of memory.\n"); + break; + } + } + if (features & IKS_STREAM_SESSION) { + iks_filter_add_rule (client->f, aji_client_connect, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_ID, "auth", IKS_RULE_DONE); + auth = iks_make_session(); + if (auth) { + iks_insert_attrib(auth, "id", "auth"); + ast_aji_increment_mid(client->mid); + ast_aji_send(client, auth); + iks_delete(auth); + } else { + ast_log(LOG_ERROR, "Out of memory.\n"); + } + } + } else { + int ret; + if (!client->jid->user) { + ast_log(LOG_ERROR, "Malformed Jabber ID : %s (domain missing?)\n", client->jid->full); + break; + } + + ret = aji_start_sasl(client, features, client->jid->user, client->password); + if (ret != IKS_OK) { + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_HOOK; + } + break; + } + } + } else if (!strcmp("failure", iks_name(node))) { + ast_log(LOG_ERROR, "JABBER: encryption failure. possible bad password.\n"); + } else if (!strcmp("success", iks_name(node))) { + client->authorized = 1; + aji_send_header(client, client->jid->server); + } + break; + case IKS_NODE_ERROR: + ast_log(LOG_ERROR, "JABBER: Node Error\n"); + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_HOOK; + break; + case IKS_NODE_STOP: + ast_log(LOG_WARNING, "JABBER: Disconnected\n"); + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_HOOK; + break; + } + } else if (client->state != AJI_CONNECTED && client->component) { + switch (type) { + case IKS_NODE_START: + if (client->state == AJI_DISCONNECTED) { + char secret[160], shasum[320], *handshake; + + sprintf(secret, "%s%s", pak->id, client->password); + ast_sha1_hash(shasum, secret); + handshake = NULL; + asprintf(&handshake, "<handshake>%s</handshake>", shasum); + if (handshake) { + aji_send_raw(client, handshake); + ast_free(handshake); + handshake = NULL; + } + client->state = AJI_CONNECTING; + if(aji_recv(client, 1) == 2) /*XXX proper result for iksemel library on iks_recv of <handshake/> XXX*/ + client->state = AJI_CONNECTED; + else + ast_log(LOG_WARNING, "Jabber didn't seem to handshake, failed to authenticate.\n"); + break; + } + break; + + case IKS_NODE_NORMAL: + break; + + case IKS_NODE_ERROR: + ast_log(LOG_ERROR, "JABBER: Node Error\n"); + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_HOOK; + + case IKS_NODE_STOP: + ast_log(LOG_WARNING, "JABBER: Disconnected\n"); + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_HOOK; + } + } + + switch (pak->type) { + case IKS_PAK_NONE: + ast_debug(1, "JABBER: I don't know what to do with paktype NONE.\n"); + break; + case IKS_PAK_MESSAGE: + aji_handle_message(client, pak); + ast_debug(1, "JABBER: Handling paktype MESSAGE.\n"); + break; + case IKS_PAK_PRESENCE: + aji_handle_presence(client, pak); + ast_debug(1, "JABBER: Handling paktype PRESENCE\n"); + break; + case IKS_PAK_S10N: + aji_handle_subscribe(client, pak); + ast_debug(1, "JABBER: Handling paktype S10N\n"); + break; + case IKS_PAK_IQ: + ast_debug(1, "JABBER: Handling paktype IQ\n"); + aji_handle_iq(client, node); + break; + default: + ast_debug(1, "JABBER: I don't know anything about paktype '%d'\n", pak->type); + break; + } + + iks_filter_packet(client->f, pak); + + if (node) + iks_delete(node); + + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_OK; +} +/*! + * \brief Uknown + * \param data void + * \param pak ikspak + * \return IKS_FILTER_EAT. +*/ +static int aji_register_approve_handler(void *data, ikspak *pak) +{ + struct aji_client *client = ASTOBJ_REF((struct aji_client *) data); + iks *iq = NULL, *presence = NULL, *x = NULL; + + iq = iks_new("iq"); + presence = iks_new("presence"); + x = iks_new("x"); + if (client && iq && presence && x) { + if (!iks_find(pak->query, "remove")) { + iks_insert_attrib(iq, "from", client->jid->full); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(iq, "type", "result"); + ast_aji_send(client, iq); + + iks_insert_attrib(presence, "from", client->jid->full); + iks_insert_attrib(presence, "to", pak->from->partial); + iks_insert_attrib(presence, "id", client->mid); + ast_aji_increment_mid(client->mid); + iks_insert_attrib(presence, "type", "subscribe"); + iks_insert_attrib(x, "xmlns", "vcard-temp:x:update"); + iks_insert_node(presence, x); + ast_aji_send(client, presence); + } + } else { + ast_log(LOG_ERROR, "Out of memory.\n"); + } + + if (iq) + iks_delete(iq); + if(presence) + iks_delete(presence); + if (x) + iks_delete(x); + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_FILTER_EAT; +} +/*! + * \brief register query + * \param data incoming aji_client request + * \param pak ikspak + * \return IKS_FILTER_EAT. +*/ +static int aji_register_query_handler(void *data, ikspak *pak) +{ + struct aji_client *client = ASTOBJ_REF((struct aji_client *) data); + struct aji_buddy *buddy = NULL; + char *node = NULL; + + client = (struct aji_client *) data; + + buddy = ASTOBJ_CONTAINER_FIND(&client->buddies, pak->from->partial); + if (!buddy) { + iks *iq = NULL, *query = NULL, *error = NULL, *notacceptable = NULL; + + ast_verbose("Someone.... %s tried to register but they aren't allowed\n", pak->from->partial); + iq = iks_new("iq"); + query = iks_new("query"); + error = iks_new("error"); + notacceptable = iks_new("not-acceptable"); + if(iq && query && error && notacceptable) { + iks_insert_attrib(iq, "type", "error"); + iks_insert_attrib(iq, "from", client->user); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(query, "xmlns", "jabber:iq:register"); + iks_insert_attrib(error, "code" , "406"); + iks_insert_attrib(error, "type", "modify"); + iks_insert_attrib(notacceptable, "xmlns", "urn:ietf:params:xml:ns:xmpp-stanzas"); + iks_insert_node(iq, query); + iks_insert_node(iq, error); + iks_insert_node(error, notacceptable); + ast_aji_send(client, iq); + } else { + ast_log(LOG_ERROR, "Out of memory.\n"); + } + if (iq) + iks_delete(iq); + if (query) + iks_delete(query); + if (error) + iks_delete(error); + if (notacceptable) + iks_delete(notacceptable); + } else if (!(node = iks_find_attrib(pak->query, "node"))) { + iks *iq = NULL, *query = NULL, *instructions = NULL; + char *explain = "Welcome to Asterisk - the Open Source PBX.\n"; + iq = iks_new("iq"); + query = iks_new("query"); + instructions = iks_new("instructions"); + if (iq && query && instructions && client) { + iks_insert_attrib(iq, "from", client->user); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(iq, "type", "result"); + iks_insert_attrib(query, "xmlns", "jabber:iq:register"); + iks_insert_cdata(instructions, explain, 0); + iks_insert_node(iq, query); + iks_insert_node(query, instructions); + ast_aji_send(client, iq); + } else { + ast_log(LOG_ERROR, "Out of memory.\n"); + } + if (iq) + iks_delete(iq); + if (query) + iks_delete(query); + if (instructions) + iks_delete(instructions); + } + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_FILTER_EAT; +} + +/*! + * \brief Handles stuff + * \param data void + * \param pak ikspak + * \return IKS_FILTER_EAT. +*/ +static int aji_ditems_handler(void *data, ikspak *pak) +{ + struct aji_client *client = ASTOBJ_REF((struct aji_client *) data); + char *node = NULL; + + if (!(node = iks_find_attrib(pak->query, "node"))) { + iks *iq = NULL, *query = NULL, *item = NULL; + iq = iks_new("iq"); + query = iks_new("query"); + item = iks_new("item"); + + if (iq && query && item) { + iks_insert_attrib(iq, "from", client->user); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(iq, "type", "result"); + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#items"); + iks_insert_attrib(item, "node", "http://jabber.org/protocol/commands"); + iks_insert_attrib(item, "name", "Million Dollar Asterisk Commands"); + iks_insert_attrib(item, "jid", client->user); + + iks_insert_node(iq, query); + iks_insert_node(query, item); + ast_aji_send(client, iq); + } else { + ast_log(LOG_ERROR, "Out of memory.\n"); + } + if (iq) + iks_delete(iq); + if (query) + iks_delete(query); + if (item) + iks_delete(item); + + } else if (!strcasecmp(node, "http://jabber.org/protocol/commands")) { + iks *iq, *query, *confirm; + iq = iks_new("iq"); + query = iks_new("query"); + confirm = iks_new("item"); + if (iq && query && confirm && client) { + iks_insert_attrib(iq, "from", client->user); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(iq, "type", "result"); + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#items"); + iks_insert_attrib(query, "node", "http://jabber.org/protocol/commands"); + iks_insert_attrib(confirm, "node", "confirmaccount"); + iks_insert_attrib(confirm, "name", "Confirm AIM account"); + iks_insert_attrib(confirm, "jid", "blog.astjab.org"); + + iks_insert_node(iq, query); + iks_insert_node(query, confirm); + ast_aji_send(client, iq); + } else { + ast_log(LOG_ERROR, "Out of memory.\n"); + } + if (iq) + iks_delete(iq); + if (query) + iks_delete(query); + if (confirm) + iks_delete(confirm); + + } else if (!strcasecmp(node, "confirmaccount")) { + iks *iq = NULL, *query = NULL, *feature = NULL; + + iq = iks_new("iq"); + query = iks_new("query"); + feature = iks_new("feature"); + + if (iq && query && feature && client) { + iks_insert_attrib(iq, "from", client->user); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(iq, "type", "result"); + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#items"); + iks_insert_attrib(feature, "var", "http://jabber.org/protocol/commands"); + iks_insert_node(iq, query); + iks_insert_node(query, feature); + ast_aji_send(client, iq); + } else { + ast_log(LOG_ERROR, "Out of memory.\n"); + } + if (iq) + iks_delete(iq); + if (query) + iks_delete(query); + if (feature) + iks_delete(feature); + } + + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_FILTER_EAT; + +} +/*! + * \brief Handle add extra info + * \param data void + * \param pak ikspak + * \return IKS_FILTER_EAT +*/ +static int aji_client_info_handler(void *data, ikspak *pak) +{ + struct aji_client *client = ASTOBJ_REF((struct aji_client *) data); + struct aji_resource *resource = NULL; + struct aji_buddy *buddy = ASTOBJ_CONTAINER_FIND(&client->buddies, pak->from->partial); + + resource = aji_find_resource(buddy, pak->from->resource); + if (pak->subtype == IKS_TYPE_RESULT) { + if (!resource) { + ast_log(LOG_NOTICE,"JABBER: Received client info from %s when not requested.\n", pak->from->full); + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_FILTER_EAT; + } + if (iks_find_with_attrib(pak->query, "feature", "var", "http://www.google.com/xmpp/protocol/voice/v1")) { + resource->cap->jingle = 1; + } else + resource->cap->jingle = 0; + } else if (pak->subtype == IKS_TYPE_GET) { + iks *iq, *disco, *ident, *google, *query; + iq = iks_new("iq"); + query = iks_new("query"); + ident = iks_new("identity"); + disco = iks_new("feature"); + google = iks_new("feature"); + if (iq && ident && disco && google) { + iks_insert_attrib(iq, "from", client->jid->full); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "type", "result"); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#info"); + iks_insert_attrib(ident, "category", "client"); + iks_insert_attrib(ident, "type", "pc"); + iks_insert_attrib(ident, "name", "asterisk"); + iks_insert_attrib(disco, "var", "http://jabber.org/protocol/disco#info"); + iks_insert_attrib(google, "var", "http://www.google.com/xmpp/protocol/voice/v1"); + iks_insert_node(iq, query); + iks_insert_node(query, ident); + iks_insert_node(query, google); + iks_insert_node(query, disco); + ast_aji_send(client, iq); + } else + ast_log(LOG_ERROR, "Out of Memory.\n"); + if (iq) + iks_delete(iq); + if (query) + iks_delete(query); + if (ident) + iks_delete(ident); + if (google) + iks_delete(google); + if (disco) + iks_delete(disco); + } else if (pak->subtype == IKS_TYPE_ERROR) { + ast_log(LOG_NOTICE, "User %s does not support discovery.\n", pak->from->full); + } + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_FILTER_EAT; +} +/*! + * \brief Handler of the return info packet + * \param data aji_client + * \param pak ikspak + * \return IKS_FILTER_EAT +*/ +static int aji_dinfo_handler(void *data, ikspak *pak) +{ + struct aji_client *client = ASTOBJ_REF((struct aji_client *) data); + char *node = NULL; + struct aji_resource *resource = NULL; + struct aji_buddy *buddy = ASTOBJ_CONTAINER_FIND(&client->buddies, pak->from->partial); + + resource = aji_find_resource(buddy, pak->from->resource); + if (pak->subtype == IKS_TYPE_ERROR) { + ast_log(LOG_WARNING, "Recieved error from a client, turn on jabber debug!\n"); + return IKS_FILTER_EAT; + } + if (pak->subtype == IKS_TYPE_RESULT) { + if (!resource) { + ast_log(LOG_NOTICE,"JABBER: Received client info from %s when not requested.\n", pak->from->full); + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_FILTER_EAT; + } + if (iks_find_with_attrib(pak->query, "feature", "var", "http://www.google.com/xmpp/protocol/voice/v1")) { + resource->cap->jingle = 1; + } else + resource->cap->jingle = 0; + } else if (pak->subtype == IKS_TYPE_GET && !(node = iks_find_attrib(pak->query, "node"))) { + iks *iq, *query, *identity, *disco, *reg, *commands, *gateway, *version, *vcard, *search; + + iq = iks_new("iq"); + query = iks_new("query"); + identity = iks_new("identity"); + disco = iks_new("feature"); + reg = iks_new("feature"); + commands = iks_new("feature"); + gateway = iks_new("feature"); + version = iks_new("feature"); + vcard = iks_new("feature"); + search = iks_new("feature"); + + if (iq && query && identity && disco && reg && commands && gateway && version && vcard && search && client) { + iks_insert_attrib(iq, "from", client->user); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(iq, "type", "result"); + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#info"); + iks_insert_attrib(identity, "category", "gateway"); + iks_insert_attrib(identity, "type", "pstn"); + iks_insert_attrib(identity, "name", "Asterisk The Open Source PBX"); + iks_insert_attrib(disco, "var", "http://jabber.org/protocol/disco"); + iks_insert_attrib(reg, "var", "jabber:iq:register"); + iks_insert_attrib(commands, "var", "http://jabber.org/protocol/commands"); + iks_insert_attrib(gateway, "var", "jabber:iq:gateway"); + iks_insert_attrib(version, "var", "jabber:iq:version"); + iks_insert_attrib(vcard, "var", "vcard-temp"); + iks_insert_attrib(search, "var", "jabber:iq:search"); + + iks_insert_node(iq, query); + iks_insert_node(query, identity); + iks_insert_node(query, disco); + iks_insert_node(query, reg); + iks_insert_node(query, commands); + iks_insert_node(query, gateway); + iks_insert_node(query, version); + iks_insert_node(query, vcard); + iks_insert_node(query, search); + ast_aji_send(client, iq); + } else { + ast_log(LOG_ERROR, "Out of memory.\n"); + } + + if (iq) + iks_delete(iq); + if (query) + iks_delete(query); + if (identity) + iks_delete(identity); + if (disco) + iks_delete(disco); + if (reg) + iks_delete(reg); + if (commands) + iks_delete(commands); + if (gateway) + iks_delete(gateway); + if (version) + iks_delete(version); + if (vcard) + iks_delete(vcard); + if (search) + iks_delete(search); + + } else if (pak->subtype == IKS_TYPE_GET && !strcasecmp(node, "http://jabber.org/protocol/commands")) { + iks *iq, *query, *confirm; + iq = iks_new("iq"); + query = iks_new("query"); + confirm = iks_new("item"); + + if (iq && query && confirm && client) { + iks_insert_attrib(iq, "from", client->user); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(iq, "type", "result"); + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#items"); + iks_insert_attrib(query, "node", "http://jabber.org/protocol/commands"); + iks_insert_attrib(confirm, "node", "confirmaccount"); + iks_insert_attrib(confirm, "name", "Confirm AIM account"); + iks_insert_attrib(confirm, "jid", client->user); + iks_insert_node(iq, query); + iks_insert_node(query, confirm); + ast_aji_send(client, iq); + } else { + ast_log(LOG_ERROR, "Out of memory.\n"); + } + if (iq) + iks_delete(iq); + if (query) + iks_delete(query); + if (confirm) + iks_delete(confirm); + + } else if (pak->subtype == IKS_TYPE_GET && !strcasecmp(node, "confirmaccount")) { + iks *iq, *query, *feature; + + iq = iks_new("iq"); + query = iks_new("query"); + feature = iks_new("feature"); + + if (iq && query && feature && client) { + iks_insert_attrib(iq, "from", client->user); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(iq, "type", "result"); + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#info"); + iks_insert_attrib(feature, "var", "http://jabber.org/protocol/commands"); + iks_insert_node(iq, query); + iks_insert_node(query, feature); + ast_aji_send(client, iq); + } else { + ast_log(LOG_ERROR, "Out of memory.\n"); + } + if (iq) + iks_delete(iq); + if (query) + iks_delete(query); + if (feature) + iks_delete(feature); + } + + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_FILTER_EAT; +} + +/*! + * \brief Handles \verbatim <iq> \endverbatim tags. + * \param client the configured XMPP client we use to connect to a XMPP server + * \param node iks + * \return void. + */ +static void aji_handle_iq(struct aji_client *client, iks *node) +{ + /*Nothing to see here */ +} + +/*! + * \brief Handles presence packets. + * \param client the configured XMPP client we use to connect to a XMPP server + * \param pak ikspak the node + */ +static void aji_handle_message(struct aji_client *client, ikspak *pak) +{ + struct aji_message *insert, *tmp; + int flag = 0; + + if (!(insert = ast_calloc(1, sizeof(*insert)))) + return; + time(&insert->arrived); + if (iks_find_cdata(pak->x, "body")) + insert->message = ast_strdup(iks_find_cdata(pak->x, "body")); + if (pak->id) + ast_copy_string(insert->id, pak->id, sizeof(insert->message)); + if (pak->from) + insert->from = ast_strdup(pak->from->full); + AST_LIST_LOCK(&client->messages); + AST_LIST_TRAVERSE_SAFE_BEGIN(&client->messages, tmp, list) { + if (flag) { + AST_LIST_REMOVE_CURRENT(list); + if (tmp->from) + ast_free(tmp->from); + if (tmp->message) + ast_free(tmp->message); + } else if (difftime(time(NULL), tmp->arrived) >= client->message_timeout) { + flag = 1; + AST_LIST_REMOVE_CURRENT(list); + if (tmp->from) + ast_free(tmp->from); + if (tmp->message) + ast_free(tmp->message); + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_INSERT_HEAD(&client->messages, insert, list); + AST_LIST_UNLOCK(&client->messages); +} +/*! + * \brief Check the presence info + * \param client the configured XMPP client we use to connect to a XMPP server + * \param pak ikspak +*/ +static void aji_handle_presence(struct aji_client *client, ikspak *pak) +{ + int status, priority; + struct aji_buddy *buddy; + struct aji_resource *tmp = NULL, *last = NULL, *found = NULL; + char *ver, *node, *descrip, *type; + + if(client->state != AJI_CONNECTED) + aji_create_buddy(pak->from->partial, client); + + buddy = ASTOBJ_CONTAINER_FIND(&client->buddies, pak->from->partial); + if (!buddy && pak->from->partial) { + /* allow our jid to be used to log in with another resource */ + if (!strcmp((const char *)pak->from->partial, (const char *)client->jid->partial)) + aji_create_buddy(pak->from->partial, client); + else + ast_log(LOG_NOTICE, "Got presence packet from %s, someone not in our roster!!!!\n", pak->from->partial); + return; + } + type = iks_find_attrib(pak->x, "type"); + if(client->component && type &&!strcasecmp("probe", type)) { + aji_set_presence(client, pak->from->full, iks_find_attrib(pak->x, "to"), client->status, client->statusmessage); + ast_verbose("what i was looking for \n"); + } + ASTOBJ_WRLOCK(buddy); + status = (pak->show) ? pak->show : 6; + priority = atoi((iks_find_cdata(pak->x, "priority")) ? iks_find_cdata(pak->x, "priority") : "0"); + tmp = buddy->resources; + descrip = ast_strdup(iks_find_cdata(pak->x,"status")); + + while (tmp && pak->from->resource) { + if (!strcasecmp(tmp->resource, pak->from->resource)) { + tmp->status = status; + if (tmp->description) ast_free(tmp->description); + tmp->description = descrip; + found = tmp; + if (status == 6) { /* Sign off Destroy resource */ + if (last && found->next) { + last->next = found->next; + } else if (!last) { + if (found->next) + buddy->resources = found->next; + else + buddy->resources = NULL; + } else if (!found->next) { + if (last) + last->next = NULL; + else + buddy->resources = NULL; + } + ast_free(found); + found = NULL; + break; + } + /* resource list is sorted by descending priority */ + if (tmp->priority != priority) { + found->priority = priority; + if (!last && !found->next) + /* resource was found to be unique, + leave loop */ + break; + /* search for resource in our list + and take it out for the moment */ + if (last) + last->next = found->next; + else + buddy->resources = found->next; + + last = NULL; + tmp = buddy->resources; + if (!buddy->resources) + buddy->resources = found; + /* priority processing */ + while (tmp) { + /* insert resource back according to + its priority value */ + if (found->priority > tmp->priority) { + if (last) + /* insert within list */ + last->next = found; + found->next = tmp; + if (!last) + /* insert on top */ + buddy->resources = found; + break; + } + if (!tmp->next) { + /* insert at the end of the list */ + tmp->next = found; + found->next = NULL; + break; + } + last = tmp; + tmp = tmp->next; + } + } + break; + } + last = tmp; + tmp = tmp->next; + } + + /* resource not found in our list, create it */ + if (!found && status != 6 && pak->from->resource) { + found = ast_calloc(1, sizeof(*found)); + + if (!found) { + ast_log(LOG_ERROR, "Out of memory!\n"); + return; + } + ast_copy_string(found->resource, pak->from->resource, sizeof(found->resource)); + found->status = status; + found->description = descrip; + found->priority = priority; + found->next = NULL; + last = NULL; + tmp = buddy->resources; + while (tmp) { + if (found->priority > tmp->priority) { + if (last) + last->next = found; + found->next = tmp; + if (!last) + buddy->resources = found; + break; + } + if (!tmp->next) { + tmp->next = found; + break; + } + last = tmp; + tmp = tmp->next; + } + if (!tmp) + buddy->resources = found; + } + + ASTOBJ_UNLOCK(buddy); + ASTOBJ_UNREF(buddy, aji_buddy_destroy); + + node = iks_find_attrib(iks_find(pak->x, "c"), "node"); + ver = iks_find_attrib(iks_find(pak->x, "c"), "ver"); + + /* handle gmail client's special caps:c tag */ + if (!node && !ver) { + node = iks_find_attrib(iks_find(pak->x, "caps:c"), "node"); + ver = iks_find_attrib(iks_find(pak->x, "caps:c"), "ver"); + } + + /* retrieve capabilites of the new resource */ + if(status !=6 && found && !found->cap) { + found->cap = aji_find_version(node, ver, pak); + if(gtalk_yuck(pak->x)) /* gtalk should do discover */ + found->cap->jingle = 1; + if(found->cap->jingle && option_debug > 4) { + ast_debug(1,"Special case for google till they support discover.\n"); + } + else { + iks *iq, *query; + iq = iks_new("iq"); + query = iks_new("query"); + if(query && iq) { + iks_insert_attrib(iq, "type", "get"); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq,"from", client->jid->full); + iks_insert_attrib(iq, "id", client->mid); + ast_aji_increment_mid(client->mid); + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#info"); + iks_insert_node(iq, query); + ast_aji_send(client, iq); + + } else + ast_log(LOG_ERROR, "Out of memory.\n"); + if(query) + iks_delete(query); + if(iq) + iks_delete(iq); + } + } + switch (pak->subtype) { + case IKS_TYPE_AVAILABLE: + ast_verb(5, "JABBER: I am available ^_* %i\n", pak->subtype); + break; + case IKS_TYPE_UNAVAILABLE: + ast_verb(5, "JABBER: I am unavailable ^_* %i\n", pak->subtype); + break; + default: + ast_verb(5, "JABBER: Ohh sexy and the wrong type: %i\n", pak->subtype); + } + switch (pak->show) { + case IKS_SHOW_UNAVAILABLE: + ast_verb(5, "JABBER: type: %i subtype %i\n", pak->subtype, pak->show); + break; + case IKS_SHOW_AVAILABLE: + ast_verb(5, "JABBER: type is available\n"); + break; + case IKS_SHOW_CHAT: + ast_verb(5, "JABBER: type: %i subtype %i\n", pak->subtype, pak->show); + break; + case IKS_SHOW_AWAY: + ast_verb(5, "JABBER: type is away\n"); + break; + case IKS_SHOW_XA: + ast_verb(5, "JABBER: type: %i subtype %i\n", pak->subtype, pak->show); + break; + case IKS_SHOW_DND: + ast_verb(5, "JABBER: type: %i subtype %i\n", pak->subtype, pak->show); + break; + default: + ast_verb(5, "JABBER: Kinky! how did that happen %i\n", pak->show); + } +} + +/*! + * \brief handles subscription requests. + * \param client the configured XMPP client we use to connect to a XMPP server + * \param pak ikspak iksemel packet. + * \return void. + */ +static void aji_handle_subscribe(struct aji_client *client, ikspak *pak) +{ + if (pak->subtype == IKS_TYPE_SUBSCRIBE) { + iks *presence = NULL, *status = NULL; + presence = iks_new("presence"); + status = iks_new("status"); + if (presence && status) { + iks_insert_attrib(presence, "type", "subscribed"); + iks_insert_attrib(presence, "to", pak->from->full); + iks_insert_attrib(presence, "from", client->jid->full); + if (pak->id) + iks_insert_attrib(presence, "id", pak->id); + iks_insert_cdata(status, "Asterisk has approved subscription", 0); + iks_insert_node(presence, status); + ast_aji_send(client, presence); + } else + ast_log(LOG_ERROR, "Unable to allocate nodes\n"); + if (presence) + iks_delete(presence); + if (status) + iks_delete(status); + if (client->component) + aji_set_presence(client, pak->from->full, iks_find_attrib(pak->x, "to"), client->status, client->statusmessage); + } + + switch (pak->subtype) { + case IKS_TYPE_SUBSCRIBE: + ast_verb(5, "JABBER: Subscribe handled.\n"); + break; + case IKS_TYPE_SUBSCRIBED: + ast_verb(5, "JABBER: Subscribed (%d) not handled.\n", pak->subtype); + break; + case IKS_TYPE_UNSUBSCRIBE: + ast_verb(5, "JABBER: Unsubscribe (%d) not handled.\n", pak->subtype); + break; + case IKS_TYPE_UNSUBSCRIBED: + ast_verb(5, "JABBER: Unsubscribed (%d) not handled.\n", pak->subtype); + break; + default: /*IKS_TYPE_ERROR: */ + ast_verb(5, "JABBER: Unknown pak subtype %d.\n", pak->subtype); + break; + } +} + +/*! + * \brief sends messages. + * \param client the configured XMPP client we use to connect to a XMPP server + * \param address + * \param message + * \return 1. + */ +int ast_aji_send_chat(struct aji_client *client, const char *address, const char *message) +{ + int res = 0; + iks *message_packet = NULL; + if (client->state == AJI_CONNECTED) { + message_packet = iks_make_msg(IKS_TYPE_CHAT, address, message); + if (message_packet) { + iks_insert_attrib(message_packet, "from", client->jid->full); + res = ast_aji_send(client, message_packet); + } else { + ast_log(LOG_ERROR, "Out of memory.\n"); + } + if (message_packet) + iks_delete(message_packet); + } else + ast_log(LOG_WARNING, "JABBER: Not connected can't send\n"); + return 1; +} + +/*! + * \brief create a chatroom. + * \param client the configured XMPP client we use to connect to a XMPP server + * \param room name of room + * \param server name of server + * \param topic topic for the room. + * \return 0. + */ +int ast_aji_create_chat(struct aji_client *client, char *room, char *server, char *topic) +{ + int res = 0; + iks *iq = NULL; + iq = iks_new("iq"); + if (iq && client) { + iks_insert_attrib(iq, "type", "get"); + iks_insert_attrib(iq, "to", server); + iks_insert_attrib(iq, "id", client->mid); + ast_aji_increment_mid(client->mid); + ast_aji_send(client, iq); + } else + ast_log(LOG_ERROR, "Out of memory.\n"); + return res; +} + +/*! + * \brief join a chatroom. + * \param client the configured XMPP client we use to connect to a XMPP server + * \param room room to join + * \return res. + */ +int ast_aji_join_chat(struct aji_client *client, char *room) +{ + int res = 0; + iks *presence = NULL, *priority = NULL; + presence = iks_new("presence"); + priority = iks_new("priority"); + if (presence && priority && client) { + iks_insert_cdata(priority, "0", 1); + iks_insert_attrib(presence, "to", room); + iks_insert_node(presence, priority); + res = ast_aji_send(client, presence); + iks_insert_cdata(priority, "5", 1); + iks_insert_attrib(presence, "to", room); + res = ast_aji_send(client, presence); + } else + ast_log(LOG_ERROR, "Out of memory.\n"); + if (presence) + iks_delete(presence); + if (priority) + iks_delete(priority); + return res; +} + +/*! + * \brief invite to a chatroom. + * \param client the configured XMPP client we use to connect to a XMPP server + * \param user + * \param room + * \param message + * \return res. + */ +int ast_aji_invite_chat(struct aji_client *client, char *user, char *room, char *message) +{ + int res = 0; + iks *invite, *body, *namespace; + + invite = iks_new("message"); + body = iks_new("body"); + namespace = iks_new("x"); + if (client && invite && body && namespace) { + iks_insert_attrib(invite, "to", user); + iks_insert_attrib(invite, "id", client->mid); + ast_aji_increment_mid(client->mid); + iks_insert_cdata(body, message, 0); + iks_insert_attrib(namespace, "xmlns", "jabber:x:conference"); + iks_insert_attrib(namespace, "jid", room); + iks_insert_node(invite, body); + iks_insert_node(invite, namespace); + res = ast_aji_send(client, invite); + } else + ast_log(LOG_ERROR, "Out of memory.\n"); + if (body) + iks_delete(body); + if (namespace) + iks_delete(namespace); + if (invite) + iks_delete(invite); + return res; +} + + +/*! + * \brief receive message loop. + * \param data void + * \return void. + */ +static void *aji_recv_loop(void *data) +{ + struct aji_client *client = ASTOBJ_REF((struct aji_client *) data); + int res = IKS_HOOK; + + while(res != IKS_OK) { + if(option_verbose > 3) + ast_verbose("JABBER: Connecting.\n"); + res = aji_reconnect(client); + sleep(4); + } + + do { + if (res == IKS_NET_RWERR || client->timeout == 0) { + while(res != IKS_OK) { + ast_verb(4, "JABBER: reconnecting.\n"); + res = aji_reconnect(client); + sleep(4); + } + } + + res = aji_recv(client, 1); + + if (client->state == AJI_DISCONNECTING) { + ast_debug(2, "Ending our Jabber client's thread due to a disconnect\n"); + pthread_exit(NULL); + } + + /* Decrease timeout if no data received */ + if (res == IKS_NET_EXPIRED) + client->timeout--; + + if (res == IKS_HOOK) + ast_log(LOG_WARNING, "JABBER: Got hook event.\n"); + else if (res == IKS_NET_TLSFAIL) + ast_log(LOG_WARNING, "JABBER: Failure in TLS.\n"); + else if (client->timeout == 0 && client->state == AJI_CONNECTED) { + res = aji_send_raw(client, " "); + if(res == IKS_OK) + client->timeout = 50; + else + ast_log(LOG_WARNING, "JABBER: Network Timeout\n"); + } else if (res == IKS_NET_RWERR) + ast_log(LOG_WARNING, "JABBER: socket read error\n"); + } while (client); + ASTOBJ_UNREF(client, aji_client_destroy); + return 0; +} + +/*! + * \brief increments the mid field for messages and other events. + * \param mid char. + * \return void. + */ +void ast_aji_increment_mid(char *mid) +{ + int i = 0; + + for (i = strlen(mid) - 1; i >= 0; i--) { + if (mid[i] != 'z') { + mid[i] = mid[i] + 1; + i = 0; + } else + mid[i] = 'a'; + } +} + +#if 0 +/*! + * \brief attempts to register to a transport. + * \param aji_client struct, and xml packet. + * \return IKS_FILTER_EAT. + */ +/*allows for registering to transport , was too sketch and is out for now. */ +static int aji_register_transport(void *data, ikspak *pak) +{ + struct aji_client *client = ASTOBJ_REF((struct aji_client *) data); + int res = 0; + struct aji_buddy *buddy = NULL; + iks *send = iks_make_iq(IKS_TYPE_GET, "jabber:iq:register"); + + if (client && send) { + ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, { + ASTOBJ_RDLOCK(iterator); + if (iterator->btype == AJI_TRANS) { + buddy = iterator; + } + ASTOBJ_UNLOCK(iterator); + }); + iks_filter_remove_hook(client->f, aji_register_transport); + iks_filter_add_rule(client->f, aji_register_transport2, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_NS, IKS_NS_REGISTER, IKS_RULE_DONE); + iks_insert_attrib(send, "to", buddy->host); + iks_insert_attrib(send, "id", client->mid); + ast_aji_increment_mid(client->mid); + iks_insert_attrib(send, "from", client->user); + res = ast_aji_send(client, send); + } else + ast_log(LOG_ERROR, "Out of memory.\n"); + + if (send) + iks_delete(send); + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_FILTER_EAT; + +} +/*! + * \brief attempts to register to a transport step 2. + * \param aji_client struct, and xml packet. + * \return IKS_FILTER_EAT. + */ +/* more of the same blob of code, too wonky for now*/ +static int aji_register_transport2(void *data, ikspak *pak) +{ + struct aji_client *client = ASTOBJ_REF((struct aji_client *) data); + int res = 0; + struct aji_buddy *buddy = NULL; + + iks *regiq = iks_new("iq"); + iks *regquery = iks_new("query"); + iks *reguser = iks_new("username"); + iks *regpass = iks_new("password"); + + if (client && regquery && reguser && regpass && regiq) { + ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, { + ASTOBJ_RDLOCK(iterator); + if (iterator->btype == AJI_TRANS) + buddy = iterator; ASTOBJ_UNLOCK(iterator); + }); + iks_filter_remove_hook(client->f, aji_register_transport2); + iks_insert_attrib(regiq, "to", buddy->host); + iks_insert_attrib(regiq, "type", "set"); + iks_insert_attrib(regiq, "id", client->mid); + ast_aji_increment_mid(client->mid); + iks_insert_attrib(regiq, "from", client->user); + iks_insert_attrib(regquery, "xmlns", "jabber:iq:register"); + iks_insert_cdata(reguser, buddy->user, 0); + iks_insert_cdata(regpass, buddy->pass, 0); + iks_insert_node(regiq, regquery); + iks_insert_node(regquery, reguser); + iks_insert_node(regquery, regpass); + res = ast_aji_send(client, regiq); + } else + ast_log(LOG_ERROR, "Out of memory.\n"); + if (regiq) + iks_delete(regiq); + if (regquery) + iks_delete(regquery); + if (reguser) + iks_delete(reguser); + if (regpass) + iks_delete(regpass); + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_FILTER_EAT; +} +#endif + +/*! + * \brief goes through roster and prunes users not needed in list, or adds them accordingly. + * \param client the configured XMPP client we use to connect to a XMPP server + * \return void. + */ +static void aji_pruneregister(struct aji_client *client) +{ + int res = 0; + iks *removeiq = iks_new("iq"); + iks *removequery = iks_new("query"); + iks *removeitem = iks_new("item"); + iks *send = iks_make_iq(IKS_TYPE_GET, "http://jabber.org/protocol/disco#items"); + + if (client && removeiq && removequery && removeitem && send) { + iks_insert_node(removeiq, removequery); + iks_insert_node(removequery, removeitem); + ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, { + ASTOBJ_RDLOCK(iterator); + /* For an aji_buddy, both AUTOPRUNE and AUTOREGISTER will never + * be called at the same time */ + if (ast_test_flag(iterator, AJI_AUTOPRUNE)) { + res = ast_aji_send(client, iks_make_s10n(IKS_TYPE_UNSUBSCRIBE, iterator->name, + "GoodBye your status is no longer needed by Asterisk the Open Source PBX" + " so I am no longer subscribing to your presence.\n")); + res = ast_aji_send(client, iks_make_s10n(IKS_TYPE_UNSUBSCRIBED, iterator->name, + "GoodBye you are no longer in the asterisk config file so I am removing" + " your access to my presence.\n")); + iks_insert_attrib(removeiq, "from", client->jid->full); + iks_insert_attrib(removeiq, "type", "set"); + iks_insert_attrib(removequery, "xmlns", "jabber:iq:roster"); + iks_insert_attrib(removeitem, "jid", iterator->name); + iks_insert_attrib(removeitem, "subscription", "remove"); + res = ast_aji_send(client, removeiq); + } else if (ast_test_flag(iterator, AJI_AUTOREGISTER)) { + res = ast_aji_send(client, iks_make_s10n(IKS_TYPE_SUBSCRIBE, iterator->name, + "Greetings I am the Asterisk Open Source PBX and I want to subscribe to your presence\n")); + ast_clear_flag(iterator, AJI_AUTOREGISTER); + } + ASTOBJ_UNLOCK(iterator); + }); + } else + ast_log(LOG_ERROR, "Out of memory.\n"); + if (removeiq) + iks_delete(removeiq); + if (removequery) + iks_delete(removequery); + if (removeitem) + iks_delete(removeitem); + if (send) + iks_delete(send); + ASTOBJ_CONTAINER_PRUNE_MARKED(&client->buddies, aji_buddy_destroy); +} + +/*! + * \brief filters the roster packet we get back from server. + * \param data void + * \param pak ikspak iksemel packet. + * \return IKS_FILTER_EAT. + */ +static int aji_filter_roster(void *data, ikspak *pak) +{ + struct aji_client *client = ASTOBJ_REF((struct aji_client *) data); + int flag = 0; + iks *x = NULL; + struct aji_buddy *buddy; + + client->state = AJI_CONNECTED; + ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, { + ASTOBJ_RDLOCK(iterator); + x = iks_child(pak->query); + flag = 0; + while (x) { + if (!iks_strcmp(iks_name(x), "item")) { + if (!strcasecmp(iterator->name, iks_find_attrib(x, "jid"))) { + flag = 1; + ast_clear_flag(iterator, AJI_AUTOPRUNE | AJI_AUTOREGISTER); + } + } + x = iks_next(x); + } + if (!flag) + ast_copy_flags(iterator, client, AJI_AUTOREGISTER); + if (x) + iks_delete(x); + ASTOBJ_UNLOCK(iterator); + }); + + x = iks_child(pak->query); + while (x) { + flag = 0; + if (iks_strcmp(iks_name(x), "item") == 0) { + ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, { + ASTOBJ_RDLOCK(iterator); + if (!strcasecmp(iterator->name, iks_find_attrib(x, "jid"))) + flag = 1; + ASTOBJ_UNLOCK(iterator); + }); + + if (!flag) { + buddy = ast_calloc(1, sizeof(*buddy)); + if (!buddy) { + ast_log(LOG_WARNING, "Out of memory\n"); + return 0; + } + ASTOBJ_INIT(buddy); + ASTOBJ_WRLOCK(buddy); + ast_copy_string(buddy->name, iks_find_attrib(x, "jid"), sizeof(buddy->name)); + ast_clear_flag(buddy, AST_FLAGS_ALL); + if(ast_test_flag(client, AJI_AUTOPRUNE)) { + ast_set_flag(buddy, AJI_AUTOPRUNE); + buddy->objflags |= ASTOBJ_FLAG_MARKED; + } else + ast_set_flag(buddy, AJI_AUTOREGISTER); + ASTOBJ_UNLOCK(buddy); + if (buddy) { + ASTOBJ_CONTAINER_LINK(&client->buddies, buddy); + ASTOBJ_UNREF(buddy, aji_buddy_destroy); + } + } + } + x = iks_next(x); + } + if (x) + iks_delete(x); + aji_pruneregister(client); + + ASTOBJ_UNREF(client, aji_client_destroy); + return IKS_FILTER_EAT; +} +/*! + * \brief reconnect to jabber server + * \param client the configured XMPP client we use to connect to a XMPP server + * \return res. +*/ +static int aji_reconnect(struct aji_client *client) +{ + int res = 0; + + if (client->state) + client->state = AJI_DISCONNECTED; + client->timeout=50; + if (client->p) + iks_parser_reset(client->p); + if (client->authorized) + client->authorized = 0; + + res = aji_initialize(client); + + return res; +} +/*! + * \brief Get the roster of jabber users + * \param client the configured XMPP client we use to connect to a XMPP server + * \return 1. +*/ +static int aji_get_roster(struct aji_client *client) +{ + iks *roster = NULL; + roster = iks_make_iq(IKS_TYPE_GET, IKS_NS_ROSTER); + if(roster) { + iks_insert_attrib(roster, "id", "roster"); + aji_set_presence(client, NULL, client->jid->full, client->status, client->statusmessage); + ast_aji_send(client, roster); + } + if (roster) + iks_delete(roster); + return 1; +} + +/*! + * \brief connects as a client to jabber server. + * \param data void + * \param pak ikspak iksemel packet + * \return res. + */ +static int aji_client_connect(void *data, ikspak *pak) +{ + struct aji_client *client = ASTOBJ_REF((struct aji_client *) data); + int res = 0; + + if (client) { + if (client->state == AJI_DISCONNECTED) { + iks_filter_add_rule(client->f, aji_filter_roster, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_ID, "roster", IKS_RULE_DONE); + client->state = AJI_CONNECTING; + client->jid = (iks_find_cdata(pak->query, "jid")) ? iks_id_new(client->stack, iks_find_cdata(pak->query, "jid")) : client->jid; + iks_filter_remove_hook(client->f, aji_client_connect); + if(!client->component) /*client*/ + aji_get_roster(client); + } + } else + ast_log(LOG_ERROR, "Out of memory.\n"); + + ASTOBJ_UNREF(client, aji_client_destroy); + return res; +} + +/*! + * \brief prepares client for connect. + * \param client the configured XMPP client we use to connect to a XMPP server + * \return 1. + */ +static int aji_initialize(struct aji_client *client) +{ + int connected = IKS_NET_NOCONN; + + /* reset stream flags */ + client->stream_flags = 0; + + /* If it's a component, connect to user, otherwise, connect to server */ + connected = iks_connect_via(client->p, S_OR(client->serverhost, client->jid->server), client->port, client->component ? client->user : client->jid->server); + + if (connected == IKS_NET_NOCONN) { + ast_log(LOG_ERROR, "JABBER ERROR: No Connection\n"); + return IKS_HOOK; + } else if (connected == IKS_NET_NODNS) { + ast_log(LOG_ERROR, "JABBER ERROR: No DNS %s for client to %s\n", client->name, S_OR(client->serverhost, client->jid->server)); + return IKS_HOOK; + } + + return IKS_OK; +} + +/*! + * \brief disconnect from jabber server. + * \param client the configured XMPP client we use to connect to a XMPP server + * \return 1. + */ +int ast_aji_disconnect(struct aji_client *client) +{ + if (client) { + ast_verb(4, "JABBER: Disconnecting\n"); +#ifdef HAVE_OPENSSL + if (client->stream_flags & SECURE) { + SSL_shutdown(client->ssl_session); + SSL_CTX_free(client->ssl_context); + SSL_free(client->ssl_session); + } +#endif + iks_disconnect(client->p); + iks_parser_delete(client->p); + ASTOBJ_UNREF(client, aji_client_destroy); + } + + return 1; +} + +/*! + * \brief set presence of client. + * \param client the configured XMPP client we use to connect to a XMPP server + * \param to user send it to + * \param from user it came from + * \param level + * \param desc + * \return void. + */ +static void aji_set_presence(struct aji_client *client, char *to, char *from, int level, char *desc) +{ + int res = 0; + iks *presence = iks_make_pres(level, desc); + iks *cnode = iks_new("c"); + iks *priority = iks_new("priority"); + char priorityS[10]; + + if (presence && cnode && client && priority) { + if(to) + iks_insert_attrib(presence, "to", to); + if(from) + iks_insert_attrib(presence, "from", from); + snprintf(priorityS, sizeof(priorityS), "%d", client->priority); + iks_insert_cdata(priority, priorityS, strlen(priorityS)); + iks_insert_node(presence, priority); + iks_insert_attrib(cnode, "node", "http://www.asterisk.org/xmpp/client/caps"); + iks_insert_attrib(cnode, "ver", "asterisk-xmpp"); + iks_insert_attrib(cnode, "ext", "voice-v1"); + iks_insert_attrib(cnode, "xmlns", "http://jabber.org/protocol/caps"); + iks_insert_node(presence, cnode); + res = ast_aji_send(client, presence); + } else + ast_log(LOG_ERROR, "Out of memory.\n"); + if (cnode) + iks_delete(cnode); + if (presence) + iks_delete(presence); + if (priority) + iks_delete(priority); +} + +/*! + * \brief Turn on console debugging. + * \return CLI_SUCCESS. + */ +static char *aji_do_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + + switch (cmd) { + case CLI_INIT: + e->command = "jabber debug"; + e->usage = + "Usage: jabber debug\n" + " Enables dumping of Jabber packets for debugging purposes.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, { + ASTOBJ_RDLOCK(iterator); + iterator->debug = 1; + ASTOBJ_UNLOCK(iterator); + }); + ast_cli(a->fd, "Jabber Debugging Enabled.\n"); + return CLI_SUCCESS; +} + +/*! + * \brief Reload jabber module. + * \return CLI_SUCCESS. + */ +static char *aji_do_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "jabber reload"; + e->usage = + "Usage: jabber reload\n" + " Reloads the Jabber module.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + aji_reload(1); + ast_cli(a->fd, "Jabber Reloaded.\n"); + return CLI_SUCCESS; +} + +/*! + * \brief Turn off console debugging. + * \return CLI_SUCCESS. + */ +static char *aji_no_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "jabber debug off"; + e->usage = + "Usage: jabber debug off\n" + " Disables dumping of Jabber packets for debugging purposes.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, { + ASTOBJ_RDLOCK(iterator); + iterator->debug = 0; + ASTOBJ_UNLOCK(iterator); + }); + ast_cli(a->fd, "Jabber Debugging Disabled.\n"); + return CLI_SUCCESS; +} + +/*! + * \brief Show client status. + * \return CLI_SUCCESS. + */ +static char *aji_show_clients(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + char *status; + int count = 0; + + switch (cmd) { + case CLI_INIT: + e->command = "jabber show connected"; + e->usage = + "Usage: jabber show connected\n" + " Shows state of clients and components\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ast_cli(a->fd, "Jabber Users and their status:\n"); + ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, { + ASTOBJ_RDLOCK(iterator); + count++; + switch (iterator->state) { + case AJI_DISCONNECTED: + status = "Disconnected"; + break; + case AJI_CONNECTING: + status = "Connecting"; + break; + case AJI_CONNECTED: + status = "Connected"; + break; + default: + status = "Unknown"; + } + ast_cli(a->fd, " User: %s - %s\n", iterator->user, status); + ASTOBJ_UNLOCK(iterator); + }); + ast_cli(a->fd, "----\n"); + ast_cli(a->fd, " Number of users: %d\n", count); + return CLI_SUCCESS; +} + +/*! + * \brief Show buddy lists + * \return CLI_SUCCESS. + */ +static char *aji_show_buddies(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct aji_resource *resource; + struct aji_client *client; + + switch (cmd) { + case CLI_INIT: + e->command = "jabber show buddies"; + e->usage = + "Usage: jabber show buddies\n" + " Shows buddy lists of our clients\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + ast_cli(a->fd, "Jabber buddy lists\n"); + ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, { + ast_cli(a->fd,"Client: %s\n", iterator->user); + client = iterator; + ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, { + ASTOBJ_RDLOCK(iterator); + ast_cli(a->fd,"\tBuddy:\t%s\n", iterator->name); + if (!iterator->resources) + ast_cli(a->fd,"\t\tResource: None\n"); + for (resource = iterator->resources; resource; resource = resource->next) { + ast_cli(a->fd,"\t\tResource: %s\n", resource->resource); + if(resource->cap) { + ast_cli(a->fd,"\t\t\tnode: %s\n", resource->cap->parent->node); + ast_cli(a->fd,"\t\t\tversion: %s\n", resource->cap->version); + ast_cli(a->fd,"\t\t\tJingle capable: %s\n", resource->cap->jingle ? "yes" : "no"); + } + ast_cli(a->fd,"\t\tStatus: %d\n", resource->status); + ast_cli(a->fd,"\t\tPriority: %d\n", resource->priority); + } + ASTOBJ_UNLOCK(iterator); + }); + iterator = client; + }); + return CLI_SUCCESS; +} + +/*! + * \brief Send test message for debugging. + * \return CLI_SUCCESS,CLI_FAILURE. + */ +static char *aji_test(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct aji_client *client; + struct aji_resource *resource; + const char *name = "asterisk"; + struct aji_message *tmp; + + switch (cmd) { + case CLI_INIT: + e->command = "jabber test"; + e->usage = + "Usage: jabber test [client]\n" + " Sends test message for debugging purposes. A specific client\n" + " as configured in jabber.conf can be optionally specified.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc > 3) + return CLI_SHOWUSAGE; + else if (a->argc == 3) + name = a->argv[2]; + + if (!(client = ASTOBJ_CONTAINER_FIND(&clients, name))) { + ast_cli(a->fd, "Unable to find client '%s'!\n", name); + return CLI_FAILURE; + } + + /* XXX Does Matt really want everyone to use his personal address for tests? */ /* XXX yes he does */ + ast_aji_send_chat(client, "mogorman@astjab.org", "blahblah"); + ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, { + ASTOBJ_RDLOCK(iterator); + ast_verbose("User: %s\n", iterator->name); + for (resource = iterator->resources; resource; resource = resource->next) { + ast_verbose("Resource: %s\n", resource->resource); + if(resource->cap) { + ast_verbose(" client: %s\n", resource->cap->parent->node); + ast_verbose(" version: %s\n", resource->cap->version); + ast_verbose(" Jingle Capable: %d\n", resource->cap->jingle); + } + ast_verbose(" Priority: %d\n", resource->priority); + ast_verbose(" Status: %d\n", resource->status); + ast_verbose(" Message: %s\n", S_OR(resource->description,"")); + } + ASTOBJ_UNLOCK(iterator); + }); + ast_verbose("\nOooh a working message stack!\n"); + AST_LIST_LOCK(&client->messages); + AST_LIST_TRAVERSE(&client->messages, tmp, list) { + ast_verbose(" Message from: %s with id %s @ %s %s\n",tmp->from, S_OR(tmp->id,""), ctime(&tmp->arrived), S_OR(tmp->message, "")); + } + AST_LIST_UNLOCK(&client->messages); + ASTOBJ_UNREF(client, aji_client_destroy); + + return CLI_SUCCESS; +} + +/*! + * \brief creates aji_client structure. + * \param label + * \param var ast_variable + * \param debug + * \return 0. + */ +static int aji_create_client(char *label, struct ast_variable *var, int debug) +{ + char *resource; + struct aji_client *client = NULL; + int flag = 0; + + client = ASTOBJ_CONTAINER_FIND(&clients,label); + if (!client) { + flag = 1; + client = ast_calloc(1, sizeof(*client)); + if (!client) { + ast_log(LOG_ERROR, "Out of memory!\n"); + return 0; + } + ASTOBJ_INIT(client); + ASTOBJ_WRLOCK(client); + ASTOBJ_CONTAINER_INIT(&client->buddies); + } else { + ASTOBJ_WRLOCK(client); + ASTOBJ_UNMARK(client); + } + ASTOBJ_CONTAINER_MARKALL(&client->buddies); + ast_copy_string(client->name, label, sizeof(client->name)); + ast_copy_string(client->mid, "aaaaa", sizeof(client->mid)); + + /* Set default values for the client object */ + client->debug = debug; + ast_copy_flags(client, &globalflags, AST_FLAGS_ALL); + client->port = 5222; + client->usetls = 1; + client->usesasl = 1; + client->forcessl = 0; + client->keepalive = 1; + client->timeout = 50; + client->message_timeout = 100; + AST_LIST_HEAD_INIT(&client->messages); + client->component = 0; + ast_copy_string(client->statusmessage, "Online and Available", sizeof(client->statusmessage)); + client->priority = 0; + client->status = IKS_SHOW_AVAILABLE; + + if (flag) { + client->authorized = 0; + client->state = AJI_DISCONNECTED; + } + while (var) { + if (!strcasecmp(var->name, "username")) + ast_copy_string(client->user, var->value, sizeof(client->user)); + else if (!strcasecmp(var->name, "serverhost")) + ast_copy_string(client->serverhost, var->value, sizeof(client->serverhost)); + else if (!strcasecmp(var->name, "secret")) + ast_copy_string(client->password, var->value, sizeof(client->password)); + else if (!strcasecmp(var->name, "statusmessage")) + ast_copy_string(client->statusmessage, var->value, sizeof(client->statusmessage)); + else if (!strcasecmp(var->name, "port")) + client->port = atoi(var->value); + else if (!strcasecmp(var->name, "timeout")) + client->message_timeout = atoi(var->value); + else if (!strcasecmp(var->name, "debug")) + client->debug = (ast_false(var->value)) ? 0 : 1; + else if (!strcasecmp(var->name, "type")) { + if (!strcasecmp(var->value, "component")) + client->component = 1; + } else if (!strcasecmp(var->name, "usetls")) { + client->usetls = (ast_false(var->value)) ? 0 : 1; + } else if (!strcasecmp(var->name, "usesasl")) { + client->usesasl = (ast_false(var->value)) ? 0 : 1; + } else if (!strcasecmp(var->name, "forceoldssl")) + client->forcessl = (ast_false(var->value)) ? 0 : 1; + else if (!strcasecmp(var->name, "keepalive")) + client->keepalive = (ast_false(var->value)) ? 0 : 1; + else if (!strcasecmp(var->name, "autoprune")) + ast_set2_flag(client, ast_true(var->value), AJI_AUTOPRUNE); + else if (!strcasecmp(var->name, "autoregister")) + ast_set2_flag(client, ast_true(var->value), AJI_AUTOREGISTER); + else if (!strcasecmp(var->name, "buddy")) + aji_create_buddy((char *)var->value, client); + else if (!strcasecmp(var->name, "priority")) + client->priority = atoi(var->value); + else if (!strcasecmp(var->name, "status")) { + if (!strcasecmp(var->value, "unavailable")) + client->status = IKS_SHOW_UNAVAILABLE; + else + if (!strcasecmp(var->value, "available") + || !strcasecmp(var->value, "online")) + client->status = IKS_SHOW_AVAILABLE; + else + if (!strcasecmp(var->value, "chat") + || !strcasecmp(var->value, "chatty")) + client->status = IKS_SHOW_CHAT; + else + if (!strcasecmp(var->value, "away")) + client->status = IKS_SHOW_AWAY; + else + if (!strcasecmp(var->value, "xa") + || !strcasecmp(var->value, "xaway")) + client->status = IKS_SHOW_XA; + else + if (!strcasecmp(var->value, "dnd")) + client->status = IKS_SHOW_DND; + else + if (!strcasecmp(var->value, "invisible")) + #ifdef IKS_SHOW_INVISIBLE + client->status = IKS_SHOW_INVISIBLE; + #else + { + ast_log(LOG_WARNING, "Your iksemel doesn't support invisible status: falling back to DND\n"); + client->status = IKS_SHOW_DND; + } + #endif + else + ast_log(LOG_WARNING, "Unknown presence status: %s\n", var->value); + } + /* no transport support in this version */ + /* else if (!strcasecmp(var->name, "transport")) + aji_create_transport(var->value, client); + */ + var = var->next; + } + if (!flag) { + ASTOBJ_UNLOCK(client); + ASTOBJ_UNREF(client, aji_client_destroy); + return 1; + } + + ast_copy_string(client->name_space, (client->component) ? "jabber:component:accept" : "jabber:client", sizeof(client->name_space)); + client->p = iks_stream_new(client->name_space, client, aji_act_hook); + if (!client->p) { + ast_log(LOG_ERROR, "Failed to create stream for client '%s'!\n", client->name); + return 0; + } + client->stack = iks_stack_new(8192, 8192); + if (!client->stack) { + ast_log(LOG_ERROR, "Failed to allocate stack for client '%s'\n", client->name); + return 0; + } + client->f = iks_filter_new(); + if (!client->f) { + ast_log(LOG_ERROR, "Failed to create filter for client '%s'\n", client->name); + return 0; + } + if (!strchr(client->user, '/') && !client->component) { /*client */ + resource = NULL; + asprintf(&resource, "%s/asterisk", client->user); + if (resource) { + client->jid = iks_id_new(client->stack, resource); + ast_free(resource); + } + } else + client->jid = iks_id_new(client->stack, client->user); + if (client->component) { + iks_filter_add_rule(client->f, aji_dinfo_handler, client, IKS_RULE_NS, "http://jabber.org/protocol/disco#info", IKS_RULE_DONE); + iks_filter_add_rule(client->f, aji_ditems_handler, client, IKS_RULE_NS, "http://jabber.org/protocol/disco#items", IKS_RULE_DONE); + iks_filter_add_rule(client->f, aji_register_query_handler, client, IKS_RULE_SUBTYPE, IKS_TYPE_GET, IKS_RULE_NS, "jabber:iq:register", IKS_RULE_DONE); + iks_filter_add_rule(client->f, aji_register_approve_handler, client, IKS_RULE_SUBTYPE, IKS_TYPE_SET, IKS_RULE_NS, "jabber:iq:register", IKS_RULE_DONE); + } else { + iks_filter_add_rule(client->f, aji_client_info_handler, client, IKS_RULE_NS, "http://jabber.org/protocol/disco#info", IKS_RULE_DONE); + } + if (!strchr(client->user, '/') && !client->component) { /*client */ + resource = NULL; + asprintf(&resource, "%s/asterisk", client->user); + if (resource) { + client->jid = iks_id_new(client->stack, resource); + ast_free(resource); + } + } else + client->jid = iks_id_new(client->stack, client->user); + iks_set_log_hook(client->p, aji_log_hook); + ASTOBJ_UNLOCK(client); + ASTOBJ_CONTAINER_LINK(&clients,client); + return 1; +} + +#if 0 +/*! + * \brief creates transport. + * \param label, buddy to dump it into. + * \return 0. + */ +/* no connecting to transports today */ +static int aji_create_transport(char *label, struct aji_client *client) +{ + char *server = NULL, *buddyname = NULL, *user = NULL, *pass = NULL; + struct aji_buddy *buddy = NULL; + + buddy = ASTOBJ_CONTAINER_FIND(&client->buddies,label); + if (!buddy) { + buddy = ast_calloc(1, sizeof(*buddy)); + if(!buddy) { + ast_log(LOG_WARNING, "Out of memory\n"); + return 0; + } + ASTOBJ_INIT(buddy); + } + ASTOBJ_WRLOCK(buddy); + server = label; + if ((buddyname = strchr(label, ','))) { + *buddyname = '\0'; + buddyname++; + if (buddyname && buddyname[0] != '\0') { + if ((user = strchr(buddyname, ','))) { + *user = '\0'; + user++; + if (user && user[0] != '\0') { + if ((pass = strchr(user, ','))) { + *pass = '\0'; + pass++; + ast_copy_string(buddy->pass, pass, sizeof(buddy->pass)); + ast_copy_string(buddy->user, user, sizeof(buddy->user)); + ast_copy_string(buddy->name, buddyname, sizeof(buddy->name)); + ast_copy_string(buddy->server, server, sizeof(buddy->server)); + return 1; + } + } + } + } + } + ASTOBJ_UNLOCK(buddy); + ASTOBJ_UNMARK(buddy); + ASTOBJ_CONTAINER_LINK(&client->buddies, buddy); + return 0; +} +#endif + +/*! + * \brief creates buddy. + * \param label char. + * \param client the configured XMPP client we use to connect to a XMPP server + * \return 1 on success, 0 on failure. + */ +static int aji_create_buddy(char *label, struct aji_client *client) +{ + struct aji_buddy *buddy = NULL; + int flag = 0; + buddy = ASTOBJ_CONTAINER_FIND(&client->buddies,label); + if (!buddy) { + flag = 1; + buddy = ast_calloc(1, sizeof(*buddy)); + if(!buddy) { + ast_log(LOG_WARNING, "Out of memory\n"); + return 0; + } + ASTOBJ_INIT(buddy); + } + ASTOBJ_WRLOCK(buddy); + ast_copy_string(buddy->name, label, sizeof(buddy->name)); + ASTOBJ_UNLOCK(buddy); + if(flag) + ASTOBJ_CONTAINER_LINK(&client->buddies, buddy); + else { + ASTOBJ_UNMARK(buddy); + ASTOBJ_UNREF(buddy, aji_buddy_destroy); + } + return 1; +} + +/*!< load config file. \return 1. */ +static int aji_load_config(int reload) +{ + char *cat = NULL; + int debug = 1; + struct ast_config *cfg = NULL; + struct ast_variable *var = NULL; + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + + if ((cfg = ast_config_load(JABBER_CONFIG, config_flags)) == CONFIG_STATUS_FILEUNCHANGED) + return -1; + + /* Reset flags to default value */ + ast_set_flag(&globalflags, AJI_AUTOPRUNE | AJI_AUTOREGISTER); + + if (!cfg) { + ast_log(LOG_WARNING, "No such configuration file %s\n", JABBER_CONFIG); + return 0; + } + + cat = ast_category_browse(cfg, NULL); + for (var = ast_variable_browse(cfg, "general"); var; var = var->next) { + if (!strcasecmp(var->name, "debug")) + debug = (ast_false(ast_variable_retrieve(cfg, "general", "debug"))) ? 0 : 1; + else if (!strcasecmp(var->name, "autoprune")) + ast_set2_flag(&globalflags, ast_true(var->value), AJI_AUTOPRUNE); + else if (!strcasecmp(var->name, "autoregister")) + ast_set2_flag(&globalflags, ast_true(var->value), AJI_AUTOREGISTER); + } + + while (cat) { + if (strcasecmp(cat, "general")) { + var = ast_variable_browse(cfg, cat); + aji_create_client(cat, var, debug); + } + cat = ast_category_browse(cfg, cat); + } + ast_config_destroy(cfg); /* or leak memory */ + return 1; +} + +/*! + * \brief grab a aji_client structure by label name. + * \param name label name + * \return aji_client. + */ +struct aji_client *ast_aji_get_client(const char *name) +{ + struct aji_client *client = NULL; + + client = ASTOBJ_CONTAINER_FIND(&clients, name); + if (!client && !strchr(name, '@')) + client = ASTOBJ_CONTAINER_FIND_FULL(&clients, name, user,,, strcasecmp); + return client; +} + +struct aji_client_container *ast_aji_get_clients(void) +{ + return &clients; +} + +static char mandescr_jabber_send[] = +"Description: Sends a message to a Jabber Client.\n" +"Variables: \n" +" Jabber: Client or transport Asterisk uses to connect to JABBER.\n" +" ScreenName: User Name to message.\n" +" Message: Message to be sent to the buddy\n"; + +/*! + * \brief Send a Jabber Message via call from the Manager + * \param s mansession Manager session + * \param m message Message to send + * \return 0 +*/ +static int manager_jabber_send(struct mansession *s, const struct message *m) +{ + struct aji_client *client = NULL; + const char *id = astman_get_header(m,"ActionID"); + const char *jabber = astman_get_header(m,"Jabber"); + const char *screenname = astman_get_header(m,"ScreenName"); + const char *message = astman_get_header(m,"Message"); + + if (ast_strlen_zero(jabber)) { + astman_send_error(s, m, "No transport specified"); + return 0; + } + if (ast_strlen_zero(screenname)) { + astman_send_error(s, m, "No ScreenName specified"); + return 0; + } + if (ast_strlen_zero(message)) { + astman_send_error(s, m, "No Message specified"); + return 0; + } + + astman_send_ack(s, m, "Attempting to send Jabber Message"); + client = ast_aji_get_client(jabber); + if (!client) { + astman_send_error(s, m, "Could not find Sender"); + return 0; + } + if (strchr(screenname, '@') && message){ + ast_aji_send_chat(client, screenname, message); + astman_append(s, "Response: Success\r\n"); + if (!ast_strlen_zero(id)) + astman_append(s, "ActionID: %s\r\n",id); + return 0; + } + astman_append(s, "Response: Error\r\n"); + if (!ast_strlen_zero(id)) + astman_append(s, "ActionID: %s\r\n",id); + return 0; +} + +/*! \brief Reload the jabber module */ +static int aji_reload(int reload) +{ + int res; + + ASTOBJ_CONTAINER_MARKALL(&clients); + if (!(res = aji_load_config(reload))) { + ast_log(LOG_ERROR, "JABBER: Failed to load config.\n"); + return 0; + } else if (res == -1) + return 1; + + ASTOBJ_CONTAINER_PRUNE_MARKED(&clients, aji_client_destroy); + ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, { + ASTOBJ_RDLOCK(iterator); + if(iterator->state == AJI_DISCONNECTED) { + if (!iterator->thread) + ast_pthread_create_background(&iterator->thread, NULL, aji_recv_loop, iterator); + } else if (iterator->state == AJI_CONNECTING) + aji_get_roster(iterator); + ASTOBJ_UNLOCK(iterator); + }); + + return 1; +} + +/*! \brief Unload the jabber module */ +static int unload_module(void) +{ + + ast_cli_unregister_multiple(aji_cli, sizeof(aji_cli) / sizeof(struct ast_cli_entry)); + ast_unregister_application(app_ajisend); + ast_unregister_application(app_ajistatus); + ast_manager_unregister("JabberSend"); + ast_custom_function_unregister(&jabberstatus_function); + + ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, { + ASTOBJ_RDLOCK(iterator); + ast_debug(3, "JABBER: Releasing and disconnecting client: %s\n", iterator->name); + iterator->state = AJI_DISCONNECTING; + ast_aji_disconnect(iterator); + pthread_join(iterator->thread, NULL); + ASTOBJ_UNLOCK(iterator); + }); + + ASTOBJ_CONTAINER_DESTROYALL(&clients, aji_client_destroy); + ASTOBJ_CONTAINER_DESTROY(&clients); + return 0; +} + +/*! \brief Unload the jabber module */ +static int load_module(void) +{ + ASTOBJ_CONTAINER_INIT(&clients); + if(!aji_reload(0)) + return AST_MODULE_LOAD_DECLINE; + ast_manager_register2("JabberSend", EVENT_FLAG_SYSTEM, manager_jabber_send, + "Sends a message to a Jabber Client", mandescr_jabber_send); + ast_register_application(app_ajisend, aji_send_exec, ajisend_synopsis, ajisend_descrip); + ast_register_application(app_ajistatus, aji_status_exec, ajistatus_synopsis, ajistatus_descrip); + ast_cli_register_multiple(aji_cli, sizeof(aji_cli) / sizeof(struct ast_cli_entry)); + ast_custom_function_register(&jabberstatus_function); + + return 0; +} + +/*! \brief Wrapper for aji_reload */ +static int reload(void) +{ + aji_reload(1); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "AJI - Asterisk Jabber Interface", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); diff --git a/trunk/res/res_limit.c b/trunk/res/res_limit.c new file mode 100644 index 000000000..28a5e1323 --- /dev/null +++ b/trunk/res/res_limit.c @@ -0,0 +1,216 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Resource limits + * + * Copyright (c) 2006 Tilghman Lesher. All rights reserved. + * + * Tilghman Lesher <res_limit_200607@the-tilghman.com> + * + * This code is released by the author with no restrictions on usage. + * + */ + +/*! \file + * + * \brief Resource limits + * + * \author Tilghman Lesher <res_limit_200607@the-tilghman.com> + */ + + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#define _XOPEN_SOURCE 600 + +#include <ctype.h> +#include <sys/time.h> +#include <sys/resource.h> +#include "asterisk/module.h" +#include "asterisk/cli.h" + +/* Find proper rlimit for virtual memory */ +#ifdef RLIMIT_AS +#define VMEM_DEF RLIMIT_AS +#else +#ifdef RLIMIT_VMEM +#define VMEM_DEF RLIMIT_VMEM +#endif +#endif + +static struct limits { + int resource; + char limit[3]; + char desc[40]; +} limits[] = { + { RLIMIT_CPU, "-t", "cpu time" }, + { RLIMIT_FSIZE, "-f", "file size" }, + { RLIMIT_DATA, "-d", "program data segment" }, + { RLIMIT_STACK, "-s", "program stack size" }, + { RLIMIT_CORE, "-c", "core file size" }, +#ifdef RLIMIT_RSS + { RLIMIT_RSS, "-m", "resident memory" }, + { RLIMIT_MEMLOCK, "-l", "amount of memory locked into RAM" }, +#endif +#ifdef RLIMIT_NPROC + { RLIMIT_NPROC, "-u", "number of processes" }, +#endif + { RLIMIT_NOFILE, "-n", "number of file descriptors" }, +#ifdef VMEM_DEF + { VMEM_DEF, "-v", "virtual memory" }, +#endif +}; + +static int str2limit(const char *string) +{ + size_t i; + for (i = 0; i < sizeof(limits) / sizeof(limits[0]); i++) { + if (!strcasecmp(string, limits[i].limit)) + return limits[i].resource; + } + return -1; +} + +static const char *str2desc(const char *string) +{ + size_t i; + for (i = 0; i < sizeof(limits) / sizeof(limits[0]); i++) { + if (!strcmp(string, limits[i].limit)) + return limits[i].desc; + } + return "<unknown>"; +} + +static char *complete_ulimit(struct ast_cli_args *a) +{ + int which = 0, i; + int wordlen = strlen(a->word); + + if (a->pos > 1) + return NULL; + for (i = 0; i < sizeof(limits) / sizeof(limits[0]); i++) { + if (!strncasecmp(limits[i].limit, a->word, wordlen)) { + if (++which > a->n) + return ast_strdup(limits[i].limit); + } + } + return NULL; +} + +static char *handle_cli_ulimit(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + int resource; + struct rlimit rlimit = { 0, 0 }; + + switch (cmd) { + case CLI_INIT: + e->command = "ulimit"; + e->usage = + "Usage: ulimit {-d|" +#ifdef RLIMIT_RSS + "-l|" +#endif + "-f|" +#ifdef RLIMIT_RSS + "-m|" +#endif + "-s|-t|" +#ifdef RLIMIT_NPROC + "-u|" +#endif +#ifdef VMEM_DEF + "-v|" +#endif + "-c|-n} [<num>]\n" + " Shows or sets the corresponding resource limit.\n" + " -d Process data segment [readonly]\n" +#ifdef RLIMIT_RSS + " -l Memory lock size [readonly]\n" +#endif + " -f File size\n" +#ifdef RLIMIT_RSS + " -m Process resident memory [readonly]\n" +#endif + " -s Process stack size [readonly]\n" + " -t CPU usage [readonly]\n" +#ifdef RLIMIT_NPROC + " -u Child processes\n" +#endif +#ifdef VMEM_DEF + " -v Process virtual memory [readonly]\n" +#endif + " -c Core dump file size\n" + " -n Number of file descriptors\n"; + return NULL; + case CLI_GENERATE: + return complete_ulimit(a); + } + + if (a->argc > 3) + return CLI_SHOWUSAGE; + + if (a->argc == 1) { + char arg2[3]; + char *newargv[2] = { "ulimit", arg2 }; + for (resource = 0; resource < sizeof(limits) / sizeof(limits[0]); resource++) { + struct ast_cli_args newArgs = { .argv = newargv, .argc = 2 }; + ast_copy_string(arg2, limits[resource].limit, sizeof(arg2)); + handle_cli_ulimit(e, CLI_HANDLER, &newArgs); + } + return CLI_SUCCESS; + } else { + resource = str2limit(a->argv[1]); + if (resource == -1) { + ast_cli(a->fd, "Unknown resource\n"); + return CLI_FAILURE; + } + + if (a->argc == 3) { + int x; +#ifdef RLIMIT_NPROC + if (resource != RLIMIT_NOFILE && resource != RLIMIT_CORE && resource != RLIMIT_NPROC && resource != RLIMIT_FSIZE) { +#else + if (resource != RLIMIT_NOFILE && resource != RLIMIT_CORE && resource != RLIMIT_FSIZE) { +#endif + ast_cli(a->fd, "Resource not permitted to be set\n"); + return CLI_FAILURE; + } + + sscanf(a->argv[2], "%d", &x); + rlimit.rlim_max = rlimit.rlim_cur = x; + setrlimit(resource, &rlimit); + return CLI_SUCCESS; + } else { + if (!getrlimit(resource, &rlimit)) { + char printlimit[32]; + const char *desc; + if (rlimit.rlim_max == RLIM_INFINITY) + ast_copy_string(printlimit, "effectively unlimited", sizeof(printlimit)); + else + snprintf(printlimit, sizeof(printlimit), "limited to %d", (int) rlimit.rlim_cur); + desc = str2desc(a->argv[1]); + ast_cli(a->fd, "%c%s (%s) is %s.\n", toupper(desc[0]), desc + 1, a->argv[1], printlimit); + } else + ast_cli(a->fd, "Could not retrieve resource limits for %s: %s\n", str2desc(a->argv[1]), strerror(errno)); + return CLI_SUCCESS; + } + } +} + +static struct ast_cli_entry cli_ulimit = + AST_CLI_DEFINE(handle_cli_ulimit, "Set or show process resource limits"); + +static int unload_module(void) +{ + return ast_cli_unregister(&cli_ulimit); +} + +static int load_module(void) +{ + return ast_cli_register(&cli_ulimit) ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Resource limits"); + diff --git a/trunk/res/res_monitor.c b/trunk/res/res_monitor.c new file mode 100644 index 000000000..3e4c099f4 --- /dev/null +++ b/trunk/res/res_monitor.c @@ -0,0 +1,765 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief PBX channel monitoring + * + * \author Mark Spencer <markster@digium.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <sys/stat.h> +#include <libgen.h> + +#include "asterisk/paths.h" /* use ast_config_AST_MONITOR_DIR */ +#include "asterisk/lock.h" +#include "asterisk/channel.h" +#include "asterisk/file.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/manager.h" +#include "asterisk/cli.h" +#include "asterisk/monitor.h" +#include "asterisk/app.h" +#include "asterisk/utils.h" +#include "asterisk/config.h" + +AST_MUTEX_DEFINE_STATIC(monitorlock); + +#define LOCK_IF_NEEDED(lock, needed) do { \ + if (needed) \ + ast_channel_lock(lock); \ + } while(0) + +#define UNLOCK_IF_NEEDED(lock, needed) do { \ + if (needed) \ + ast_channel_unlock(lock); \ + } while (0) + +static unsigned long seq = 0; + +static char *monitor_synopsis = "Monitor a channel"; + +static char *monitor_descrip = " Monitor([file_format[:urlbase],[fname_base],[options]]):\n" +"Used to start monitoring a channel. The channel's input and output\n" +"voice packets are logged to files until the channel hangs up or\n" +"monitoring is stopped by the StopMonitor application.\n" +" file_format optional, if not set, defaults to \"wav\"\n" +" fname_base if set, changes the filename used to the one specified.\n" +" options:\n" +" m - when the recording ends mix the two leg files into one and\n" +" delete the two leg files. If the variable MONITOR_EXEC is set, the\n" +" application referenced in it will be executed instead of\n" +#ifdef HAVE_SOXMIX +" soxmix and the raw leg files will NOT be deleted automatically.\n" +" soxmix or MONITOR_EXEC is handed 3 arguments, the two leg files\n" +#else +" sox and the raw leg files will NOT be deleted automatically.\n" +" sox or MONITOR_EXEC is handed 3 arguments, the two leg files\n" +#endif +" and a target mixed file name which is the same as the leg file names\n" +" only without the in/out designator.\n" +" If MONITOR_EXEC_ARGS is set, the contents will be passed on as\n" +" additional arguments to MONITOR_EXEC\n" +" Both MONITOR_EXEC and the Mix flag can be set from the\n" +" administrator interface\n" +"\n" +" b - Don't begin recording unless a call is bridged to another channel\n" +" i - Skip recording of input stream (disables m option)\n" +" o - Skip recording of output stream (disables m option)\n" +"\nReturns -1 if monitor files can't be opened or if the channel is already\n" +"monitored, otherwise 0.\n" +; + +static char *stopmonitor_synopsis = "Stop monitoring a channel"; + +static char *stopmonitor_descrip = " StopMonitor():\n" + "Stops monitoring a channel. Has no effect if the channel is not monitored\n"; + +static char *changemonitor_synopsis = "Change monitoring filename of a channel"; + +static char *changemonitor_descrip = " ChangeMonitor(filename_base):\n" + "Changes monitoring filename of a channel. Has no effect if the channel is not monitored.\n" + "The argument is the new filename base to use for monitoring this channel.\n"; + +static char *pausemonitor_synopsis = "Pause monitoring of a channel"; + +static char *pausemonitor_descrip = " PauseMonitor():\n" + "Pauses monitoring of a channel until it is re-enabled by a call to UnpauseMonitor.\n"; + +static char *unpausemonitor_synopsis = "Unpause monitoring of a channel"; + +static char *unpausemonitor_descrip = " UnpauseMonitor():\n" + "Unpauses monitoring of a channel on which monitoring had\n" + "previously been paused with PauseMonitor.\n"; + +/*! + * \brief Change state of monitored channel + * \param chan + * \param state monitor state + * \retval 0 on success. + * \retval -1 on failure. +*/ +static int ast_monitor_set_state(struct ast_channel *chan, int state) +{ + LOCK_IF_NEEDED(chan, 1); + if (!chan->monitor) { + UNLOCK_IF_NEEDED(chan, 1); + return -1; + } + chan->monitor->state = state; + UNLOCK_IF_NEEDED(chan, 1); + return 0; +} + +/*! \brief Start monitoring a channel + * \param chan ast_channel struct to record + * \param format_spec file format to use for recording + * \param fname_base filename base to record to + * \param need_lock whether to lock the channel mutex + * \param stream_action whether to record the input and/or output streams. X_REC_IN | X_REC_OUT is most often used + * Creates the file to record, if no format is specified it assumes WAV + * It also sets channel variable __MONITORED=yes + * \retval 0 on success + * \retval -1 on failure + */ +int ast_monitor_start( struct ast_channel *chan, const char *format_spec, + const char *fname_base, int need_lock, int stream_action) +{ + int res = 0; + + LOCK_IF_NEEDED(chan, need_lock); + + if (!(chan->monitor)) { + struct ast_channel_monitor *monitor; + char *channel_name, *p; + + /* Create monitoring directory if needed */ + ast_mkdir(ast_config_AST_MONITOR_DIR, 0777); + + if (!(monitor = ast_calloc(1, sizeof(*monitor)))) { + UNLOCK_IF_NEEDED(chan, need_lock); + return -1; + } + + /* Determine file names */ + if (!ast_strlen_zero(fname_base)) { + int directory = strchr(fname_base, '/') ? 1 : 0; + /* try creating the directory just in case it doesn't exist */ + if (directory) { + char *name = ast_strdupa(fname_base); + ast_mkdir(dirname(name), 0777); + } + snprintf(monitor->read_filename, FILENAME_MAX, "%s/%s-in", + directory ? "" : ast_config_AST_MONITOR_DIR, fname_base); + snprintf(monitor->write_filename, FILENAME_MAX, "%s/%s-out", + directory ? "" : ast_config_AST_MONITOR_DIR, fname_base); + ast_copy_string(monitor->filename_base, fname_base, sizeof(monitor->filename_base)); + } else { + ast_mutex_lock(&monitorlock); + snprintf(monitor->read_filename, FILENAME_MAX, "%s/audio-in-%ld", + ast_config_AST_MONITOR_DIR, seq); + snprintf(monitor->write_filename, FILENAME_MAX, "%s/audio-out-%ld", + ast_config_AST_MONITOR_DIR, seq); + seq++; + ast_mutex_unlock(&monitorlock); + + channel_name = ast_strdupa(chan->name); + while ((p = strchr(channel_name, '/'))) { + *p = '-'; + } + snprintf(monitor->filename_base, FILENAME_MAX, "%s/%d-%s", + ast_config_AST_MONITOR_DIR, (int)time(NULL), channel_name); + monitor->filename_changed = 1; + } + + monitor->stop = ast_monitor_stop; + + /* Determine file format */ + if (!ast_strlen_zero(format_spec)) { + monitor->format = ast_strdup(format_spec); + } else { + monitor->format = ast_strdup("wav"); + } + + /* open files */ + if (stream_action & X_REC_IN) { + if (ast_fileexists(monitor->read_filename, NULL, NULL) > 0) + ast_filedelete(monitor->read_filename, NULL); + if (!(monitor->read_stream = ast_writefile(monitor->read_filename, + monitor->format, NULL, + O_CREAT|O_TRUNC|O_WRONLY, 0, AST_FILE_MODE))) { + ast_log(LOG_WARNING, "Could not create file %s\n", + monitor->read_filename); + ast_free(monitor); + UNLOCK_IF_NEEDED(chan, need_lock); + return -1; + } + } else + monitor->read_stream = NULL; + + if (stream_action & X_REC_OUT) { + if (ast_fileexists(monitor->write_filename, NULL, NULL) > 0) { + ast_filedelete(monitor->write_filename, NULL); + } + if (!(monitor->write_stream = ast_writefile(monitor->write_filename, + monitor->format, NULL, + O_CREAT|O_TRUNC|O_WRONLY, 0, AST_FILE_MODE))) { + ast_log(LOG_WARNING, "Could not create file %s\n", + monitor->write_filename); + ast_closestream(monitor->read_stream); + ast_free(monitor); + UNLOCK_IF_NEEDED(chan, need_lock); + return -1; + } + } else + monitor->write_stream = NULL; + + chan->monitor = monitor; + ast_monitor_set_state(chan, AST_MONITOR_RUNNING); + /* so we know this call has been monitored in case we need to bill for it or something */ + pbx_builtin_setvar_helper(chan, "__MONITORED","true"); + + manager_event(EVENT_FLAG_CALL, "MonitorStart", + "Channel: %s\r\n" + "Uniqueid: %s\r\n", + chan->name, + chan->uniqueid + ); + } else { + ast_debug(1,"Cannot start monitoring %s, already monitored\n", chan->name); + res = -1; + } + + UNLOCK_IF_NEEDED(chan, need_lock); + + return res; +} + +/*! + * \brief Get audio format. + * \param format recording format. + * The file format extensions that Asterisk uses are not all the same as that + * which soxmix expects. This function ensures that the format used as the + * extension on the filename is something soxmix will understand. + */ +static const char *get_soxmix_format(const char *format) +{ + const char *res = format; + + if (!strcasecmp(format,"ulaw")) + res = "ul"; + if (!strcasecmp(format,"alaw")) + res = "al"; + + return res; +} + +/*! + * \brief Stop monitoring channel + * \param chan + * \param need_lock + * Stop the recording, close any open streams, mix in/out channels if required + * \return Always 0 +*/ +int ast_monitor_stop(struct ast_channel *chan, int need_lock) +{ + int delfiles = 0; + + LOCK_IF_NEEDED(chan, need_lock); + + if (chan->monitor) { + char filename[ FILENAME_MAX ]; + + if (chan->monitor->read_stream) { + ast_closestream(chan->monitor->read_stream); + } + if (chan->monitor->write_stream) { + ast_closestream(chan->monitor->write_stream); + } + + if (chan->monitor->filename_changed && !ast_strlen_zero(chan->monitor->filename_base)) { + if (ast_fileexists(chan->monitor->read_filename,NULL,NULL) > 0) { + snprintf(filename, FILENAME_MAX, "%s-in", chan->monitor->filename_base); + if (ast_fileexists(filename, NULL, NULL) > 0) { + ast_filedelete(filename, NULL); + } + ast_filerename(chan->monitor->read_filename, filename, chan->monitor->format); + } else { + ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->read_filename); + } + + if (ast_fileexists(chan->monitor->write_filename,NULL,NULL) > 0) { + snprintf(filename, FILENAME_MAX, "%s-out", chan->monitor->filename_base); + if (ast_fileexists(filename, NULL, NULL) > 0) { + ast_filedelete(filename, NULL); + } + ast_filerename(chan->monitor->write_filename, filename, chan->monitor->format); + } else { + ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->write_filename); + } + } + + if (chan->monitor->joinfiles && !ast_strlen_zero(chan->monitor->filename_base)) { + char tmp[1024]; + char tmp2[1024]; + const char *format = !strcasecmp(chan->monitor->format,"wav49") ? "WAV" : chan->monitor->format; + char *name = chan->monitor->filename_base; + int directory = strchr(name, '/') ? 1 : 0; + const char *dir = directory ? "" : ast_config_AST_MONITOR_DIR; + const char *execute, *execute_args; + + /* Set the execute application */ + execute = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC"); + if (ast_strlen_zero(execute)) { +#ifdef HAVE_SOXMIX + execute = "nice -n 19 soxmix"; +#else + execute = "nice -n 19 sox -m"; +#endif + format = get_soxmix_format(format); + delfiles = 1; + } + execute_args = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC_ARGS"); + if (ast_strlen_zero(execute_args)) { + execute_args = ""; + } + + snprintf(tmp, sizeof(tmp), "%s \"%s/%s-in.%s\" \"%s/%s-out.%s\" \"%s/%s.%s\" %s &", execute, dir, name, format, dir, name, format, dir, name, format,execute_args); + if (delfiles) { + snprintf(tmp2,sizeof(tmp2), "( %s& rm -f \"%s/%s-\"* ) &",tmp, dir ,name); /* remove legs when done mixing */ + ast_copy_string(tmp, tmp2, sizeof(tmp)); + } + ast_debug(1,"monitor executing %s\n",tmp); + if (ast_safe_system(tmp) == -1) + ast_log(LOG_WARNING, "Execute of %s failed.\n",tmp); + } + + ast_free(chan->monitor->format); + ast_free(chan->monitor); + chan->monitor = NULL; + + manager_event(EVENT_FLAG_CALL, "MonitorStop", + "Channel: %s\r\n" + "Uniqueid: %s\r\n", + chan->name, + chan->uniqueid + ); + } + + UNLOCK_IF_NEEDED(chan, need_lock); + + return 0; +} + + +/*! \brief Pause monitoring of channel */ +int ast_monitor_pause(struct ast_channel *chan) +{ + return ast_monitor_set_state(chan, AST_MONITOR_PAUSED); +} + +/*! \brief Unpause monitoring of channel */ +int ast_monitor_unpause(struct ast_channel *chan) +{ + return ast_monitor_set_state(chan, AST_MONITOR_RUNNING); +} + +/*! \brief Wrapper for ast_monitor_pause */ +static int pause_monitor_exec(struct ast_channel *chan, void *data) +{ + return ast_monitor_pause(chan); +} + +/*! \brief Wrapper for ast_monitor_unpause */ +static int unpause_monitor_exec(struct ast_channel *chan, void *data) +{ + return ast_monitor_unpause(chan); +} + +/*! + * \brief Change monitored filename of channel + * \param chan + * \param fname_base new filename + * \param need_lock + * \retval 0 on success. + * \retval -1 on failure. +*/ +int ast_monitor_change_fname(struct ast_channel *chan, const char *fname_base, int need_lock) +{ + if (ast_strlen_zero(fname_base)) { + ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to null\n", chan->name); + return -1; + } + + LOCK_IF_NEEDED(chan, need_lock); + + if (chan->monitor) { + int directory = strchr(fname_base, '/') ? 1 : 0; + /* try creating the directory just in case it doesn't exist */ + if (directory) { + char *name = ast_strdupa(fname_base); + ast_mkdir(dirname(name), 0777); + } + + snprintf(chan->monitor->filename_base, FILENAME_MAX, "%s/%s", directory ? "" : ast_config_AST_MONITOR_DIR, fname_base); + chan->monitor->filename_changed = 1; + } else { + ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to %s, monitoring not started\n", chan->name, fname_base); + } + + UNLOCK_IF_NEEDED(chan, need_lock); + + return 0; +} + + +/*! + * \brief Start monitor + * \param chan + * \param data arguments passed fname|options + * \retval 0 on success. + * \retval -1 on failure. +*/ +static int start_monitor_exec(struct ast_channel *chan, void *data) +{ + char *arg = NULL; + char *options = NULL; + char *delay = NULL; + char *urlprefix = NULL; + char tmp[256]; + int stream_action = X_REC_IN | X_REC_OUT; + int joinfiles = 0; + int waitforbridge = 0; + int res = 0; + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(format); + AST_APP_ARG(fname_base); + AST_APP_ARG(options); + ); + + /* Parse arguments. */ + if (ast_strlen_zero((char*)data)) { + ast_log(LOG_ERROR, "Monitor requires an argument\n"); + return 0; + } + + parse = ast_strdupa((char*)data); + AST_STANDARD_APP_ARGS(args, parse); + + if (!ast_strlen_zero(args.options)) { + if (strchr(args.options, 'm')) + stream_action |= X_JOIN; + if (strchr(args.options, 'b')) + waitforbridge = 1; + if (strchr(args.options, 'i')) + stream_action &= ~X_REC_IN; + if (strchr(args.options, 'o')) + stream_action &= ~X_REC_OUT; + } + + arg = strchr(args.format, ':'); + if (arg) { + *arg++ = 0; + urlprefix = arg; + } + + if (urlprefix) { + snprintf(tmp, sizeof(tmp), "%s/%s.%s", urlprefix, args.fname_base, + ((strcmp(args.format, "gsm")) ? "wav" : "gsm")); + if (!chan->cdr && !(chan->cdr = ast_cdr_alloc())) + return -1; + ast_cdr_setuserfield(chan, tmp); + } + if (waitforbridge) { + /* We must remove the "b" option if listed. In principle none of + the following could give NULL results, but we check just to + be pedantic. Reconstructing with checks for 'm' option does not + work if we end up adding more options than 'm' in the future. */ + delay = ast_strdupa(data); + options = strrchr(delay, '|'); + if (options) { + arg = strchr(options, 'b'); + if (arg) { + *arg = 'X'; + pbx_builtin_setvar_helper(chan,"AUTO_MONITOR", delay); + } + } + return 0; + } + + res = ast_monitor_start(chan, args.format, args.fname_base, 1, stream_action); + if (res < 0) + res = ast_monitor_change_fname(chan, args.fname_base, 1); + + if (stream_action & X_JOIN) { + if ((stream_action & X_REC_IN) && (stream_action & X_REC_OUT)) + joinfiles = 1; + else + ast_log(LOG_WARNING, "Won't mix streams unless both input and output streams are recorded\n"); + } + ast_monitor_setjoinfiles(chan, joinfiles); + + return res; +} + +/*! \brief Wrapper function \see ast_monitor_stop */ +static int stop_monitor_exec(struct ast_channel *chan, void *data) +{ + return ast_monitor_stop(chan, 1); +} + +/*! \brief Wrapper function \see ast_monitor_change_fname */ +static int change_monitor_exec(struct ast_channel *chan, void *data) +{ + return ast_monitor_change_fname(chan, (const char*)data, 1); +} + +static char start_monitor_action_help[] = +"Description: The 'Monitor' action may be used to record the audio on a\n" +" specified channel. The following parameters may be used to control\n" +" this:\n" +" Channel - Required. Used to specify the channel to record.\n" +" File - Optional. Is the name of the file created in the\n" +" monitor spool directory. Defaults to the same name\n" +" as the channel (with slashes replaced with dashes).\n" +" Format - Optional. Is the audio recording format. Defaults\n" +" to \"wav\".\n" +" Mix - Optional. Boolean parameter as to whether to mix\n" +" the input and output channels together after the\n" +" recording is finished.\n"; + +/*! \brief Start monitoring a channel by manager connection */ +static int start_monitor_action(struct mansession *s, const struct message *m) +{ + struct ast_channel *c = NULL; + const char *name = astman_get_header(m, "Channel"); + const char *fname = astman_get_header(m, "File"); + const char *format = astman_get_header(m, "Format"); + const char *mix = astman_get_header(m, "Mix"); + char *d; + + if (ast_strlen_zero(name)) { + astman_send_error(s, m, "No channel specified"); + return 0; + } + c = ast_get_channel_by_name_locked(name); + if (!c) { + astman_send_error(s, m, "No such channel"); + return 0; + } + + if (ast_strlen_zero(fname)) { + /* No filename base specified, default to channel name as per CLI */ + if (!(fname = ast_strdup(c->name))) { + astman_send_error(s, m, "Could not start monitoring channel"); + ast_channel_unlock(c); + return 0; + } + /* Channels have the format technology/channel_name - have to replace that / */ + if ((d = strchr(fname, '/'))) + *d = '-'; + } + + if (ast_monitor_start(c, format, fname, 1, X_REC_IN | X_REC_OUT)) { + if (ast_monitor_change_fname(c, fname, 1)) { + astman_send_error(s, m, "Could not start monitoring channel"); + ast_channel_unlock(c); + return 0; + } + } + + if (ast_true(mix)) { + ast_monitor_setjoinfiles(c, 1); + } + + ast_channel_unlock(c); + astman_send_ack(s, m, "Started monitoring channel"); + return 0; +} + +static char stop_monitor_action_help[] = +"Description: The 'StopMonitor' action may be used to end a previously\n" +" started 'Monitor' action. The only parameter is 'Channel', the name\n" +" of the channel monitored.\n"; + +/*! \brief Stop monitoring a channel by manager connection */ +static int stop_monitor_action(struct mansession *s, const struct message *m) +{ + struct ast_channel *c = NULL; + const char *name = astman_get_header(m, "Channel"); + int res; + if (ast_strlen_zero(name)) { + astman_send_error(s, m, "No channel specified"); + return 0; + } + c = ast_get_channel_by_name_locked(name); + if (!c) { + astman_send_error(s, m, "No such channel"); + return 0; + } + res = ast_monitor_stop(c, 1); + ast_channel_unlock(c); + if (res) { + astman_send_error(s, m, "Could not stop monitoring channel"); + return 0; + } + astman_send_ack(s, m, "Stopped monitoring channel"); + return 0; +} + +static char change_monitor_action_help[] = +"Description: The 'ChangeMonitor' action may be used to change the file\n" +" started by a previous 'Monitor' action. The following parameters may\n" +" be used to control this:\n" +" Channel - Required. Used to specify the channel to record.\n" +" File - Required. Is the new name of the file created in the\n" +" monitor spool directory.\n"; + +/*! \brief Change filename of a monitored channel by manager connection */ +static int change_monitor_action(struct mansession *s, const struct message *m) +{ + struct ast_channel *c = NULL; + const char *name = astman_get_header(m, "Channel"); + const char *fname = astman_get_header(m, "File"); + if (ast_strlen_zero(name)) { + astman_send_error(s, m, "No channel specified"); + return 0; + } + if (ast_strlen_zero(fname)) { + astman_send_error(s, m, "No filename specified"); + return 0; + } + c = ast_get_channel_by_name_locked(name); + if (!c) { + astman_send_error(s, m, "No such channel"); + return 0; + } + if (ast_monitor_change_fname(c, fname, 1)) { + astman_send_error(s, m, "Could not change monitored filename of channel"); + ast_channel_unlock(c); + return 0; + } + ast_channel_unlock(c); + astman_send_ack(s, m, "Changed monitor filename"); + return 0; +} + +void ast_monitor_setjoinfiles(struct ast_channel *chan, int turnon) +{ + if (chan->monitor) + chan->monitor->joinfiles = turnon; +} + +enum MONITOR_PAUSING_ACTION +{ + MONITOR_ACTION_PAUSE, + MONITOR_ACTION_UNPAUSE +}; + +static int do_pause_or_unpause(struct mansession *s, const struct message *m, int action) +{ + struct ast_channel *c = NULL; + const char *name = astman_get_header(m, "Channel"); + + if (ast_strlen_zero(name)) { + astman_send_error(s, m, "No channel specified"); + return -1; + } + + c = ast_get_channel_by_name_locked(name); + if (!c) { + astman_send_error(s, m, "No such channel"); + return -1; + } + + if (action == MONITOR_ACTION_PAUSE) + ast_monitor_pause(c); + else + ast_monitor_unpause(c); + + ast_channel_unlock(c); + astman_send_ack(s, m, (action == MONITOR_ACTION_PAUSE ? "Paused monitoring of the channel" : "Unpaused monitoring of the channel")); + return 0; +} + +static char pause_monitor_action_help[] = + "Description: The 'PauseMonitor' action may be used to temporarily stop the\n" + " recording of a channel. The following parameters may\n" + " be used to control this:\n" + " Channel - Required. Used to specify the channel to record.\n"; + +static int pause_monitor_action(struct mansession *s, const struct message *m) +{ + return do_pause_or_unpause(s, m, MONITOR_ACTION_PAUSE); +} + +static char unpause_monitor_action_help[] = + "Description: The 'UnpauseMonitor' action may be used to re-enable recording\n" + " of a channel after calling PauseMonitor. The following parameters may\n" + " be used to control this:\n" + " Channel - Required. Used to specify the channel to record.\n"; + +static int unpause_monitor_action(struct mansession *s, const struct message *m) +{ + return do_pause_or_unpause(s, m, MONITOR_ACTION_UNPAUSE); +} + + +static int load_module(void) +{ + ast_register_application("Monitor", start_monitor_exec, monitor_synopsis, monitor_descrip); + ast_register_application("StopMonitor", stop_monitor_exec, stopmonitor_synopsis, stopmonitor_descrip); + ast_register_application("ChangeMonitor", change_monitor_exec, changemonitor_synopsis, changemonitor_descrip); + ast_register_application("PauseMonitor", pause_monitor_exec, pausemonitor_synopsis, pausemonitor_descrip); + ast_register_application("UnpauseMonitor", unpause_monitor_exec, unpausemonitor_synopsis, unpausemonitor_descrip); + ast_manager_register2("Monitor", EVENT_FLAG_CALL, start_monitor_action, monitor_synopsis, start_monitor_action_help); + ast_manager_register2("StopMonitor", EVENT_FLAG_CALL, stop_monitor_action, stopmonitor_synopsis, stop_monitor_action_help); + ast_manager_register2("ChangeMonitor", EVENT_FLAG_CALL, change_monitor_action, changemonitor_synopsis, change_monitor_action_help); + ast_manager_register2("PauseMonitor", EVENT_FLAG_CALL, pause_monitor_action, pausemonitor_synopsis, pause_monitor_action_help); + ast_manager_register2("UnpauseMonitor", EVENT_FLAG_CALL, unpause_monitor_action, unpausemonitor_synopsis, unpause_monitor_action_help); + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_unregister_application("Monitor"); + ast_unregister_application("StopMonitor"); + ast_unregister_application("ChangeMonitor"); + ast_unregister_application("PauseMonitor"); + ast_unregister_application("UnpauseMonitor"); + ast_manager_unregister("Monitor"); + ast_manager_unregister("StopMonitor"); + ast_manager_unregister("ChangeMonitor"); + ast_manager_unregister("PauseMonitor"); + ast_manager_unregister("UnpauseMonitor"); + + return 0; +} + +/* usecount semantics need to be defined */ +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Call Monitoring Resource", + .load = load_module, + .unload = unload_module, + ); diff --git a/trunk/res/res_musiconhold.c b/trunk/res/res_musiconhold.c new file mode 100644 index 000000000..b4c5edfbb --- /dev/null +++ b/trunk/res/res_musiconhold.c @@ -0,0 +1,1566 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2006, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Routines implementing music on hold + * + * \arg See also \ref Config_moh + * + * \author Mark Spencer <markster@digium.com> + */ + +/*** MODULEINFO + <conflict>win32</conflict> + <use>zaptel</use> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <ctype.h> +#include <signal.h> +#include <sys/time.h> +#include <sys/signal.h> +#include <netinet/in.h> +#include <sys/stat.h> +#include <dirent.h> +#include <sys/ioctl.h> +#ifdef SOLARIS +#include <thread.h> +#endif + +#include "asterisk/zapata.h" + +#include "asterisk/lock.h" +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/translate.h" +#include "asterisk/say.h" +#include "asterisk/musiconhold.h" +#include "asterisk/config.h" +#include "asterisk/utils.h" +#include "asterisk/cli.h" +#include "asterisk/stringfields.h" +#include "asterisk/linkedlists.h" + +#define INITIAL_NUM_FILES 8 + +static char *play_moh = "MusicOnHold"; +static char *wait_moh = "WaitMusicOnHold"; +static char *set_moh = "SetMusicOnHold"; +static char *start_moh = "StartMusicOnHold"; +static char *stop_moh = "StopMusicOnHold"; + +static char *play_moh_syn = "Play Music On Hold indefinitely"; +static char *wait_moh_syn = "Wait, playing Music On Hold"; +static char *set_moh_syn = "Set default Music On Hold class"; +static char *start_moh_syn = "Play Music On Hold"; +static char *stop_moh_syn = "Stop Playing Music On Hold"; + +static char *play_moh_desc = " MusicOnHold(class):\n" +"Plays hold music specified by class. If omitted, the default\n" +"music source for the channel will be used. Set the default \n" +"class with the SetMusicOnHold() application.\n" +"Returns -1 on hangup.\n" +"Never returns otherwise.\n"; + +static char *wait_moh_desc = " WaitMusicOnHold(delay):\n" +"Plays hold music specified number of seconds. Returns 0 when\n" +"done, or -1 on hangup. If no hold music is available, the delay will\n" +"still occur with no sound.\n"; + +static char *set_moh_desc = " SetMusicOnHold(class):\n" +"Sets the default class for music on hold for a given channel. When\n" +"music on hold is activated, this class will be used to select which\n" +"music is played.\n"; + +static char *start_moh_desc = " StartMusicOnHold(class):\n" +"Starts playing music on hold, uses default music class for channel.\n" +"Starts playing music specified by class. If omitted, the default\n" +"music source for the channel will be used. Always returns 0.\n"; + +static char *stop_moh_desc = " StopMusicOnHold(): " +"Stops playing music on hold.\n"; + +static int respawn_time = 20; + +struct moh_files_state { + struct mohclass *class; + int origwfmt; + int samples; + int sample_queue; + int pos; + int save_pos; +}; + +#define MOH_QUIET (1 << 0) +#define MOH_SINGLE (1 << 1) +#define MOH_CUSTOM (1 << 2) +#define MOH_RANDOMIZE (1 << 3) +#define MOH_SORTALPHA (1 << 4) + +#define MOH_CACHERTCLASSES (1 << 5) /*!< Should we use a separate instance of MOH for each user or not */ + +static struct ast_flags global_flags[1] = {{0}}; /*!< global MOH_ flags */ + +struct mohclass { + char name[MAX_MUSICCLASS]; + char dir[256]; + char args[256]; + char mode[80]; + char digit; + /*! A dynamically sized array to hold the list of filenames in "files" mode */ + char **filearray; + /*! The current size of the filearray */ + int allowed_files; + /*! The current number of files loaded into the filearray */ + int total_files; + unsigned int flags; + /*! The format from the MOH source, not applicable to "files" mode */ + int format; + /*! The pid of the external application delivering MOH */ + int pid; + time_t start; + pthread_t thread; + /*! Source of audio */ + int srcfd; + /*! FD for timing source */ + int pseudofd; + /*! Number of users */ + int inuse; + /*! Created on the fly, from RT engine */ + int realtime; + unsigned int delete:1; + AST_LIST_HEAD_NOLOCK(, mohdata) members; + AST_LIST_ENTRY(mohclass) list; +}; + +struct mohdata { + int pipe[2]; + int origwfmt; + struct mohclass *parent; + struct ast_frame f; + AST_LIST_ENTRY(mohdata) list; +}; + +AST_RWLIST_HEAD_STATIC(mohclasses, mohclass); + +#define LOCAL_MPG_123 "/usr/local/bin/mpg123" +#define MPG_123 "/usr/bin/mpg123" +#define MAX_MP3S 256 + +static int ast_moh_destroy_one(struct mohclass *moh); +static int reload(void); + +static void ast_moh_free_class(struct mohclass **mohclass) +{ + struct mohdata *member; + struct mohclass *class = *mohclass; + int i; + + while ((member = AST_LIST_REMOVE_HEAD(&class->members, list))) + ast_free(member); + + if (class->thread) { + pthread_cancel(class->thread); + class->thread = 0; + } + + if (class->filearray) { + for (i = 0; i < class->total_files; i++) + ast_free(class->filearray[i]); + ast_free(class->filearray); + } + + ast_free(class); + *mohclass = NULL; +} + + +static void moh_files_release(struct ast_channel *chan, void *data) +{ + struct moh_files_state *state = chan->music_state; + + if (chan && state) { + if (chan->stream) { + ast_closestream(chan->stream); + chan->stream = NULL; + } + ast_verb(3, "Stopped music on hold on %s\n", chan->name); + + if (state->origwfmt && ast_set_write_format(chan, state->origwfmt)) { + ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, state->origwfmt); + } + state->save_pos = state->pos; + } + if (ast_atomic_dec_and_test(&state->class->inuse) && state->class->delete) + ast_moh_destroy_one(state->class); +} + + +static int ast_moh_files_next(struct ast_channel *chan) +{ + struct moh_files_state *state = chan->music_state; + int tries; + + /* Discontinue a stream if it is running already */ + if (chan->stream) { + ast_closestream(chan->stream); + chan->stream = NULL; + } + + if (!state->class->total_files) { + ast_log(LOG_WARNING, "No files available for class '%s'\n", state->class->name); + return -1; + } + + /* If a specific file has been saved, use it */ + if (state->save_pos >= 0) { + state->pos = state->save_pos; + state->save_pos = -1; + } else if (ast_test_flag(state->class, MOH_RANDOMIZE)) { + /* Get a random file and ensure we can open it */ + for (tries = 0; tries < 20; tries++) { + state->pos = rand() % state->class->total_files; + if (ast_fileexists(state->class->filearray[state->pos], NULL, NULL) > 0) + break; + } + state->samples = 0; + } else { + /* This is easy, just increment our position and make sure we don't exceed the total file count */ + state->pos++; + state->pos %= state->class->total_files; + state->samples = 0; + } + + if (!ast_openstream_full(chan, state->class->filearray[state->pos], chan->language, 1)) { + ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", state->class->filearray[state->pos], strerror(errno)); + state->pos++; + state->pos %= state->class->total_files; + return -1; + } + + ast_debug(1, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]); + + if (state->samples) + ast_seekstream(chan->stream, state->samples, SEEK_SET); + + return 0; +} + + +static struct ast_frame *moh_files_readframe(struct ast_channel *chan) +{ + struct ast_frame *f = NULL; + + if (!(chan->stream && (f = ast_readframe(chan->stream)))) { + if (!ast_moh_files_next(chan)) + f = ast_readframe(chan->stream); + } + + return f; +} + +static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples) +{ + struct moh_files_state *state = chan->music_state; + struct ast_frame *f = NULL; + int res = 0; + + state->sample_queue += samples; + + while (state->sample_queue > 0) { + if ((f = moh_files_readframe(chan))) { + state->samples += f->samples; + res = ast_write(chan, f); + state->sample_queue -= f->samples; + ast_frfree(f); + if (res < 0) { + ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno)); + return -1; + } + } else + return -1; + } + return res; +} + + +static void *moh_files_alloc(struct ast_channel *chan, void *params) +{ + struct moh_files_state *state; + struct mohclass *class = params; + + if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) { + chan->music_state = state; + state->class = class; + state->save_pos = -1; + } else + state = chan->music_state; + + if (state) { + if (state->class != class) { + /* initialize */ + memset(state, 0, sizeof(*state)); + state->class = class; + if (ast_test_flag(state->class, MOH_RANDOMIZE) && class->total_files) + state->pos = ast_random() % class->total_files; + } + + state->origwfmt = chan->writeformat; + + ast_verb(3, "Started music on hold, class '%s', on %s\n", class->name, chan->name); + } + + return chan->music_state; +} + +/*! \note This function should be called with the mohclasses list locked */ +static struct mohclass *get_mohbydigit(char digit) +{ + struct mohclass *moh = NULL; + + AST_RWLIST_TRAVERSE(&mohclasses, moh, list) { + if (digit == moh->digit) + break; + } + + return moh; +} + +static void moh_handle_digit(struct ast_channel *chan, char digit) +{ + struct mohclass *moh; + const char *classname = NULL; + + AST_RWLIST_RDLOCK(&mohclasses); + if ((moh = get_mohbydigit(digit))) + classname = ast_strdupa(moh->name); + AST_RWLIST_UNLOCK(&mohclasses); + + if (!moh) + return; + + ast_moh_stop(chan); + ast_moh_start(chan, classname, NULL); +} + +static struct ast_generator moh_file_stream = +{ + alloc: moh_files_alloc, + release: moh_files_release, + generate: moh_files_generator, + digit: moh_handle_digit, +}; + +static int spawn_mp3(struct mohclass *class) +{ + int fds[2]; + int files = 0; + char fns[MAX_MP3S][80]; + char *argv[MAX_MP3S + 50]; + char xargs[256]; + char *argptr; + int argc = 0; + DIR *dir = NULL; + struct dirent *de; + sigset_t signal_set, old_set; + + + if (!strcasecmp(class->dir, "nodir")) { + files = 1; + } else { + dir = opendir(class->dir); + if (!dir && !strstr(class->dir,"http://") && !strstr(class->dir,"HTTP://")) { + ast_log(LOG_WARNING, "%s is not a valid directory\n", class->dir); + return -1; + } + } + + if (!ast_test_flag(class, MOH_CUSTOM)) { + argv[argc++] = "mpg123"; + argv[argc++] = "-q"; + argv[argc++] = "-s"; + argv[argc++] = "--mono"; + argv[argc++] = "-r"; + argv[argc++] = "8000"; + + if (!ast_test_flag(class, MOH_SINGLE)) { + argv[argc++] = "-b"; + argv[argc++] = "2048"; + } + + argv[argc++] = "-f"; + + if (ast_test_flag(class, MOH_QUIET)) + argv[argc++] = "4096"; + else + argv[argc++] = "8192"; + + /* Look for extra arguments and add them to the list */ + ast_copy_string(xargs, class->args, sizeof(xargs)); + argptr = xargs; + while (!ast_strlen_zero(argptr)) { + argv[argc++] = argptr; + strsep(&argptr, ","); + } + } else { + /* Format arguments for argv vector */ + ast_copy_string(xargs, class->args, sizeof(xargs)); + argptr = xargs; + while (!ast_strlen_zero(argptr)) { + argv[argc++] = argptr; + strsep(&argptr, " "); + } + } + + + if (strstr(class->dir,"http://") || strstr(class->dir,"HTTP://")) { + ast_copy_string(fns[files], class->dir, sizeof(fns[files])); + argv[argc++] = fns[files]; + files++; + } else if (dir) { + while ((de = readdir(dir)) && (files < MAX_MP3S)) { + if ((strlen(de->d_name) > 3) && + ((ast_test_flag(class, MOH_CUSTOM) && + (!strcasecmp(de->d_name + strlen(de->d_name) - 4, ".raw") || + !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".sln"))) || + !strcasecmp(de->d_name + strlen(de->d_name) - 4, ".mp3"))) { + ast_copy_string(fns[files], de->d_name, sizeof(fns[files])); + argv[argc++] = fns[files]; + files++; + } + } + } + argv[argc] = NULL; + if (dir) { + closedir(dir); + } + if (pipe(fds)) { + ast_log(LOG_WARNING, "Pipe failed\n"); + return -1; + } + if (!files) { + ast_log(LOG_WARNING, "Found no files in '%s'\n", class->dir); + close(fds[0]); + close(fds[1]); + return -1; + } + if (time(NULL) - class->start < respawn_time) { + sleep(respawn_time - (time(NULL) - class->start)); + } + + /* Block signals during the fork() */ + sigfillset(&signal_set); + pthread_sigmask(SIG_BLOCK, &signal_set, &old_set); + + time(&class->start); + class->pid = fork(); + if (class->pid < 0) { + close(fds[0]); + close(fds[1]); + ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno)); + return -1; + } + if (!class->pid) { + int x; + + if (ast_opt_high_priority) + ast_set_priority(0); + + /* Reset ignored signals back to default */ + signal(SIGPIPE, SIG_DFL); + pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL); + + close(fds[0]); + /* Stdout goes to pipe */ + dup2(fds[1], STDOUT_FILENO); + /* Close unused file descriptors */ + for (x=3;x<8192;x++) { + if (-1 != fcntl(x, F_GETFL)) { + close(x); + } + } + /* Child */ + chdir(class->dir); + if (ast_test_flag(class, MOH_CUSTOM)) { + execv(argv[0], argv); + } else { + /* Default install is /usr/local/bin */ + execv(LOCAL_MPG_123, argv); + /* Many places have it in /usr/bin */ + execv(MPG_123, argv); + /* Check PATH as a last-ditch effort */ + execvp("mpg123", argv); + } + ast_log(LOG_WARNING, "Exec failed: %s\n", strerror(errno)); + close(fds[1]); + _exit(1); + } else { + /* Parent */ + pthread_sigmask(SIG_SETMASK, &old_set, NULL); + close(fds[1]); + } + return fds[0]; +} + +static void *monmp3thread(void *data) +{ +#define MOH_MS_INTERVAL 100 + + struct mohclass *class = data; + struct mohdata *moh; + char buf[8192]; + short sbuf[8192]; + int res, res2; + int len; + struct timeval tv, tv_tmp; + + tv.tv_sec = 0; + tv.tv_usec = 0; + for(;/* ever */;) { + pthread_testcancel(); + /* Spawn mp3 player if it's not there */ + if (class->srcfd < 0) { + if ((class->srcfd = spawn_mp3(class)) < 0) { + ast_log(LOG_WARNING, "Unable to spawn mp3player\n"); + /* Try again later */ + sleep(500); + pthread_testcancel(); + } + } + if (class->pseudofd > -1) { +#ifdef SOLARIS + thr_yield(); +#endif + /* Pause some amount of time */ + res = read(class->pseudofd, buf, sizeof(buf)); + pthread_testcancel(); + } else { + long delta; + /* Reliable sleep */ + tv_tmp = ast_tvnow(); + if (ast_tvzero(tv)) + tv = tv_tmp; + delta = ast_tvdiff_ms(tv_tmp, tv); + if (delta < MOH_MS_INTERVAL) { /* too early */ + tv = ast_tvadd(tv, ast_samp2tv(MOH_MS_INTERVAL, 1000)); /* next deadline */ + usleep(1000 * (MOH_MS_INTERVAL - delta)); + pthread_testcancel(); + } else { + ast_log(LOG_NOTICE, "Request to schedule in the past?!?!\n"); + tv = tv_tmp; + } + res = 8 * MOH_MS_INTERVAL; /* 8 samples per millisecond */ + } + if (AST_LIST_EMPTY(&class->members)) + continue; + /* Read mp3 audio */ + len = ast_codec_get_len(class->format, res); + + if ((res2 = read(class->srcfd, sbuf, len)) != len) { + if (!res2) { + close(class->srcfd); + class->srcfd = -1; + pthread_testcancel(); + if (class->pid > 1) { + kill(class->pid, SIGHUP); + usleep(100000); + kill(class->pid, SIGTERM); + usleep(100000); + kill(class->pid, SIGKILL); + class->pid = 0; + } + } else { + ast_debug(1, "Read %d bytes of audio while expecting %d\n", res2, len); + } + continue; + } + pthread_testcancel(); + AST_RWLIST_RDLOCK(&mohclasses); + AST_RWLIST_TRAVERSE(&class->members, moh, list) { + /* Write data */ + if ((res = write(moh->pipe[1], sbuf, res2)) != res2) { + ast_debug(1, "Only wrote %d of %d bytes to pipe\n", res, res2); + } + } + AST_RWLIST_UNLOCK(&mohclasses); + } + return NULL; +} + +static int play_moh_exec(struct ast_channel *chan, void *data) +{ + if (ast_moh_start(chan, data, NULL)) { + ast_log(LOG_WARNING, "Unable to start music on hold (class '%s') on channel %s\n", (char *)data, chan->name); + return 0; + } + while (!ast_safe_sleep(chan, 10000)); + ast_moh_stop(chan); + return -1; +} + +static int wait_moh_exec(struct ast_channel *chan, void *data) +{ + int res; + if (!data || !atoi(data)) { + ast_log(LOG_WARNING, "WaitMusicOnHold requires an argument (number of seconds to wait)\n"); + return -1; + } + if (ast_moh_start(chan, NULL, NULL)) { + ast_log(LOG_WARNING, "Unable to start music on hold for %d seconds on channel %s\n", atoi(data), chan->name); + return 0; + } + res = ast_safe_sleep(chan, atoi(data) * 1000); + ast_moh_stop(chan); + return res; +} + +static int set_moh_exec(struct ast_channel *chan, void *data) +{ + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "SetMusicOnHold requires an argument (class)\n"); + return -1; + } + ast_string_field_set(chan, musicclass, data); + return 0; +} + +static int start_moh_exec(struct ast_channel *chan, void *data) +{ + char *class = NULL; + if (data && strlen(data)) + class = data; + if (ast_moh_start(chan, class, NULL)) + ast_log(LOG_NOTICE, "Unable to start music on hold class '%s' on channel %s\n", class ? class : "default", chan->name); + + return 0; +} + +static int stop_moh_exec(struct ast_channel *chan, void *data) +{ + ast_moh_stop(chan); + + return 0; +} + +/*! \note This function should be called with the mohclasses list locked */ +static struct mohclass *get_mohbyname(const char *name, int warn) +{ + struct mohclass *moh = NULL; + + AST_RWLIST_TRAVERSE(&mohclasses, moh, list) { + if (!strcasecmp(name, moh->name)) + break; + } + + if (!moh && warn) + ast_log(LOG_DEBUG, "Music on Hold class '%s' not found in memory\n", name); + + return moh; +} + +static struct mohdata *mohalloc(struct mohclass *cl) +{ + struct mohdata *moh; + long flags; + + if (!(moh = ast_calloc(1, sizeof(*moh)))) + return NULL; + + if (pipe(moh->pipe)) { + ast_log(LOG_WARNING, "Failed to create pipe: %s\n", strerror(errno)); + ast_free(moh); + return NULL; + } + + /* Make entirely non-blocking */ + flags = fcntl(moh->pipe[0], F_GETFL); + fcntl(moh->pipe[0], F_SETFL, flags | O_NONBLOCK); + flags = fcntl(moh->pipe[1], F_GETFL); + fcntl(moh->pipe[1], F_SETFL, flags | O_NONBLOCK); + + moh->f.frametype = AST_FRAME_VOICE; + moh->f.subclass = cl->format; + moh->f.offset = AST_FRIENDLY_OFFSET; + + moh->parent = cl; + + AST_RWLIST_WRLOCK(&mohclasses); + AST_LIST_INSERT_HEAD(&cl->members, moh, list); + AST_RWLIST_UNLOCK(&mohclasses); + + return moh; +} + +static void moh_release(struct ast_channel *chan, void *data) +{ + struct mohdata *moh = data; + int oldwfmt; + struct moh_files_state *state; + + AST_RWLIST_WRLOCK(&mohclasses); + AST_RWLIST_REMOVE(&moh->parent->members, moh, list); + AST_RWLIST_UNLOCK(&mohclasses); + + close(moh->pipe[0]); + close(moh->pipe[1]); + oldwfmt = moh->origwfmt; + state = chan->music_state; + if (moh->parent->delete && ast_atomic_dec_and_test(&moh->parent->inuse)) + ast_moh_destroy_one(moh->parent); + if (ast_atomic_dec_and_test(&state->class->inuse) && state->class->delete) + ast_moh_destroy_one(state->class); + + ast_free(moh); + if (chan) { + if (oldwfmt && ast_set_write_format(chan, oldwfmt)) + ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", chan->name, ast_getformatname(oldwfmt)); + ast_verb(3, "Stopped music on hold on %s\n", chan->name); + } +} + +static void *moh_alloc(struct ast_channel *chan, void *params) +{ + struct mohdata *res; + struct mohclass *class = params; + struct moh_files_state *state; + + /* Initiating music_state for current channel. Channel should know name of moh class */ + if (!chan->music_state && (state = ast_calloc(1, sizeof(*state)))) { + chan->music_state = state; + state->class = class; + } else + state = chan->music_state; + if (state && state->class != class) { + memset(state, 0, sizeof(*state)); + state->class = class; + } + + if ((res = mohalloc(class))) { + res->origwfmt = chan->writeformat; + if (ast_set_write_format(chan, class->format)) { + ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format)); + moh_release(NULL, res); + res = NULL; + } + ast_verb(3, "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name); + } + return res; +} + +static int moh_generate(struct ast_channel *chan, void *data, int len, int samples) +{ + struct mohdata *moh = data; + short buf[1280 + AST_FRIENDLY_OFFSET / 2]; + int res; + + if (!moh->parent->pid) + return -1; + + len = ast_codec_get_len(moh->parent->format, samples); + + if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) { + ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name); + len = sizeof(buf) - AST_FRIENDLY_OFFSET; + } + res = read(moh->pipe[0], buf + AST_FRIENDLY_OFFSET/2, len); + if (res <= 0) + return 0; + + moh->f.datalen = res; + moh->f.data = buf + AST_FRIENDLY_OFFSET / 2; + moh->f.samples = ast_codec_get_samples(&moh->f); + + if (ast_write(chan, &moh->f) < 0) { + ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno)); + return -1; + } + + return 0; +} + +static struct ast_generator mohgen = +{ + alloc: moh_alloc, + release: moh_release, + generate: moh_generate, + digit: moh_handle_digit +}; + +static int moh_add_file(struct mohclass *class, const char *filepath) +{ + if (!class->allowed_files) { + if (!(class->filearray = ast_calloc(1, INITIAL_NUM_FILES * sizeof(*class->filearray)))) + return -1; + class->allowed_files = INITIAL_NUM_FILES; + } else if (class->total_files == class->allowed_files) { + if (!(class->filearray = ast_realloc(class->filearray, class->allowed_files * sizeof(*class->filearray) * 2))) { + class->allowed_files = 0; + class->total_files = 0; + return -1; + } + class->allowed_files *= 2; + } + + if (!(class->filearray[class->total_files] = ast_strdup(filepath))) + return -1; + + class->total_files++; + + return 0; +} + +static int moh_sort_compare(const void *i1, const void *i2) +{ + char *s1, *s2; + + s1 = ((char **)i1)[0]; + s2 = ((char **)i2)[0]; + + return strcasecmp(s1, s2); +} + +static int moh_scan_files(struct mohclass *class) { + + DIR *files_DIR; + struct dirent *files_dirent; + char path[PATH_MAX]; + char filepath[PATH_MAX]; + char *ext; + struct stat statbuf; + int dirnamelen; + int i; + + files_DIR = opendir(class->dir); + if (!files_DIR) { + ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist\n", class->dir); + return -1; + } + + for (i = 0; i < class->total_files; i++) + ast_free(class->filearray[i]); + + class->total_files = 0; + dirnamelen = strlen(class->dir) + 2; + getcwd(path, sizeof(path)); + chdir(class->dir); + while ((files_dirent = readdir(files_DIR))) { + /* The file name must be at least long enough to have the file type extension */ + if ((strlen(files_dirent->d_name) < 4)) + continue; + + /* Skip files that starts with a dot */ + if (files_dirent->d_name[0] == '.') + continue; + + /* Skip files without extensions... they are not audio */ + if (!strchr(files_dirent->d_name, '.')) + continue; + + snprintf(filepath, sizeof(filepath), "%s/%s", class->dir, files_dirent->d_name); + + if (stat(filepath, &statbuf)) + continue; + + if (!S_ISREG(statbuf.st_mode)) + continue; + + if ((ext = strrchr(filepath, '.'))) + *ext = '\0'; + + /* if the file is present in multiple formats, ensure we only put it into the list once */ + for (i = 0; i < class->total_files; i++) + if (!strcmp(filepath, class->filearray[i])) + break; + + if (i == class->total_files) { + if (moh_add_file(class, filepath)) + break; + } + } + + closedir(files_DIR); + chdir(path); + if (ast_test_flag(class, MOH_SORTALPHA)) + qsort(&class->filearray[0], class->total_files, sizeof(char *), moh_sort_compare); + return class->total_files; +} + +static int moh_diff(struct mohclass *old, struct mohclass *new) +{ + if (!old || !new) + return -1; + + if (strcmp(old->dir, new->dir)) + return -1; + else if (strcmp(old->mode, new->mode)) + return -1; + else if (strcmp(old->args, new->args)) + return -1; + else if (old->flags != new->flags) + return -1; + + return 0; +} + +static int moh_register(struct mohclass *moh, int reload) +{ +#ifdef HAVE_ZAPTEL + int x; +#endif + struct mohclass *mohclass = NULL; + + AST_RWLIST_WRLOCK(&mohclasses); + if ((mohclass = get_mohbyname(moh->name, 0)) && !moh_diff(mohclass, moh)) { + mohclass->delete = 0; + if (reload) { + ast_debug(1, "Music on Hold class '%s' left alone from initial load.\n", moh->name); + } else { + ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name); + } + ast_free(moh); + AST_RWLIST_UNLOCK(&mohclasses); + return -1; + } + AST_RWLIST_UNLOCK(&mohclasses); + + time(&moh->start); + moh->start -= respawn_time; + + if (!strcasecmp(moh->mode, "files")) { + if (!moh_scan_files(moh)) { + ast_moh_free_class(&moh); + return -1; + } + if (strchr(moh->args, 'r')) + ast_set_flag(moh, MOH_RANDOMIZE); + } else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") || !strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") || !strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) { + + if (!strcasecmp(moh->mode, "custom")) + ast_set_flag(moh, MOH_CUSTOM); + else if (!strcasecmp(moh->mode, "mp3nb")) + ast_set_flag(moh, MOH_SINGLE); + else if (!strcasecmp(moh->mode, "quietmp3nb")) + ast_set_flag(moh, MOH_SINGLE | MOH_QUIET); + else if (!strcasecmp(moh->mode, "quietmp3")) + ast_set_flag(moh, MOH_QUIET); + + moh->srcfd = -1; +#ifdef HAVE_ZAPTEL + /* Open /dev/zap/pseudo for timing... Is + there a better, yet reliable way to do this? */ + moh->pseudofd = open("/dev/zap/pseudo", O_RDONLY); + if (moh->pseudofd < 0) { + ast_log(LOG_WARNING, "Unable to open pseudo channel for timing... Sound may be choppy.\n"); + } else { + x = 320; + ioctl(moh->pseudofd, ZT_SET_BLOCKSIZE, &x); + } +#else + moh->pseudofd = -1; +#endif + if (ast_pthread_create_background(&moh->thread, NULL, monmp3thread, moh)) { + ast_log(LOG_WARNING, "Unable to create moh...\n"); + if (moh->pseudofd > -1) + close(moh->pseudofd); + ast_moh_free_class(&moh); + return -1; + } + } else { + ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode); + ast_moh_free_class(&moh); + return -1; + } + + AST_RWLIST_WRLOCK(&mohclasses); + AST_RWLIST_INSERT_HEAD(&mohclasses, moh, list); + AST_RWLIST_UNLOCK(&mohclasses); + + return 0; +} + +static void local_ast_moh_cleanup(struct ast_channel *chan) +{ + struct moh_files_state *state = chan->music_state; + + if (state) { + if (state->class->realtime) { + if (ast_test_flag(global_flags, MOH_CACHERTCLASSES)) { + /* We are cleaning out cached RT class, we should remove it from list, if no one else using it */ + if (!(state->class->inuse)) { + /* Remove this class from list */ + AST_RWLIST_WRLOCK(&mohclasses); + AST_RWLIST_REMOVE(&mohclasses, state->class, list); + AST_RWLIST_UNLOCK(&mohclasses); + + /* Free some memory */ + ast_moh_destroy_one(state->class); + } + } else { + ast_moh_destroy_one(state->class); + } + } + ast_free(chan->music_state); + chan->music_state = NULL; + } +} + +static struct mohclass *moh_class_malloc(void) +{ + struct mohclass *class; + + if ((class = ast_calloc(1, sizeof(*class)))) { + class->format = AST_FORMAT_SLINEAR; + class->realtime = 0; + } + + return class; +} + +static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass) +{ + struct mohclass *mohclass = NULL; + struct ast_variable *var = NULL; + struct ast_variable *tmp = NULL; + struct moh_files_state *state = chan->music_state; +#ifdef HAVE_ZAPTEL + int x; +#endif + + /* The following is the order of preference for which class to use: + * 1) The channels explicitly set musicclass, which should *only* be + * set by a call to Set(CHANNEL(musicclass)=whatever) in the dialplan. + * 2) The mclass argument. If a channel is calling ast_moh_start() as the + * result of receiving a HOLD control frame, this should be the + * payload that came with the frame. + * 3) The interpclass argument. This would be from the mohinterpret + * option from channel drivers. This is the same as the old musicclass + * option. + * 4) The default class. + */ + + /* First, let's check in memory for static and cached RT classes */ + AST_RWLIST_RDLOCK(&mohclasses); + if (!ast_strlen_zero(chan->musicclass)) + mohclass = get_mohbyname(chan->musicclass, 1); + if (!mohclass && !ast_strlen_zero(mclass)) + mohclass = get_mohbyname(mclass, 1); + if (!mohclass && !ast_strlen_zero(interpclass)) + mohclass = get_mohbyname(interpclass, 1); + AST_RWLIST_UNLOCK(&mohclasses); + + /* If no moh class found in memory, then check RT */ + if (!mohclass && ast_check_realtime("musiconhold")) { + if (!ast_strlen_zero(chan->musicclass)) { + var = ast_load_realtime("musiconhold", "name", chan->musicclass, NULL); + } + if (!var && !ast_strlen_zero(mclass)) + var = ast_load_realtime("musiconhold", "name", mclass, NULL); + if (!var && !ast_strlen_zero(interpclass)) + var = ast_load_realtime("musiconhold", "name", interpclass, NULL); + if (!var) + var = ast_load_realtime("musiconhold", "name", "default", NULL); + if (var && (mohclass = moh_class_malloc())) { + mohclass->realtime = 1; + for (tmp = var; tmp; tmp = tmp->next) { + if (!strcasecmp(tmp->name, "name")) + ast_copy_string(mohclass->name, tmp->value, sizeof(mohclass->name)); + else if (!strcasecmp(tmp->name, "mode")) + ast_copy_string(mohclass->mode, tmp->value, sizeof(mohclass->mode)); + else if (!strcasecmp(tmp->name, "directory")) + ast_copy_string(mohclass->dir, tmp->value, sizeof(mohclass->dir)); + else if (!strcasecmp(tmp->name, "application")) + ast_copy_string(mohclass->args, tmp->value, sizeof(mohclass->args)); + else if (!strcasecmp(tmp->name, "digit") && (isdigit(*tmp->value) || strchr("*#", *tmp->value))) + mohclass->digit = *tmp->value; + else if (!strcasecmp(tmp->name, "random")) + ast_set2_flag(mohclass, ast_true(tmp->value), MOH_RANDOMIZE); + else if (!strcasecmp(tmp->name, "sort") && !strcasecmp(tmp->value, "random")) + ast_set_flag(mohclass, MOH_RANDOMIZE); + else if (!strcasecmp(tmp->name, "sort") && !strcasecmp(tmp->value, "alpha")) + ast_set_flag(mohclass, MOH_SORTALPHA); + else if (!strcasecmp(tmp->name, "format")) { + mohclass->format = ast_getformatbyname(tmp->value); + if (!mohclass->format) { + ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", tmp->value); + mohclass->format = AST_FORMAT_SLINEAR; + } + } + } + ast_variables_destroy(var); + if (ast_strlen_zero(mohclass->dir)) { + if (!strcasecmp(mohclass->mode, "custom")) { + strcpy(mohclass->dir, "nodir"); + } else { + ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", mohclass->name); + ast_free(mohclass); + return -1; + } + } + if (ast_strlen_zero(mohclass->mode)) { + ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", mohclass->name); + ast_free(mohclass); + return -1; + } + if (ast_strlen_zero(mohclass->args) && !strcasecmp(mohclass->mode, "custom")) { + ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", mohclass->name); + ast_free(mohclass); + return -1; + } + + if (ast_test_flag(global_flags, MOH_CACHERTCLASSES)) { + /* CACHERTCLASSES enabled, let's add this class to default tree */ + if (state && state->class) { + /* Class already exist for this channel */ + ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name); + if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) { + /* we found RT class with the same name, seems like we should continue playing existing one */ + ast_moh_free_class(&mohclass); + mohclass = state->class; + } + } + moh_register(mohclass, 0); + } else { + + /* We don't register RT moh class, so let's init it manualy */ + + time(&mohclass->start); + mohclass->start -= respawn_time; + + if (!strcasecmp(mohclass->mode, "files")) { + if (!moh_scan_files(mohclass)) { + ast_moh_free_class(&mohclass); + return -1; + } + if (strchr(mohclass->args, 'r')) + ast_set_flag(mohclass, MOH_RANDOMIZE); + } else if (!strcasecmp(mohclass->mode, "mp3") || !strcasecmp(mohclass->mode, "mp3nb") || !strcasecmp(mohclass->mode, "quietmp3") || !strcasecmp(mohclass->mode, "quietmp3nb") || !strcasecmp(mohclass->mode, "httpmp3") || !strcasecmp(mohclass->mode, "custom")) { + + if (!strcasecmp(mohclass->mode, "custom")) + ast_set_flag(mohclass, MOH_CUSTOM); + else if (!strcasecmp(mohclass->mode, "mp3nb")) + ast_set_flag(mohclass, MOH_SINGLE); + else if (!strcasecmp(mohclass->mode, "quietmp3nb")) + ast_set_flag(mohclass, MOH_SINGLE | MOH_QUIET); + else if (!strcasecmp(mohclass->mode, "quietmp3")) + ast_set_flag(mohclass, MOH_QUIET); + + mohclass->srcfd = -1; +#ifdef HAVE_ZAPTEL + /* Open /dev/zap/pseudo for timing... Is + there a better, yet reliable way to do this? */ + mohclass->pseudofd = open("/dev/zap/pseudo", O_RDONLY); + if (mohclass->pseudofd < 0) { + ast_log(LOG_WARNING, "Unable to open pseudo channel for timing... Sound may be choppy.\n"); + } else { + x = 320; + ioctl(mohclass->pseudofd, ZT_SET_BLOCKSIZE, &x); + } +#else + mohclass->pseudofd = -1; +#endif + /* Let's check if this channel already had a moh class before */ + if (state && state->class) { + /* Class already exist for this channel */ + ast_log(LOG_NOTICE, "This channel already has a MOH class attached (%s)!\n", state->class->name); + if (state->class->realtime && !ast_test_flag(global_flags, MOH_CACHERTCLASSES) && !strcasecmp(mohclass->name, state->class->name)) { + /* we found RT class with the same name, seems like we should continue playing existing one */ + ast_moh_free_class(&mohclass); + mohclass = state->class; + + } + } else { + if (ast_pthread_create_background(&mohclass->thread, NULL, monmp3thread, mohclass)) { + ast_log(LOG_WARNING, "Unable to create moh...\n"); + if (mohclass->pseudofd > -1) + close(mohclass->pseudofd); + ast_moh_free_class(&mohclass); + return -1; + } + } + } else { + ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mohclass->mode); + ast_moh_free_class(&mohclass); + return -1; + } + + } + + } else if (var) + ast_variables_destroy(var); + } + + + + /* Requested MOH class not found, check for 'default' class in musiconhold.conf */ + if (!mohclass) { + AST_RWLIST_RDLOCK(&mohclasses); + mohclass = get_mohbyname("default", 1); + if (mohclass) + ast_atomic_fetchadd_int(&mohclass->inuse, +1); + AST_RWLIST_UNLOCK(&mohclasses); + } else { + AST_RWLIST_RDLOCK(&mohclasses); + ast_atomic_fetchadd_int(&mohclass->inuse, +1); + AST_RWLIST_UNLOCK(&mohclasses); + } + + if (!mohclass) + return -1; + + ast_set_flag(chan, AST_FLAG_MOH); + if (mohclass->total_files) { + return ast_activate_generator(chan, &moh_file_stream, mohclass); + } else + return ast_activate_generator(chan, &mohgen, mohclass); +} + +static void local_ast_moh_stop(struct ast_channel *chan) +{ + struct moh_files_state *state = chan->music_state; + ast_clear_flag(chan, AST_FLAG_MOH); + ast_deactivate_generator(chan); + + if (state) { + if (chan->stream) { + ast_closestream(chan->stream); + chan->stream = NULL; + } + } +} + +static int load_moh_classes(int reload) +{ + struct ast_config *cfg; + struct ast_variable *var; + struct mohclass *class; + char *cat; + int numclasses = 0; + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + + cfg = ast_config_load("musiconhold.conf", config_flags); + + if (cfg == NULL || cfg == CONFIG_STATUS_FILEUNCHANGED) + return 0; + + if (reload) { + AST_RWLIST_WRLOCK(&mohclasses); + AST_RWLIST_TRAVERSE(&mohclasses, class, list) { + class->delete = 1; + } + AST_RWLIST_UNLOCK(&mohclasses); + } + + ast_clear_flag(global_flags, AST_FLAGS_ALL); + + cat = ast_category_browse(cfg, NULL); + for (; cat; cat = ast_category_browse(cfg, cat)) { + /* Setup common options from [general] section */ + if (!strcasecmp(cat, "general")) { + var = ast_variable_browse(cfg, cat); + while (var) { + if (!strcasecmp(var->name, "cachertclasses")) { + ast_set2_flag(global_flags, ast_true(var->value), MOH_CACHERTCLASSES); + } else { + ast_log(LOG_WARNING, "Unknown option '%s' in [general] section of musiconhold.conf\n", var->name); + } + var = var->next; + } + } + /* These names were deprecated in 1.4 and should not be used until after the next major release. */ + if (strcasecmp(cat, "classes") && strcasecmp(cat, "moh_files") && strcasecmp(cat, "general")) { + if (!(class = moh_class_malloc())) + break; + + ast_copy_string(class->name, cat, sizeof(class->name)); + var = ast_variable_browse(cfg, cat); + while (var) { + if (!strcasecmp(var->name, "mode")) + ast_copy_string(class->mode, var->value, sizeof(class->mode)); + else if (!strcasecmp(var->name, "directory")) + ast_copy_string(class->dir, var->value, sizeof(class->dir)); + else if (!strcasecmp(var->name, "application")) + ast_copy_string(class->args, var->value, sizeof(class->args)); + else if (!strcasecmp(var->name, "digit") && (isdigit(*var->value) || strchr("*#", *var->value))) + class->digit = *var->value; + else if (!strcasecmp(var->name, "random")) + ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE); + else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "random")) + ast_set_flag(class, MOH_RANDOMIZE); + else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "alpha")) + ast_set_flag(class, MOH_SORTALPHA); + else if (!strcasecmp(var->name, "format")) { + class->format = ast_getformatbyname(var->value); + if (!class->format) { + ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value); + class->format = AST_FORMAT_SLINEAR; + } + } + var = var->next; + } + + if (ast_strlen_zero(class->dir)) { + if (!strcasecmp(class->mode, "custom")) { + strcpy(class->dir, "nodir"); + } else { + ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name); + ast_free(class); + continue; + } + } + if (ast_strlen_zero(class->mode)) { + ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name); + ast_free(class); + continue; + } + if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) { + ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name); + ast_free(class); + continue; + } + + /* Don't leak a class when it's already registered */ + moh_register(class, reload); + + numclasses++; + } + } + + ast_config_destroy(cfg); + + return numclasses; +} + +static int ast_moh_destroy_one(struct mohclass *moh) +{ + char buff[8192]; + int bytes, tbytes = 0, stime = 0, pid = 0; + + if (moh) { + if (moh->pid > 1) { + ast_debug(1, "killing %d!\n", moh->pid); + stime = time(NULL) + 2; + pid = moh->pid; + moh->pid = 0; + /* Back when this was just mpg123, SIGKILL was fine. Now we need + * to give the process a reason and time enough to kill off its + * children. */ + kill(pid, SIGHUP); + usleep(100000); + kill(pid, SIGTERM); + usleep(100000); + kill(pid, SIGKILL); + while ((ast_wait_for_input(moh->srcfd, 100) > 0) && (bytes = read(moh->srcfd, buff, 8192)) && time(NULL) < stime) + tbytes = tbytes + bytes; + ast_debug(1, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes); + close(moh->srcfd); + } + ast_moh_free_class(&moh); + } + + return 0; +} + +static void ast_moh_destroy(void) +{ + struct mohclass *moh; + + ast_verb(2, "Destroying musiconhold processes\n"); + + AST_RWLIST_WRLOCK(&mohclasses); + while ((moh = AST_RWLIST_REMOVE_HEAD(&mohclasses, list))) { + ast_moh_destroy_one(moh); + } + AST_RWLIST_UNLOCK(&mohclasses); +} + +static char *handle_cli_moh_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "moh reload"; + e->usage = + "Usage: moh reload\n" + " Reloads the MusicOnHold module.\n" + " Alias for 'module reload res_musiconhold.so'\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != e->args) + return CLI_SHOWUSAGE; + + reload(); + + return CLI_SUCCESS; +} + +static char *handle_cli_moh_show_files(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + int i; + struct mohclass *class; + + switch (cmd) { + case CLI_INIT: + e->command = "moh show files"; + e->usage = + "Usage: moh show files\n" + " Lists all loaded file-based MusicOnHold classes and their\n" + " files.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != e->args) + return CLI_SHOWUSAGE; + + AST_RWLIST_RDLOCK(&mohclasses); + AST_RWLIST_TRAVERSE(&mohclasses, class, list) { + if (!class->total_files) + continue; + + ast_cli(a->fd, "Class: %s\n", class->name); + for (i = 0; i < class->total_files; i++) + ast_cli(a->fd, "\tFile: %s\n", class->filearray[i]); + } + AST_RWLIST_UNLOCK(&mohclasses); + + return CLI_SUCCESS; +} + +static char *handle_cli_moh_show_classes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct mohclass *class; + + switch (cmd) { + case CLI_INIT: + e->command = "moh show classes"; + e->usage = + "Usage: moh show classes\n" + " Lists all MusicOnHold classes.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != e->args) + return CLI_SHOWUSAGE; + + AST_RWLIST_RDLOCK(&mohclasses); + AST_RWLIST_TRAVERSE(&mohclasses, class, list) { + ast_cli(a->fd, "Class: %s\n", class->name); + ast_cli(a->fd, "\tMode: %s\n", S_OR(class->mode, "<none>")); + ast_cli(a->fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>")); + ast_cli(a->fd, "\tUse Count: %d\n", class->inuse); + if (class->digit) + ast_cli(a->fd, "\tDigit: %c\n", class->digit); + if (ast_test_flag(class, MOH_CUSTOM)) + ast_cli(a->fd, "\tApplication: %s\n", S_OR(class->args, "<none>")); + if (strcasecmp(class->mode, "files")) + ast_cli(a->fd, "\tFormat: %s\n", ast_getformatname(class->format)); + } + AST_RWLIST_UNLOCK(&mohclasses); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_moh[] = { + AST_CLI_DEFINE(handle_cli_moh_reload, "Reload MusicOnHold"), + AST_CLI_DEFINE(handle_cli_moh_show_classes, "List MusicOnHold classes"), + AST_CLI_DEFINE(handle_cli_moh_show_files, "List MusicOnHold file-based classes") +}; + +static int init_classes(int reload) +{ + struct mohclass *moh; + + if (!load_moh_classes(reload)) /* Load classes from config */ + return 0; /* Return if nothing is found */ + + AST_RWLIST_WRLOCK(&mohclasses); + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&mohclasses, moh, list) { + if (reload && moh->delete) { + AST_RWLIST_REMOVE_CURRENT(list); + if (!moh->inuse) + ast_moh_destroy_one(moh); + } else if (moh->total_files) { + if (moh_scan_files(moh) <= 0) { + ast_log(LOG_WARNING, "No files found for class '%s'\n", moh->name); + moh->delete = 1; + AST_LIST_REMOVE_CURRENT(list); + if (!moh->inuse) + ast_moh_destroy_one(moh); + } + } + } + AST_RWLIST_TRAVERSE_SAFE_END + AST_RWLIST_UNLOCK(&mohclasses); + + return 1; +} + +static int load_module(void) +{ + int res; + + res = ast_register_application(play_moh, play_moh_exec, play_moh_syn, play_moh_desc); + ast_register_atexit(ast_moh_destroy); + ast_cli_register_multiple(cli_moh, sizeof(cli_moh) / sizeof(struct ast_cli_entry)); + if (!res) + res = ast_register_application(wait_moh, wait_moh_exec, wait_moh_syn, wait_moh_desc); + if (!res) + res = ast_register_application(set_moh, set_moh_exec, set_moh_syn, set_moh_desc); + if (!res) + res = ast_register_application(start_moh, start_moh_exec, start_moh_syn, start_moh_desc); + if (!res) + res = ast_register_application(stop_moh, stop_moh_exec, stop_moh_syn, stop_moh_desc); + + if (!init_classes(0)) { /* No music classes configured, so skip it */ + ast_log(LOG_WARNING, "No music on hold classes configured, disabling music on hold.\n"); + } else { + ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup); + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int reload(void) +{ + if (init_classes(1)) + ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup); + + return 0; +} + +static int unload_module(void) +{ + return -1; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Music On Hold Resource", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); diff --git a/trunk/res/res_odbc.c b/trunk/res/res_odbc.c new file mode 100644 index 000000000..d030de142 --- /dev/null +++ b/trunk/res/res_odbc.c @@ -0,0 +1,778 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * + * res_odbc.c <ODBC resource manager> + * Copyright (C) 2004 - 2005 Anthony Minessale II <anthmct@yahoo.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief ODBC resource manager + * + * \author Mark Spencer <markster@digium.com> + * \author Anthony Minessale II <anthmct@yahoo.com> + * + * \arg See also: \ref cdr_odbc + */ + +/*** MODULEINFO + <depend>unixodbc</depend> + <depend>ltdl</depend> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/config.h" +#include "asterisk/pbx.h" +#include "asterisk/module.h" +#include "asterisk/cli.h" +#include "asterisk/lock.h" +#include "asterisk/res_odbc.h" + +struct odbc_class +{ + AST_LIST_ENTRY(odbc_class) list; + char name[80]; + char dsn[80]; + char *username; + char *password; + char sanitysql[256]; + SQLHENV env; + unsigned int haspool:1; /* Boolean - TDS databases need this */ + unsigned int limit:10; /* Gives a limit of 1023 maximum */ + unsigned int count:10; /* Running count of pooled connections */ + unsigned int delme:1; /* Purge the class */ + unsigned int backslash_is_escape:1; /* On this database, the backslash is a native escape sequence */ + AST_LIST_HEAD(, odbc_obj) odbc_obj; +}; + +AST_LIST_HEAD_STATIC(odbc_list, odbc_class); + +static odbc_status odbc_obj_connect(struct odbc_obj *obj); +static odbc_status odbc_obj_disconnect(struct odbc_obj *obj); +static int odbc_register_class(struct odbc_class *class, int connect); + + +SQLHSTMT ast_odbc_direct_execute(struct odbc_obj *obj, SQLHSTMT (*exec_cb)(struct odbc_obj *obj, void *data), void *data) +{ + int attempt; + SQLHSTMT stmt; + + for (attempt = 0; attempt < 2; attempt++) { + stmt = exec_cb(obj, data); + + if (stmt) { + break; + } else { + obj->up = 0; + ast_log(LOG_WARNING, "SQL Exec Direct failed. Attempting a reconnect...\n"); + + odbc_obj_disconnect(obj); + odbc_obj_connect(obj); + } + } + + return stmt; +} + +SQLHSTMT ast_odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT (*prepare_cb)(struct odbc_obj *obj, void *data), void *data) +{ + int res = 0, i, attempt; + SQLINTEGER nativeerror=0, numfields=0; + SQLSMALLINT diagbytes=0; + unsigned char state[10], diagnostic[256]; + SQLHSTMT stmt; + + for (attempt = 0; attempt < 2; attempt++) { + /* This prepare callback may do more than just prepare -- it may also + * bind parameters, bind results, etc. The real key, here, is that + * when we disconnect, all handles become invalid for most databases. + * We must therefore redo everything when we establish a new + * connection. */ + stmt = prepare_cb(obj, data); + + if (stmt) { + res = SQLExecute(stmt); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) { + if (res == SQL_ERROR) { + SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes); + for (i = 0; i < numfields; i++) { + SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes); + ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes); + if (i > 10) { + ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields); + break; + } + } + } + + ast_log(LOG_WARNING, "SQL Execute error %d! Attempting a reconnect...\n", res); + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + stmt = NULL; + + obj->up = 0; + /* + * While this isn't the best way to try to correct an error, this won't automatically + * fail when the statement handle invalidates. + */ + ast_odbc_sanity_check(obj); + continue; + } + break; + } else if (attempt == 0) + ast_odbc_sanity_check(obj); + } + + return stmt; +} + +int ast_odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt) +{ + int res = 0, i; + SQLINTEGER nativeerror=0, numfields=0; + SQLSMALLINT diagbytes=0; + unsigned char state[10], diagnostic[256]; + + res = SQLExecute(stmt); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) { + if (res == SQL_ERROR) { + SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes); + for (i = 0; i < numfields; i++) { + SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes); + ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes); + if (i > 10) { + ast_log(LOG_WARNING, "Oh, that was good. There are really %d diagnostics?\n", (int)numfields); + break; + } + } + } +#if 0 + /* This is a really bad method of trying to correct a dead connection. It + * only ever really worked with MySQL. It will not work with any other + * database, since most databases prepare their statements on the server, + * and if you disconnect, you invalidate the statement handle. Hence, if + * you disconnect, you're going to fail anyway, whether you try to execute + * a second time or not. + */ + ast_log(LOG_WARNING, "SQL Execute error %d! Attempting a reconnect...\n", res); + ast_mutex_lock(&obj->lock); + obj->up = 0; + ast_mutex_unlock(&obj->lock); + odbc_obj_disconnect(obj); + odbc_obj_connect(obj); + res = SQLExecute(stmt); +#endif + } + + return res; +} + + +int ast_odbc_sanity_check(struct odbc_obj *obj) +{ + char *test_sql = "select 1"; + SQLHSTMT stmt; + int res = 0; + + if (!ast_strlen_zero(obj->parent->sanitysql)) + test_sql = obj->parent->sanitysql; + + if (obj->up) { + res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + obj->up = 0; + } else { + res = SQLPrepare(stmt, (unsigned char *)test_sql, SQL_NTS); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + obj->up = 0; + } else { + res = SQLExecute(stmt); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + obj->up = 0; + } + } + } + SQLFreeHandle (SQL_HANDLE_STMT, stmt); + } + + if (!obj->up) { /* Try to reconnect! */ + ast_log(LOG_WARNING, "Connection is down attempting to reconnect...\n"); + odbc_obj_disconnect(obj); + odbc_obj_connect(obj); + } + return obj->up; +} + +static int load_odbc_config(void) +{ + static char *cfg = "res_odbc.conf"; + struct ast_config *config; + struct ast_variable *v; + char *cat; + const char *dsn, *username, *password, *sanitysql; + int enabled, pooling, limit, bse; + int connect = 0, res = 0; + struct ast_flags config_flags = { 0 }; + + struct odbc_class *new; + + config = ast_config_load(cfg, config_flags); + if (!config) { + ast_log(LOG_WARNING, "Unable to load config file res_odbc.conf\n"); + return -1; + } + for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) { + if (!strcasecmp(cat, "ENV")) { + for (v = ast_variable_browse(config, cat); v; v = v->next) { + setenv(v->name, v->value, 1); + ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value); + } + } else { + /* Reset all to defaults for each class of odbc connections */ + dsn = username = password = sanitysql = NULL; + enabled = 1; + connect = 0; + pooling = 0; + limit = 0; + bse = 1; + for (v = ast_variable_browse(config, cat); v; v = v->next) { + if (!strcasecmp(v->name, "pooling")) { + if (ast_true(v->value)) + pooling = 1; + } else if (!strcasecmp(v->name, "limit")) { + sscanf(v->value, "%d", &limit); + if (ast_true(v->value) && !limit) { + ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'. Setting limit to 1023 for ODBC class '%s'.\n", v->value, cat); + limit = 1023; + } else if (ast_false(v->value)) { + ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'. Disabling ODBC class '%s'.\n", v->value, cat); + enabled = 0; + break; + } + } else if (!strcasecmp(v->name, "enabled")) { + enabled = ast_true(v->value); + } else if (!strcasecmp(v->name, "pre-connect")) { + connect = ast_true(v->value); + } else if (!strcasecmp(v->name, "dsn")) { + dsn = v->value; + } else if (!strcasecmp(v->name, "username")) { + username = v->value; + } else if (!strcasecmp(v->name, "password")) { + password = v->value; + } else if (!strcasecmp(v->name, "sanitysql")) { + sanitysql = v->value; + } else if (!strcasecmp(v->name, "backslash_is_escape")) { + bse = ast_true(v->value); + } + } + + if (enabled && !ast_strlen_zero(dsn)) { + new = ast_calloc(1, sizeof(*new)); + + if (!new) { + res = -1; + break; + } + + if (cat) + ast_copy_string(new->name, cat, sizeof(new->name)); + if (dsn) + ast_copy_string(new->dsn, dsn, sizeof(new->dsn)); + if (username) + new->username = ast_strdup(username); + if (password) + new->password = ast_strdup(password); + if (sanitysql) + ast_copy_string(new->sanitysql, sanitysql, sizeof(new->sanitysql)); + + SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env); + res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0); + + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n"); + SQLFreeHandle(SQL_HANDLE_ENV, new->env); + return res; + } + + if (pooling) { + new->haspool = pooling; + if (limit) { + new->limit = limit; + } else { + ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless. Changing limit from 0 to 5.\n"); + new->limit = 5; + } + } + + new->backslash_is_escape = bse ? 1 : 0; + + odbc_register_class(new, connect); + ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn); + } + } + } + ast_config_destroy(config); + return res; +} + +static char *handle_cli_odbc_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct odbc_class *class; + struct odbc_obj *current; + int length = 0; + int which = 0; + char *ret = NULL; + + switch (cmd) { + case CLI_INIT: + e->command = "odbc show"; + e->usage = + "Usage: odbc show [class]\n" + " List settings of a particular ODBC class or,\n" + " if not specified, all classes.\n"; + return NULL; + case CLI_GENERATE: + if (a->pos != 2) + return NULL; + length = strlen(a->word); + AST_LIST_LOCK(&odbc_list); + AST_LIST_TRAVERSE(&odbc_list, class, list) { + if (!strncasecmp(a->word, class->name, length) && ++which > a->n) { + ret = ast_strdup(class->name); + break; + } + } + if (!ret && !strncasecmp(a->word, "all", length) && ++which > a->n) { + ret = ast_strdup("all"); + } + AST_LIST_UNLOCK(&odbc_list); + return ret; + } + + ast_cli(a->fd, "\nODBC DSN Settings\n"); + ast_cli(a->fd, "-----------------\n\n"); + AST_LIST_LOCK(&odbc_list); + AST_LIST_TRAVERSE(&odbc_list, class, list) { + if ((a->argc == 2) || (a->argc == 3 && !strcmp(a->argv[2], "all")) || (!strcmp(a->argv[2], class->name))) { + int count = 0; + ast_cli(a->fd, " Name: %s\n DSN: %s\n", class->name, class->dsn); + + if (class->haspool) { + ast_cli(a->fd, " Pooled: Yes\n Limit: %d\n Connections in use: %d\n", class->limit, class->count); + + AST_LIST_TRAVERSE(&(class->odbc_obj), current, list) { + ast_cli(a->fd, " - Connection %d: %s\n", ++count, current->used ? "in use" : current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected"); + } + } else { + /* Should only ever be one of these */ + AST_LIST_TRAVERSE(&(class->odbc_obj), current, list) { + ast_cli(a->fd, " Pooled: No\n Connected: %s\n", current->up && ast_odbc_sanity_check(current) ? "Yes" : "No"); + } + } + ast_cli(a->fd, "\n"); + } + } + AST_LIST_UNLOCK(&odbc_list); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_odbc[] = { + AST_CLI_DEFINE(handle_cli_odbc_show, "List ODBC DSN(s)") +}; + +static int odbc_register_class(struct odbc_class *class, int connect) +{ + struct odbc_obj *obj; + if (class) { + AST_LIST_LOCK(&odbc_list); + AST_LIST_INSERT_HEAD(&odbc_list, class, list); + AST_LIST_UNLOCK(&odbc_list); + + if (connect) { + /* Request and release builds a connection */ + obj = ast_odbc_request_obj(class->name, 0); + if (obj) + ast_odbc_release_obj(obj); + } + + return 0; + } else { + ast_log(LOG_WARNING, "Attempted to register a NULL class?\n"); + return -1; + } +} + +void ast_odbc_release_obj(struct odbc_obj *obj) +{ + /* For pooled connections, this frees the connection to be + * reused. For non-pooled connections, it does nothing. */ + obj->used = 0; +} + +int ast_odbc_backslash_is_escape(struct odbc_obj *obj) +{ + return obj->parent->backslash_is_escape; +} + +struct odbc_obj *ast_odbc_request_obj(const char *name, int check) +{ + struct odbc_obj *obj = NULL; + struct odbc_class *class; + + AST_LIST_LOCK(&odbc_list); + AST_LIST_TRAVERSE(&odbc_list, class, list) { + if (!strcmp(class->name, name)) + break; + } + AST_LIST_UNLOCK(&odbc_list); + + if (!class) + return NULL; + + AST_LIST_LOCK(&class->odbc_obj); + if (class->haspool) { + /* Recycle connections before building another */ + AST_LIST_TRAVERSE(&class->odbc_obj, obj, list) { + if (! obj->used) { + obj->used = 1; + break; + } + } + + if (!obj && (class->count < class->limit)) { + class->count++; + obj = ast_calloc(1, sizeof(*obj)); + if (!obj) { + AST_LIST_UNLOCK(&class->odbc_obj); + return NULL; + } + ast_mutex_init(&obj->lock); + obj->parent = class; + if (odbc_obj_connect(obj) == ODBC_FAIL) { + ast_log(LOG_WARNING, "Failed to connect to %s\n", name); + ast_mutex_destroy(&obj->lock); + ast_free(obj); + obj = NULL; + class->count--; + } else { + obj->used = 1; + AST_LIST_INSERT_TAIL(&class->odbc_obj, obj, list); + } + } + } else { + /* Non-pooled connection: multiple modules can use the same connection. */ + AST_LIST_TRAVERSE(&class->odbc_obj, obj, list) { + /* Non-pooled connection: if there is an entry, return it */ + break; + } + + if (!obj) { + /* No entry: build one */ + obj = ast_calloc(1, sizeof(*obj)); + if (!obj) { + AST_LIST_UNLOCK(&class->odbc_obj); + return NULL; + } + ast_mutex_init(&obj->lock); + obj->parent = class; + if (odbc_obj_connect(obj) == ODBC_FAIL) { + ast_log(LOG_WARNING, "Failed to connect to %s\n", name); + ast_mutex_destroy(&obj->lock); + ast_free(obj); + obj = NULL; + } else { + AST_LIST_INSERT_HEAD(&class->odbc_obj, obj, list); + } + } + } + AST_LIST_UNLOCK(&class->odbc_obj); + + if (obj && check) { + ast_odbc_sanity_check(obj); + } + return obj; +} + +static odbc_status odbc_obj_disconnect(struct odbc_obj *obj) +{ + int res; + ast_mutex_lock(&obj->lock); + + res = SQLDisconnect(obj->con); + + if (res == ODBC_SUCCESS) { + ast_log(LOG_WARNING, "res_odbc: disconnected %d from %s [%s]\n", res, obj->parent->name, obj->parent->dsn); + } else { + ast_log(LOG_WARNING, "res_odbc: %s [%s] already disconnected\n", + obj->parent->name, obj->parent->dsn); + } + obj->up = 0; + ast_mutex_unlock(&obj->lock); + return ODBC_SUCCESS; +} + +static odbc_status odbc_obj_connect(struct odbc_obj *obj) +{ + int res; + SQLINTEGER err; + short int mlen; + unsigned char msg[200], stat[10]; +#ifdef NEEDTRACE + SQLINTEGER enable = 1; + char *tracefile = "/tmp/odbc.trace"; +#endif + ast_mutex_lock(&obj->lock); + + res = SQLAllocHandle(SQL_HANDLE_DBC, obj->parent->env, &obj->con); + + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "res_odbc: Error AllocHDB %d\n", res); + ast_mutex_unlock(&obj->lock); + return ODBC_FAIL; + } + SQLSetConnectAttr(obj->con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *) 10, 0); + SQLSetConnectAttr(obj->con, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER *) 10, 0); +#ifdef NEEDTRACE + SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER); + SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile)); +#endif + + if (obj->up) { + odbc_obj_disconnect(obj); + ast_log(LOG_NOTICE, "Re-connecting %s\n", obj->parent->name); + } else { + ast_log(LOG_NOTICE, "Connecting %s\n", obj->parent->name); + } + + res = SQLConnect(obj->con, + (SQLCHAR *) obj->parent->dsn, SQL_NTS, + (SQLCHAR *) obj->parent->username, SQL_NTS, + (SQLCHAR *) obj->parent->password, SQL_NTS); + + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, stat, &err, msg, 100, &mlen); + ast_mutex_unlock(&obj->lock); + ast_log(LOG_WARNING, "res_odbc: Error SQLConnect=%d errno=%d %s\n", res, (int)err, msg); + return ODBC_FAIL; + } else { + ast_log(LOG_NOTICE, "res_odbc: Connected to %s [%s]\n", obj->parent->name, obj->parent->dsn); + obj->up = 1; + } + + ast_mutex_unlock(&obj->lock); + return ODBC_SUCCESS; +} + +static int reload(void) +{ + static char *cfg = "res_odbc.conf"; + struct ast_config *config; + struct ast_variable *v; + char *cat; + const char *dsn, *username, *password, *sanitysql; + int enabled, pooling, limit, bse; + int connect = 0, res = 0; + struct ast_flags config_flags = { CONFIG_FLAG_FILEUNCHANGED }; + + struct odbc_class *new, *class; + struct odbc_obj *current; + + /* First, mark all to be purged */ + AST_LIST_LOCK(&odbc_list); + AST_LIST_TRAVERSE(&odbc_list, class, list) { + class->delme = 1; + } + + config = ast_config_load(cfg, config_flags); + if (config != NULL && config != CONFIG_STATUS_FILEUNCHANGED) { + for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) { + if (!strcasecmp(cat, "ENV")) { + for (v = ast_variable_browse(config, cat); v; v = v->next) { + setenv(v->name, v->value, 1); + ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value); + } + } else { + char *freeme = NULL; + /* Reset all to defaults for each class of odbc connections */ + dsn = username = password = sanitysql = NULL; + enabled = 1; + connect = 0; + pooling = 0; + limit = 0; + bse = 1; + for (v = ast_variable_browse(config, cat); v; v = v->next) { + if (!strcasecmp(v->name, "pooling")) { + pooling = 1; + } else if (!strcasecmp(v->name, "limit")) { + sscanf(v->value, "%d", &limit); + if (ast_true(v->value) && !limit) { + ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'. Setting limit to 1023 for ODBC class '%s'.\n", v->value, cat); + limit = 1023; + } else if (ast_false(v->value)) { + ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'. Disabling ODBC class '%s'.\n", v->value, cat); + enabled = 0; + break; + } + } else if (!strcasecmp(v->name, "enabled")) { + enabled = ast_true(v->value); + } else if (!strcasecmp(v->name, "pre-connect")) { + connect = ast_true(v->value); + } else if (!strcasecmp(v->name, "dsn")) { + dsn = v->value; + } else if (!strcasecmp(v->name, "username")) { + username = v->value; + } else if (!strcasecmp(v->name, "password")) { + password = v->value; + } else if (!strcasecmp(v->name, "sanitysql")) { + sanitysql = v->value; + } else if (!strcasecmp(v->name, "backslash_is_escape")) { + bse = ast_true(v->value); + } + } + + if (enabled && !ast_strlen_zero(dsn)) { + /* First, check the list to see if it already exists */ + AST_LIST_TRAVERSE(&odbc_list, class, list) { + if (!strcmp(class->name, cat)) { + class->delme = 0; + break; + } + } + + if (class) { + new = class; + } else { + new = ast_calloc(1, sizeof(*new)); + } + + if (!new) { + res = -1; + break; + } + + if (cat) + ast_copy_string(new->name, cat, sizeof(new->name)); + if (dsn) + ast_copy_string(new->dsn, dsn, sizeof(new->dsn)); + + /* Safely replace username */ + if (class && class->username) + freeme = class->username; + if (username) + new->username = ast_strdup(username); + if (freeme) { + ast_free(freeme); + freeme = NULL; + } + + /* Safely replace password */ + if (class && class->password) + freeme = class->password; + if (password) + new->password = ast_strdup(password); + if (freeme) { + ast_free(freeme); + freeme = NULL; + } + + if (sanitysql) + ast_copy_string(new->sanitysql, sanitysql, sizeof(new->sanitysql)); + + if (!class) { + SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env); + res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0); + + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n"); + SQLFreeHandle(SQL_HANDLE_ENV, new->env); + AST_LIST_UNLOCK(&odbc_list); + return res; + } + } + + if (pooling) { + new->haspool = pooling; + if (limit) { + new->limit = limit; + } else { + ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless. Changing limit from 0 to 5.\n"); + new->limit = 5; + } + } + + new->backslash_is_escape = bse; + + if (class) { + ast_log(LOG_NOTICE, "Refreshing ODBC class '%s' dsn->[%s]\n", cat, dsn); + } else { + odbc_register_class(new, connect); + ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn); + } + } + } + } + ast_config_destroy(config); + } + + /* Purge classes that we know can go away (pooled with 0, only) */ + AST_LIST_TRAVERSE_SAFE_BEGIN(&odbc_list, class, list) { + if (class->delme && class->haspool && class->count == 0) { + while ((current = AST_LIST_REMOVE_HEAD(&class->odbc_obj, list))) { + odbc_obj_disconnect(current); + ast_mutex_destroy(¤t->lock); + ast_free(current); + } + + AST_LIST_REMOVE_CURRENT(list); + if (class->username) + ast_free(class->username); + if (class->password) + ast_free(class->password); + ast_free(class); + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&odbc_list); + + return 0; +} + +static int unload_module(void) +{ + /* Prohibit unloading */ + return -1; +} + +static int load_module(void) +{ + if (load_odbc_config() == -1) + return AST_MODULE_LOAD_DECLINE; + ast_cli_register_multiple(cli_odbc, sizeof(cli_odbc) / sizeof(struct ast_cli_entry)); + ast_log(LOG_NOTICE, "res_odbc loaded.\n"); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "ODBC resource", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); diff --git a/trunk/res/res_phoneprov.c b/trunk/res/res_phoneprov.c new file mode 100644 index 000000000..bffe1a2df --- /dev/null +++ b/trunk/res/res_phoneprov.c @@ -0,0 +1,1033 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2008, Digium, Inc. + * + * Mark Spencer <markster@digium.com> + * Matthew Brooks <mbrooks@digium.com> + * Terry Wilson <twilson@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \Phone provisioning application for the asterisk internal http server + * + * \author Matthew Brooks <mbrooks@digium.com> + * \author Terry Wilson <twilson@digium.com> + */ + +#include "asterisk.h" + +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <net/if.h> + +ASTERISK_FILE_VERSION(__FILE__, "$Revision: 96773 $") + +#include "asterisk/file.h" +#include "asterisk/paths.h" +#include "asterisk/pbx.h" +#include "asterisk/cli.h" +#include "asterisk/module.h" +#include "asterisk/http.h" +#include "asterisk/utils.h" +#include "asterisk/app.h" +#include "asterisk/strings.h" +#include "asterisk/stringfields.h" +#include "asterisk/options.h" +#include "asterisk/config.h" +#include "asterisk/acl.h" +#include "asterisk/astobj2.h" +#include "asterisk/version.h" + +#ifdef LOW_MEMORY +#define MAX_PROFILE_BUCKETS 1 +#define MAX_ROUTE_BUCKETS 1 +#else +#define MAX_PROFILE_BUCKETS 17 +#define MAX_ROUTE_BUCKETS 563 +#endif /* LOW_MEMORY */ + +#define VAR_BUF_SIZE 4096 + +/*! \brief for use in lookup_iface */ +static struct in_addr __ourip = { .s_addr = 0x00000000, }; + +/* \note This enum and the pp_variable_list must be in the same order or + * bad things happen! */ +enum pp_variables { + PP_MACADDRESS, + PP_USERNAME, + PP_FULLNAME, + PP_SECRET, + PP_LABEL, + PP_CALLERID, + PP_TIMEZONE, + PP_VAR_LIST_LENGTH, /* This entry must always be the last in the list */ +}; + +/*! \brief Lookup table to translate between users.conf property names and + * variables for use in phoneprov templates */ +static const struct pp_variable_lookup { + enum pp_variables id; + const char * const user_var; + const char * const template_var; +} pp_variable_list[] = { + { PP_MACADDRESS, "macaddress", "MAC" }, + { PP_USERNAME, "username", "USERNAME" }, + { PP_FULLNAME, "fullname", "DISPLAY_NAME" }, + { PP_SECRET, "secret", "SECRET" }, + { PP_LABEL, "label", "LABEL" }, + { PP_CALLERID, "cid_number", "CALLERID" }, + { PP_TIMEZONE, "timezone", "TIMEZONE" }, +}; + +/*! \brief structure to hold file data */ +struct phoneprov_file { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(format); /*!< After variable substitution, becomes route->uri */ + AST_STRING_FIELD(template); /*!< Template/physical file location */ + AST_STRING_FIELD(mime_type);/*!< Mime-type of the file */ + ); + AST_LIST_ENTRY(phoneprov_file) entry; +}; + +/*! \brief structure to hold phone profiles read from phoneprov.conf */ +struct phone_profile { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(name); /*!< Name of phone profile */ + AST_STRING_FIELD(default_mime_type); /*!< Default mime type if it isn't provided */ + AST_STRING_FIELD(staticdir); /*!< Subdirectory that static files are stored in */ + ); + struct varshead *headp; /*!< List of variables set with 'setvar' in phoneprov.conf */ + AST_LIST_HEAD_NOLOCK(, phoneprov_file) static_files; /*!< List of static files */ + AST_LIST_HEAD_NOLOCK(, phoneprov_file) dynamic_files; /*!< List of dynamic files */ +}; + +/*! \brief structure to hold users read from users.conf */ +struct user { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(name); /*!< Name of user */ + AST_STRING_FIELD(macaddress); /*!< Mac address of user's phone */ + ); + struct phone_profile *profile; /*!< Profile the phone belongs to */ + struct varshead *headp; /*!< List of variables to substitute into templates */ + AST_LIST_ENTRY(user) entry; +}; + +/*! \brief structure to hold http routes (valid URIs, and the files they link to) */ +struct http_route { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(uri); /*!< The URI requested */ + ); + struct phoneprov_file *file; /*!< The file that links to the URI */ + struct user *user; /*!< The user that has variables to substitute into the file + * NULL in the case of a static route */ +}; + +static struct ao2_container *profiles; +static struct ao2_container *http_routes; +static AST_RWLIST_HEAD_STATIC(users, user); + +/*! \brief Extensions whose mime types we think we know */ +static struct { + char *ext; + char *mtype; +} mimetypes[] = { + { "png", "image/png" }, + { "xml", "text/xml" }, + { "jpg", "image/jpeg" }, + { "js", "application/x-javascript" }, + { "wav", "audio/x-wav" }, + { "mp3", "audio/mpeg" }, +}; + +char global_server[80] = ""; /*!< Server to substitute into templates */ +char global_serverport[6] = ""; /*!< Server port to substitute into templates */ +char global_default_profile[80] = ""; /*!< Default profile to use if one isn't specified */ + +/*! \brief List of global variables currently available: VOICEMAIL_EXTEN, EXTENSION_LENGTH */ +struct varshead global_variables; + +/*! \brief Return mime type based on extension */ +static char *ftype2mtype(const char *ftype) +{ + int x; + + if (ast_strlen_zero(ftype)) + return NULL; + + for (x = 0;x < ARRAY_LEN(mimetypes);x++) { + if (!strcasecmp(ftype, mimetypes[x].ext)) + return mimetypes[x].mtype; + } + + return NULL; +} + +/* iface is the interface (e.g. eth0); address is the return value */ +static int lookup_iface(const char *iface, struct in_addr *address) +{ + int mysock, res = 0; + struct ifreq ifr; + struct sockaddr_in *sin; + + memset(&ifr, 0, sizeof(ifr)); + ast_copy_string(ifr.ifr_name, iface, sizeof(ifr.ifr_name)); + + mysock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + if (mysock < 0) { + ast_log(LOG_ERROR, "Failed to create socket: %s\n", strerror(errno)); + return -1; + } + + res = ioctl(mysock, SIOCGIFADDR, &ifr); + + close(mysock); + + if (res < 0) { + ast_log(LOG_WARNING, "Unable to get IP of %s: %s\n", iface, strerror(errno)); + memcpy(address, &__ourip, sizeof(__ourip)); + return -1; + } else { + sin = (struct sockaddr_in *)&ifr.ifr_addr; + memcpy(address, &sin->sin_addr, sizeof(*address)); + return 0; + } +} + +static struct phone_profile *unref_profile(struct phone_profile *prof) +{ + ao2_ref(prof, -1); + + return NULL; +} + +/*! \brief Return a phone profile looked up by name */ +static struct phone_profile *find_profile(const char *name) +{ + struct phone_profile tmp = { + .name = name, + }; + + return ao2_find(profiles, &tmp, OBJ_POINTER); +} + +static int profile_hash_fn(const void *obj, const int flags) +{ + const struct phone_profile *profile = obj; + + return ast_str_hash(profile->name); +} + +static int profile_cmp_fn(void *obj, void *arg, int flags) +{ + const struct phone_profile *profile1 = obj, *profile2 = arg; + + return !strcasecmp(profile1->name, profile2->name) ? CMP_MATCH : 0; +} + +static void delete_file(struct phoneprov_file *file) +{ + ast_string_field_free_memory(file); + free(file); +} + +static void profile_destructor(void *obj) +{ + struct phone_profile *profile = obj; + struct phoneprov_file *file; + struct ast_var_t *var; + + while ((file = AST_LIST_REMOVE_HEAD(&profile->static_files, entry))) + delete_file(file); + + while ((file = AST_LIST_REMOVE_HEAD(&profile->dynamic_files, entry))) + delete_file(file); + + while ((var = AST_LIST_REMOVE_HEAD(profile->headp, entries))) + ast_var_delete(var); + + free(profile->headp); + ast_string_field_free_memory(profile); +} + +static struct http_route *unref_route(struct http_route *route) +{ + ao2_ref(route, -1); + + return NULL; +} + +static int routes_hash_fn(const void *obj, const int flags) +{ + const struct http_route *route = obj; + + return ast_str_hash(route->uri); +} + +static int routes_cmp_fn(void *obj, void *arg, int flags) +{ + const struct http_route *route1 = obj, *route2 = arg; + + return !strcmp(route1->uri, route2->uri) ? CMP_MATCH : 0; +} + +static void route_destructor(void *obj) +{ + struct http_route *route = obj; + + ast_string_field_free_memory(route); +} + +/*! \brief Read a TEXT file into a string and return the length */ +static int load_file(const char *filename, char **ret) +{ + int len = 0; + FILE *f; + + if (!(f = fopen(filename, "r"))) { + *ret = NULL; + return -1; + } + + fseek(f, 0, SEEK_END); + len = ftell(f); + fseek(f, 0, SEEK_SET); + if (!(*ret = ast_malloc(len + 1))) + return -2; + + if (len != fread(*ret, sizeof(char), len, f)) { + free(*ret); + *ret = NULL; + return -3; + } + + fclose(f); + + (*ret)[len] = '\0'; + + return len; +} + +/*! \brief Callback that is executed everytime an http request is received by this module */ +static struct ast_str *phoneprov_callback(struct server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength) +{ + struct http_route *route; + struct http_route search_route = { + .uri = uri, + }; + struct ast_str *result = ast_str_create(512); + char path[PATH_MAX]; + char *file = NULL; + int len; + int fd; + char buf[256]; + struct timeval tv = ast_tvnow(); + struct ast_tm tm; + + if (!(route = ao2_find(http_routes, &search_route, OBJ_POINTER))) + goto out404; + + snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, route->file->template); + + if (!route->user) { /* Static file */ + + fd = open(path, O_RDONLY); + if (fd < 0) + goto out500; + + len = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + if (len < 0) { + ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len); + close(fd); + goto out500; + } + + ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT")); + fprintf(ser->f, "HTTP/1.1 200 OK\r\n" + "Server: Asterisk/%s\r\n" + "Date: %s\r\n" + "Connection: close\r\n" + "Cache-Control: no-cache, no-store\r\n" + "Content-Length: %d\r\n" + "Content-Type: %s\r\n\r\n", + ast_get_version(), buf, len, route->file->mime_type); + + while ((len = read(fd, buf, sizeof(buf))) > 0) + fwrite(buf, 1, len, ser->f); + + close(fd); + route = unref_route(route); + return NULL; + } else { /* Dynamic file */ + int bufsize; + char *tmp; + + len = load_file(path, &file); + if (len < 0) { + ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len); + if (file) + ast_free(file); + goto out500; + } + + if (!file) + goto out500; + + /* XXX This is a hack -- maybe sum length of all variables in route->user->headp and add that? */ + bufsize = len + VAR_BUF_SIZE; + + /* malloc() instead of alloca() here, just in case the file is bigger than + * we have enough stack space for. */ + if (!(tmp = ast_calloc(1, bufsize))) { + if (file) + ast_free(file); + goto out500; + } + + /* Unless we are overridden by serveriface or serveraddr, we set the SERVER variable to + * the IP address we are listening on that the phone contacted for this config file */ + if (ast_strlen_zero(global_server)) { + struct sockaddr name; + socklen_t namelen = sizeof(name); + int res; + + if ((res = getsockname(ser->fd, &name, &namelen))) + ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n"); + else { + struct ast_var_t *var; + + if ((var = ast_var_assign("SERVER", ast_inet_ntoa(((struct sockaddr_in *)&name)->sin_addr)))) + AST_LIST_INSERT_TAIL(route->user->headp, var, entries); + } + } + + pbx_substitute_variables_varshead(route->user->headp, file, tmp, bufsize); + + if (file) + ast_free(file); + + ast_str_append(&result, 0, + "Content-Type: %s\r\n" + "Content-length: %d\r\n" + "\r\n" + "%s", route->file->mime_type, (int) strlen(tmp), tmp); + + if (tmp) + ast_free(tmp); + + route = unref_route(route); + + return result; + } + +out404: + *status = 404; + *title = strdup("Not Found"); + *contentlength = 0; + return ast_http_error(404, "Not Found", NULL, "Nothing to see here. Move along."); + +out500: + route = unref_route(route); + *status = 500; + *title = strdup("Internal Server Error"); + *contentlength = 0; + return ast_http_error(500, "Internal Error", NULL, "An internal error has occured."); +} + +/*! \brief Build a route structure and add it to the list of available http routes + \param pp_file File to link to the route + \param user User to link to the route (NULL means static route) + \param uri URI of the route +*/ +static void build_route(struct phoneprov_file *pp_file, struct user *user, char *uri) +{ + struct http_route *route; + + if (!(route = ao2_alloc(sizeof(*route), route_destructor))) + return; + + if (ast_string_field_init(route, 32)) { + ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", pp_file->format); + route = unref_route(route); + return; + } + + ast_string_field_set(route, uri, S_OR(uri, pp_file->format)); + route->user = user; + route->file = pp_file; + + ao2_link(http_routes, route); + + route = unref_route(route); +} + +/*! \brief Build a phone profile and add it to the list of phone profiles + \param name the name of the profile + \param v ast_variable from parsing phoneprov.conf +*/ +static void build_profile(const char *name, struct ast_variable *v) +{ + struct phone_profile *profile; + struct ast_var_t *var; + + if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor))) + return; + + if (ast_string_field_init(profile, 32)) { + profile = unref_profile(profile); + return; + } + + if (!(profile->headp = ast_calloc(1, sizeof(*profile->headp)))) { + profile = unref_profile(profile); + return; + } + + AST_LIST_HEAD_INIT_NOLOCK(&profile->static_files); + AST_LIST_HEAD_INIT_NOLOCK(&profile->dynamic_files); + + ast_string_field_set(profile, name, name); + for (; v; v = v->next) { + if (!strcasecmp(v->name, "mime_type")) { + ast_string_field_set(profile, default_mime_type, v->value); + } else if (!strcasecmp(v->name, "setvar")) { + struct ast_var_t *var; + char *value_copy = ast_strdupa(v->value); + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(varname); + AST_APP_ARG(varval); + ); + + AST_NONSTANDARD_APP_ARGS(args, value_copy, '='); + do { + if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval)) + break; + args.varname = ast_strip(args.varname); + args.varval = ast_strip(args.varval); + if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval)) + break; + if ((var = ast_var_assign(args.varname, args.varval))) + AST_LIST_INSERT_TAIL(profile->headp, var, entries); + } while (0); + } else if (!strcasecmp(v->name, "staticdir")) { + ast_string_field_set(profile, staticdir, v->value); + } else { + struct phoneprov_file *pp_file; + char *file_extension; + char *value_copy = ast_strdupa(v->value); + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(filename); + AST_APP_ARG(mimetype); + ); + + if (!(pp_file = ast_calloc(1, sizeof(*pp_file)))) { + profile = unref_profile(profile); + return; + } + if (ast_string_field_init(pp_file, 32)) { + ast_free(pp_file); + profile = unref_profile(profile); + return; + } + + if ((file_extension = strrchr(pp_file->format, '.'))) + file_extension++; + + AST_STANDARD_APP_ARGS(args, value_copy); + + /* Mime type order of preference + * 1) Specific mime-type defined for file in profile + * 2) Mime determined by extension + * 3) Default mime type specified in profile + * 4) text/plain + */ + ast_string_field_set(pp_file, mime_type, S_OR(args.mimetype, (S_OR(S_OR(ftype2mtype(file_extension), profile->default_mime_type), "text/plain")))); + + if (!strcasecmp(v->name, "static_file")) { + ast_string_field_set(pp_file, format, args.filename); + ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename); + AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry); + /* Add a route for the static files, as their filenames won't change per-user */ + build_route(pp_file, NULL, NULL); + } else { + ast_string_field_set(pp_file, format, v->name); + ast_string_field_set(pp_file, template, args.filename); + AST_LIST_INSERT_TAIL(&profile->dynamic_files, pp_file, entry); + } + } + } + + /* Append the global variables to the variables list for this profile. + * This is for convenience later, when we need to provide a single + * variable list for use in substitution. */ + AST_LIST_TRAVERSE(&global_variables, var, entries) { + struct ast_var_t *new_var; + if ((new_var = ast_var_assign(var->name, var->value))) + AST_LIST_INSERT_TAIL(profile->headp, new_var, entries); + } + + ao2_link(profiles, profile); + + profile = unref_profile(profile); +} + +/*! \brief Free all memory associated with a user */ +static void delete_user(struct user *user) +{ + struct ast_var_t *var; + + while ((var = AST_LIST_REMOVE_HEAD(user->headp, entries))) + ast_var_delete(var); + + ast_free(user->headp); + ast_string_field_free_memory(user); + user->profile = unref_profile(user->profile); + free(user); +} + +/*! \brief Destroy entire user list */ +static void delete_users(void) +{ + struct user *user; + + AST_RWLIST_WRLOCK(&users); + while ((user = AST_LIST_REMOVE_HEAD(&users, entry))) + delete_user(user); + AST_RWLIST_UNLOCK(&users); +} + +/*! \brief Set all timezone-related variables based on a zone (i.e. America/New_York) + \param headp pointer to list of user variables + \param zone A time zone. NULL sets variables based on timezone of the machine +*/ +static void set_timezone_variables(struct varshead *headp, const char *zone) +{ + time_t utc_time; + int dstenable; + time_t dststart; + time_t dstend; + struct ast_tm tm_info; + int tzoffset; + char buffer[21]; + struct ast_var_t *var; + struct timeval tv; + + time(&utc_time); + ast_get_dst_info(&utc_time, &dstenable, &dststart, &dstend, &tzoffset, zone); + snprintf(buffer, sizeof(buffer), "%d", tzoffset); + var = ast_var_assign("TZOFFSET", buffer); + if (var) + AST_LIST_INSERT_TAIL(headp, var, entries); + + if (!dstenable) + return; + + if ((var = ast_var_assign("DST_ENABLE", "1"))) + AST_LIST_INSERT_TAIL(headp, var, entries); + + tv.tv_sec = dststart; + ast_localtime(&tv, &tm_info, zone); + + snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon+1); + if ((var = ast_var_assign("DST_START_MONTH", buffer))) + AST_LIST_INSERT_TAIL(headp, var, entries); + + snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday); + if ((var = ast_var_assign("DST_START_MDAY", buffer))) + AST_LIST_INSERT_TAIL(headp, var, entries); + + snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour); + if ((var = ast_var_assign("DST_START_HOUR", buffer))) + AST_LIST_INSERT_TAIL(headp, var, entries); + + tv.tv_sec = dstend; + ast_localtime(&tv, &tm_info, zone); + + snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon + 1); + if ((var = ast_var_assign("DST_END_MONTH", buffer))) + AST_LIST_INSERT_TAIL(headp, var, entries); + + snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday); + if ((var = ast_var_assign("DST_END_MDAY", buffer))) + AST_LIST_INSERT_TAIL(headp, var, entries); + + snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour); + if ((var = ast_var_assign("DST_END_HOUR", buffer))) + AST_LIST_INSERT_TAIL(headp, var, entries); +} + +/*! \brief Build and return a user structure based on gathered config data */ +static struct user *build_user(struct ast_config *cfg, const char *name, const char *mac, struct phone_profile *profile) +{ + struct user *user; + struct ast_var_t *var; + const char *tmp; + int i; + + if (!(user = ast_calloc(1, sizeof(*user)))) { + profile = unref_profile(profile); + return NULL; + } + + if (!(user->headp = ast_calloc(1, sizeof(*user->headp)))) { + profile = unref_profile(profile); + ast_free(user); + return NULL; + } + + if (ast_string_field_init(user, 32)) { + profile = unref_profile(profile); + delete_user(user); + return NULL; + } + + ast_string_field_set(user, name, name); + ast_string_field_set(user, macaddress, mac); + user->profile = profile; /* already ref counted by find_profile */ + + for (i = 0; i < PP_VAR_LIST_LENGTH; i++) { + tmp = ast_variable_retrieve(cfg, name, pp_variable_list[i].user_var); + + if (i == PP_TIMEZONE) { + /* perfectly ok if tmp is NULL, will set variables based on server's time zone */ + set_timezone_variables(user->headp, tmp); + } + + if (tmp && (var = ast_var_assign(pp_variable_list[i].template_var, tmp))) + AST_LIST_INSERT_TAIL(user->headp, var, entries); + } + + if (!ast_strlen_zero(global_server)) { + if ((var = ast_var_assign("SERVER", global_server))) + AST_LIST_INSERT_TAIL(user->headp, var, entries); + } + + if (!ast_strlen_zero(global_serverport)) { + if ((var = ast_var_assign("SERVER_PORT", global_serverport))) + AST_LIST_INSERT_TAIL(user->headp, var, entries); + } + + /* Append profile variables here, and substitute variables on profile + * setvars, so that we can use user specific variables in them */ + AST_LIST_TRAVERSE(user->profile->headp, var, entries) { + char expand_buf[VAR_BUF_SIZE] = {0,}; + struct ast_var_t *var2; + + pbx_substitute_variables_varshead(user->headp, var->value, expand_buf, sizeof(expand_buf)); + if ((var2 = ast_var_assign(var->name, expand_buf))) + AST_LIST_INSERT_TAIL(user->headp, var2, entries); + } + + return user; +} + +/*! \brief Add an http route for dynamic files attached to the profile of the user */ +static int build_user_routes(struct user *user) +{ + struct phoneprov_file *pp_file; + + AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) { + char expand_buf[VAR_BUF_SIZE] = { 0, }; + + pbx_substitute_variables_varshead(user->headp, pp_file->format, expand_buf, sizeof(expand_buf)); + build_route(pp_file, user, expand_buf); + } + + return 0; +} + +/* \brief Parse config files and create appropriate structures */ +static int set_config(void) +{ + struct ast_config *cfg; + char *cat; + struct ast_variable *v; + struct ast_flags config_flags = { 0 }; + + /* Try to grab the port from sip.conf. If we don't get it here, we'll set it + * to whatever is set in phoneprov.conf or default to 5060 */ + if ((cfg = ast_config_load("sip.conf", config_flags))) { + ast_copy_string(global_serverport, S_OR(ast_variable_retrieve(cfg, "general", "bindport"), "5060"), sizeof(global_serverport)); + ast_config_destroy(cfg); + } + + if (!(cfg = ast_config_load("phoneprov.conf", config_flags))) { + ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n"); + return -1; + } + + cat = NULL; + while ((cat = ast_category_browse(cfg, cat))) { + if (!strcasecmp(cat, "general")) { + for (v = ast_variable_browse(cfg, cat); v; v = v->next) { + if (!strcasecmp(v->name, "serveraddr")) + ast_copy_string(global_server, v->value, sizeof(global_server)); + else if (!strcasecmp(v->name, "serveriface")) { + struct in_addr addr; + lookup_iface(v->value, &addr); + ast_copy_string(global_server, ast_inet_ntoa(addr), sizeof(global_server)); + } else if (!strcasecmp(v->name, "serverport")) + ast_copy_string(global_serverport, v->value, sizeof(global_serverport)); + else if (!strcasecmp(v->name, "default_profile")) + ast_copy_string(global_default_profile, v->value, sizeof(global_default_profile)); + } + } else + build_profile(cat, ast_variable_browse(cfg, cat)); + } + + ast_config_destroy(cfg); + + if (!(cfg = ast_config_load("users.conf", config_flags))) { + ast_log(LOG_WARNING, "Unable to load users.cfg\n"); + return 0; + } + + cat = NULL; + while ((cat = ast_category_browse(cfg, cat))) { + const char *tmp, *mac; + struct user *user; + struct phone_profile *profile; + struct ast_var_t *var; + + if (!strcasecmp(cat, "general")) { + for (v = ast_variable_browse(cfg, cat); v; v = v->next) { + if (!strcasecmp(v->name, "vmexten")) { + if ((var = ast_var_assign("VOICEMAIL_EXTEN", v->value))) + AST_LIST_INSERT_TAIL(&global_variables, var, entries); + } + if (!strcasecmp(v->name, "localextenlength")) { + if ((var = ast_var_assign("EXTENSION_LENGTH", v->value))) + AST_LIST_INSERT_TAIL(&global_variables, var, entries); + } + } + } + + if (!strcasecmp(cat, "authentication")) + continue; + + if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp))) + continue; + + if (!(mac = ast_variable_retrieve(cfg, cat, "macaddress"))) { + ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat); + continue; + } + + tmp = S_OR(ast_variable_retrieve(cfg, cat, "profile"), global_default_profile); + if (ast_strlen_zero(tmp)) { + ast_log(LOG_WARNING, "No profile for user [%s] with mac '%s' - skipping\n", cat, mac); + continue; + } + + if (!(profile = find_profile(tmp))) { + ast_log(LOG_WARNING, "Could not look up profile '%s' - skipping.\n", tmp); + continue; + } + + if (!(user = build_user(cfg, cat, mac, profile))) { + ast_log(LOG_WARNING, "Could not create user %s - skipping.\n", cat); + continue; + } + + if (build_user_routes(user)) { + ast_log(LOG_WARNING, "Could not create http routes for %s - skipping\n", user->name); + delete_user(user); + continue; + } + + AST_RWLIST_WRLOCK(&users); + AST_RWLIST_INSERT_TAIL(&users, user, entry); + AST_RWLIST_UNLOCK(&users); + } + + ast_config_destroy(cfg); + + return 0; +} + +/*! \brief Delete all http routes, freeing their memory */ +static void delete_routes(void) +{ + struct ao2_iterator i; + struct http_route *route; + + i = ao2_iterator_init(http_routes, 0); + while ((route = ao2_iterator_next(&i))) { + ao2_unlink(http_routes, route); + route = unref_route(route); + } +} + +/*! \brief Delete all phone profiles, freeing their memory */ +static void delete_profiles(void) +{ + struct ao2_iterator i; + struct phone_profile *profile; + + i = ao2_iterator_init(profiles, 0); + while ((profile = ao2_iterator_next(&i))) { + ao2_unlink(profiles, profile); + profile = unref_profile(profile); + } +} + +/*! \brief A dialplan function that can be used to print a string for each phoneprov user */ +static int pp_each_user_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +{ + char *tmp, expand_buf[VAR_BUF_SIZE] = {0,}; + struct user *user; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(string); + AST_APP_ARG(exclude_mac); + ); + AST_STANDARD_APP_ARGS(args, data); + + /* Fix data by turning %{ into ${ */ + while ((tmp = strstr(args.string, "%{"))) + *tmp = '$'; + + AST_RWLIST_RDLOCK(&users); + AST_RWLIST_TRAVERSE(&users, user, entry) { + if (!ast_strlen_zero(args.exclude_mac) && !strcasecmp(user->macaddress, args.exclude_mac)) + continue; + pbx_substitute_variables_varshead(user->headp, args.string, expand_buf, sizeof(expand_buf)); + ast_build_string(&buf, &len, expand_buf); + } + AST_RWLIST_UNLOCK(&users); + + return 0; +} + +static struct ast_custom_function pp_each_user_function = { + .name = "PP_EACH_USER", + .synopsis = "Generate a string for each phoneprov user", + .syntax = "PP_EACH_USER(<string>|<exclude_mac>)", + .desc = + "Pass in a string, with phoneprov variables you want substituted in the format of\n" + "%{VARNAME}, and you will get the string rendered for each user in phoneprov\n" + "excluding ones with MAC address <exclude_mac>. Probably not useful outside of\n" + "res_phoneprov.\n" + "\nExample: ${PP_EACH_USER(<item><fn>%{DISPLAY_NAME}</fn></item>|${MAC})", + .read = pp_each_user_exec, +}; + +/*! \brief CLI command to list static and dynamic routes */ +static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ +#define FORMAT "%-40.40s %-30.30s\n" + struct ao2_iterator i; + struct http_route *route; + + switch(cmd) { + case CLI_INIT: + e->command = "phoneprov show routes"; + e->usage = + "Usage: phoneprov show routes\n" + " Lists all registered phoneprov http routes.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + /* This currently iterates over routes twice, but it is the only place I've needed + * to really separate static an dynamic routes, so I've just left it this way. */ + ast_cli(a->fd, "Static routes\n\n"); + ast_cli(a->fd, FORMAT, "Relative URI", "Physical location"); + i = ao2_iterator_init(http_routes, 0); + while ((route = ao2_iterator_next(&i))) { + if (!route->user) + ast_cli(a->fd, FORMAT, route->uri, route->file->template); + route = unref_route(route); + } + + ast_cli(a->fd, "\nDynamic routes\n\n"); + ast_cli(a->fd, FORMAT, "Relative URI", "Template"); + + i = ao2_iterator_init(http_routes, 0); + while ((route = ao2_iterator_next(&i))) { + if (route->user) + ast_cli(a->fd, FORMAT, route->uri, route->file->template); + route = unref_route(route); + } + + return CLI_SUCCESS; +} + +static struct ast_cli_entry pp_cli[] = { + AST_CLI_DEFINE(handle_show_routes, "Show registered phoneprov http routes"), +}; + +static struct ast_http_uri phoneprovuri = { + .callback = phoneprov_callback, + .description = "Asterisk HTTP Phone Provisioning Tool", + .uri = "phoneprov", + .has_subtree = 1, +}; + +static int load_module(void) +{ + profiles = ao2_container_alloc(MAX_PROFILE_BUCKETS, profile_hash_fn, profile_cmp_fn); + + http_routes = ao2_container_alloc(MAX_ROUTE_BUCKETS, routes_hash_fn, routes_cmp_fn); + + AST_LIST_HEAD_INIT_NOLOCK(&global_variables); + + ast_custom_function_register(&pp_each_user_function); + ast_cli_register_multiple(pp_cli, ARRAY_LEN(pp_cli)); + + set_config(); + ast_http_uri_link(&phoneprovuri); + + return 0; +} + +static int unload_module(void) +{ + struct ast_var_t *var; + + ast_http_uri_unlink(&phoneprovuri); + ast_custom_function_unregister(&pp_each_user_function); + ast_cli_unregister_multiple(pp_cli, ARRAY_LEN(pp_cli)); + + delete_routes(); + delete_users(); + delete_profiles(); + ao2_ref(profiles, -1); + ao2_ref(http_routes, -1); + + while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries))) + ast_var_delete(var); + + return 0; +} + +static int reload(void) +{ + delete_routes(); + delete_users(); + delete_profiles(); + set_config(); + + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "HTTP Phone Provisioning", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); diff --git a/trunk/res/res_realtime.c b/trunk/res/res_realtime.c new file mode 100644 index 000000000..14ad28c40 --- /dev/null +++ b/trunk/res/res_realtime.c @@ -0,0 +1,129 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Anthony Minessale <anthmct@yahoo.com> + * Mark Spencer <markster@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief RealTime CLI + * + * \author Anthony Minessale <anthmct@yahoo.com> + * \author Mark Spencer <markster@digium.com> + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/file.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/config.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/cli.h" + + +static char *cli_realtime_load(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + char *header_format = "%30s %-30s\n"; + struct ast_variable *var=NULL; + + switch (cmd) { + case CLI_INIT: + e->command = "realtime load"; + e->usage = + "Usage: realtime load <family> <colmatch> <value>\n" + " Prints out a list of variables using the RealTime driver.\n" + " You must supply a family name, a column to match on, and a value to match to.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + + if (a->argc < 5) + return CLI_SHOWUSAGE; + + var = ast_load_realtime_all(a->argv[2], a->argv[3], a->argv[4], NULL); + + if (var) { + ast_cli(a->fd, header_format, "Column Name", "Column Value"); + ast_cli(a->fd, header_format, "--------------------", "--------------------"); + while (var) { + ast_cli(a->fd, header_format, var->name, var->value); + var = var->next; + } + } else { + ast_cli(a->fd, "No rows found matching search criteria.\n"); + } + return CLI_SUCCESS; +} + +static char *cli_realtime_update(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { + int res = 0; + + switch (cmd) { + case CLI_INIT: + e->command = "realtime update"; + e->usage = + "Usage: realtime update <family> <colupdate> <newvalue> <colmatch> <valuematch>\n" + " Update a single variable using the RealTime driver.\n" + " You must supply a family name, a column to update on, a new value, column to match, and value to match.\n" + " Ex: realtime update sipfriends name bobsphone port 4343\n" + " will execute SQL as UPDATE sipfriends SET port = 4343 WHERE name = bobsphone\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + + if (a->argc < 7) + return CLI_SHOWUSAGE; + + res = ast_update_realtime(a->argv[2], a->argv[3], a->argv[4], a->argv[5], a->argv[6], NULL); + + if(res < 0) { + ast_cli(a->fd, "Failed to update. Check the debug log for possible SQL related entries.\n"); + return CLI_FAILURE; + } + + ast_cli(a->fd, "Updated %d RealTime record%s.\n", res, ESS(res)); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_realtime[] = { + AST_CLI_DEFINE(cli_realtime_load, "Used to print out RealTime variables."), + AST_CLI_DEFINE(cli_realtime_update, "Used to update RealTime variables."), +}; + +static int unload_module(void) +{ + ast_cli_unregister_multiple(cli_realtime, sizeof(cli_realtime) / sizeof(struct ast_cli_entry)); + return 0; +} + +static int load_module(void) +{ + ast_cli_register_multiple(cli_realtime, sizeof(cli_realtime) / sizeof(struct ast_cli_entry)); + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Realtime Data Lookup/Rewrite"); diff --git a/trunk/res/res_smdi.c b/trunk/res/res_smdi.c new file mode 100644 index 000000000..1df720e83 --- /dev/null +++ b/trunk/res/res_smdi.c @@ -0,0 +1,746 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Copyright (C) 2005-2006, Digium, Inc. + * + * Matthew A. Nicholson <mnicholson@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief SMDI support for Asterisk. + * \author Matthew A. Nicholson <mnicholson@digium.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <termios.h> +#include <sys/time.h> +#include <time.h> +#include <ctype.h> + +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/utils.h" +#include "asterisk/smdi.h" +#include "asterisk/config.h" +#include "asterisk/astobj.h" +#include "asterisk/io.h" + +/* Message expiry time in milliseconds */ +#define SMDI_MSG_EXPIRY_TIME 30000 /* 30 seconds */ + +static const char config_file[] = "smdi.conf"; + +static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *msg); +static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *msg); + +static void *smdi_read(void *iface_p); +static int smdi_load(int reload); + +struct module_symbols *me; /* initialized in load_module() */ + +/*! \brief SMDI interface container. */ +struct ast_smdi_interface_container { + ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface); +} smdi_ifaces; + +/*! + * \internal + * \brief Push an SMDI message to the back of an interface's message queue. + * \param iface a pointer to the interface to use. + * \param md_msg a pointer to the message to use. + */ +static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg) +{ + ASTOBJ_CONTAINER_LINK_END(&iface->md_q, md_msg); +} + +/*! + * \internal + * \brief Push an SMDI message to the back of an interface's message queue. + * \param iface a pointer to the interface to use. + * \param mwi_msg a pointer to the message to use. + */ +static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg) +{ + ASTOBJ_CONTAINER_LINK_END(&iface->mwi_q, mwi_msg); +} + +/*! + * \brief Set the MWI indicator for a mailbox. + * \param iface the interface to use. + * \param mailbox the mailbox to use. + */ +int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox) +{ + FILE *file; + int i; + + if (!(file = fopen(iface->name, "w"))) { + ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno)); + return 1; + } + + ASTOBJ_WRLOCK(iface); + + fprintf(file, "OP:MWI "); + + for (i = 0; i < iface->msdstrip; i++) + fprintf(file, "0"); + + fprintf(file, "%s!\x04", mailbox); + fclose(file); + + ASTOBJ_UNLOCK(iface); + ast_debug(1, "Sent MWI set message for %s on %s\n", mailbox, iface->name); + return 0; +} + +/*! + * \brief Unset the MWI indicator for a mailbox. + * \param iface the interface to use. + * \param mailbox the mailbox to use. + */ +int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox) +{ + FILE *file; + int i; + + if (!(file = fopen(iface->name, "w"))) { + ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno)); + return 1; + } + + ASTOBJ_WRLOCK(iface); + + fprintf(file, "RMV:MWI "); + + for (i = 0; i < iface->msdstrip; i++) + fprintf(file, "0"); + + fprintf(file, "%s!\x04", mailbox); + fclose(file); + + ASTOBJ_UNLOCK(iface); + ast_debug(1, "Sent MWI unset message for %s on %s\n", mailbox, iface->name); + return 0; +} + +/*! + * \brief Put an SMDI message back in the front of the queue. + * \param iface a pointer to the interface to use. + * \param md_msg a pointer to the message to use. + * + * This function puts a message back in the front of the specified queue. It + * should be used if a message was popped but is not going to be processed for + * some reason, and the message needs to be returned to the queue. + */ +void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg) +{ + ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg); +} + +/*! + * \brief Put an SMDI message back in the front of the queue. + * \param iface a pointer to the interface to use. + * \param mwi_msg a pointer to the message to use. + * + * This function puts a message back in the front of the specified queue. It + * should be used if a message was popped but is not going to be processed for + * some reason, and the message needs to be returned to the queue. + */ +void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg) +{ + ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_msg); +} + +/*! + * \brief Get the next SMDI message from the queue. + * \param iface a pointer to the interface to use. + * + * This function pulls the first unexpired message from the SMDI message queue + * on the specified interface. It will purge all expired SMDI messages before + * returning. + * + * \return the next SMDI message, or NULL if there were no pending messages. + */ +struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface) +{ + struct ast_smdi_md_message *md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q); + struct timeval now; + long elapsed = 0; + + /* purge old messages */ + now = ast_tvnow(); + while (md_msg) { + elapsed = ast_tvdiff_ms(now, md_msg->timestamp); + + if (elapsed > iface->msg_expiry) { + /* found an expired message */ + ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy); + ast_log(LOG_NOTICE, "Purged expired message from %s SMDI MD message queue. Message was %ld milliseconds too old.\n", + iface->name, elapsed - iface->msg_expiry); + md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q); + } + else { + /* good message, return it */ + break; + } + } + + return md_msg; +} + +/*! + * \brief Get the next SMDI message from the queue. + * \param iface a pointer to the interface to use. + * \param timeout the time to wait before returning in milliseconds. + * + * This function pulls a message from the SMDI message queue on the specified + * interface. If no message is available this function will wait the specified + * amount of time before returning. + * + * \return the next SMDI message, or NULL if there were no pending messages and + * the timeout has expired. + */ +extern struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout) +{ + struct timeval start = ast_tvnow(); + long diff = 0; + struct ast_smdi_md_message *msg; + + while (diff < timeout) { + + if ((msg = ast_smdi_md_message_pop(iface))) + return msg; + + /* check timeout */ + diff = ast_tvdiff_ms(ast_tvnow(), start); + } + + return (ast_smdi_md_message_pop(iface)); +} + +/*! + * \brief Get the next SMDI message from the queue. + * \param iface a pointer to the interface to use. + * + * This function pulls the first unexpired message from the SMDI message queue + * on the specified interface. It will purge all expired SMDI messages before + * returning. + * + * \return the next SMDI message, or NULL if there were no pending messages. + */ +extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface) +{ + struct ast_smdi_mwi_message *mwi_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q); + struct timeval now = ast_tvnow(); + long elapsed = 0; + + /* purge old messages */ + while (mwi_msg) { + elapsed = ast_tvdiff_ms(now, mwi_msg->timestamp); + + if (elapsed > iface->msg_expiry) { + /* found an expired message */ + ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy); + ast_log(LOG_NOTICE, "Purged expired message from %s SMDI MWI message queue. Message was %ld milliseconds too old.\n", + iface->name, elapsed - iface->msg_expiry); + mwi_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q); + } + else { + /* good message, return it */ + break; + } + } + + return mwi_msg; +} + +/*! + * \brief Get the next SMDI message from the queue. + * \param iface a pointer to the interface to use. + * \param timeout the time to wait before returning in milliseconds. + * + * This function pulls a message from the SMDI message queue on the specified + * interface. If no message is available this function will wait the specified + * amount of time before returning. + * + * \return the next SMDI message, or NULL if there were no pending messages and + * the timeout has expired. + */ +extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout) +{ + struct timeval start = ast_tvnow(); + long diff = 0; + struct ast_smdi_mwi_message *msg; + + while (diff < timeout) { + + if ((msg = ast_smdi_mwi_message_pop(iface))) + return msg; + + /* check timeout */ + diff = ast_tvdiff_ms(ast_tvnow(), start); + } + + return (ast_smdi_mwi_message_pop(iface)); +} + +/*! + * \brief Find an SMDI interface with the specified name. + * \param iface_name the name/port of the interface to search for. + * + * \return a pointer to the interface located or NULL if none was found. This + * actually returns an ASTOBJ reference and should be released using + * #ASTOBJ_UNREF(iface, ast_smdi_interface_destroy). + */ +extern struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name) +{ + return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name)); +} + +/*! \brief Read an SMDI message. + * + * \param iface_p the SMDI interface to read from. + * + * This function loops and reads from and SMDI interface. It must be stopped + * using pthread_cancel(). + */ +static void *smdi_read(void *iface_p) +{ + struct ast_smdi_interface *iface = iface_p; + struct ast_smdi_md_message *md_msg; + struct ast_smdi_mwi_message *mwi_msg; + char c = '\0'; + char *cp = NULL; + int i; + int start = 0; + + /* read an smdi message */ + while ((c = fgetc(iface->file))) { + + /* check if this is the start of a message */ + if (!start) { + if (c == 'M') + start = 1; + } + else { /* Determine if this is a MD or MWI message */ + if(c == 'D') { /* MD message */ + start = 0; + + if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) { + ASTOBJ_UNREF(iface,ast_smdi_interface_destroy); + return NULL; + } + + ASTOBJ_INIT(md_msg); + + /* read the message desk number */ + for(i = 0; i < SMDI_MESG_DESK_NUM_LEN; i++) + md_msg->mesg_desk_num[i] = fgetc(iface->file); + + md_msg->mesg_desk_num[SMDI_MESG_DESK_NUM_LEN] = '\0'; + + /* read the message desk terminal number */ + for(i = 0; i < SMDI_MESG_DESK_TERM_LEN; i++) + md_msg->mesg_desk_term[i] = fgetc(iface->file); + + md_msg->mesg_desk_term[SMDI_MESG_DESK_TERM_LEN] = '\0'; + + /* read the message type */ + md_msg->type = fgetc(iface->file); + + /* read the forwarding station number (may be blank) */ + cp = &md_msg->fwd_st[0]; + for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) { + if((c = fgetc(iface->file)) == ' ') { + *cp = '\0'; + break; + } + + /* store c in md_msg->fwd_st */ + if( i >= iface->msdstrip) + *cp++ = c; + } + + /* make sure the value is null terminated, even if this truncates it */ + md_msg->fwd_st[SMDI_MAX_STATION_NUM_LEN] = '\0'; + cp = NULL; + + /* read the calling station number (may be blank) */ + cp = &md_msg->calling_st[0]; + for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) { + if (!isdigit((c = fgetc(iface->file)))) { + *cp = '\0'; + break; + } + + /* store c in md_msg->calling_st */ + if (i >= iface->msdstrip) + *cp++ = c; + } + + /* make sure the value is null terminated, even if this truncates it */ + md_msg->calling_st[SMDI_MAX_STATION_NUM_LEN] = '\0'; + cp = NULL; + + /* add the message to the message queue */ + md_msg->timestamp = ast_tvnow(); + ast_smdi_md_message_push(iface, md_msg); + ast_debug(1, "Recieved SMDI MD message on %s\n", iface->name); + + ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy); + + } else if(c == 'W') { /* MWI message */ + start = 0; + + if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) { + ASTOBJ_UNREF(iface,ast_smdi_interface_destroy); + return NULL; + } + + ASTOBJ_INIT(mwi_msg); + + /* discard the 'I' (from 'MWI') */ + fgetc(iface->file); + + /* read the forwarding station number (may be blank) */ + cp = &mwi_msg->fwd_st[0]; + for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) { + if ((c = fgetc(iface->file)) == ' ') { + *cp = '\0'; + break; + } + + /* store c in md_msg->fwd_st */ + if (i >= iface->msdstrip) + *cp++ = c; + } + + /* make sure the station number is null terminated, even if this will truncate it */ + mwi_msg->fwd_st[SMDI_MAX_STATION_NUM_LEN] = '\0'; + cp = NULL; + + /* read the mwi failure cause */ + for (i = 0; i < SMDI_MWI_FAIL_CAUSE_LEN; i++) + mwi_msg->cause[i] = fgetc(iface->file); + + mwi_msg->cause[SMDI_MWI_FAIL_CAUSE_LEN] = '\0'; + + /* add the message to the message queue */ + mwi_msg->timestamp = ast_tvnow(); + ast_smdi_mwi_message_push(iface, mwi_msg); + ast_debug(1, "Recieved SMDI MWI message on %s\n", iface->name); + + ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy); + } else { + ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c); + start = 0; + } + } + } + + ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name); + ASTOBJ_UNREF(iface,ast_smdi_interface_destroy); + return NULL; +} + +/*! \brief ast_smdi_md_message destructor. */ +void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg) +{ + ast_free(msg); +} + +/*! \brief ast_smdi_mwi_message destructor. */ +void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg) +{ + ast_free(msg); +} + +/*! \brief ast_smdi_interface destructor. */ +void ast_smdi_interface_destroy(struct ast_smdi_interface *iface) +{ + if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) { + pthread_cancel(iface->thread); + pthread_join(iface->thread, NULL); + } + + iface->thread = AST_PTHREADT_STOP; + + if(iface->file) + fclose(iface->file); + + ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy); + ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy); + ASTOBJ_CONTAINER_DESTROY(&iface->md_q); + ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q); + ast_free(iface); + + ast_module_unref(ast_module_info->self); +} + +/*! + * \internal + * \brief Load and reload SMDI configuration. + * \param reload this should be 1 if we are reloading and 0 if not. + * + * This function loads/reloads the SMDI configuration and starts and stops + * interfaces accordingly. + * + * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started. + */ +static int smdi_load(int reload) +{ + struct ast_config *conf; + struct ast_variable *v; + struct ast_smdi_interface *iface = NULL; + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; + int res = 0; + + /* Config options */ + speed_t baud_rate = B9600; /* 9600 baud rate */ + tcflag_t paritybit = PARENB; /* even parity checking */ + tcflag_t charsize = CS7; /* seven bit characters */ + int stopbits = 0; /* One stop bit */ + + int msdstrip = 0; /* strip zero digits */ + long msg_expiry = SMDI_MSG_EXPIRY_TIME; + + if (!(conf = ast_config_load(config_file, config_flags))) { + if (reload) + ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file); + else + ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file); + return 1; + } else if (conf == CONFIG_STATUS_FILEUNCHANGED) + return 0; + + /* Mark all interfaces that we are listening on. We will unmark them + * as we find them in the config file, this way we know any interfaces + * still marked after we have finished parsing the config file should + * be stopped. + */ + if (reload) + ASTOBJ_CONTAINER_MARKALL(&smdi_ifaces); + + for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) { + if (!strcasecmp(v->name, "baudrate")) { + if (!strcasecmp(v->value, "9600")) + baud_rate = B9600; + else if(!strcasecmp(v->value, "4800")) + baud_rate = B4800; + else if(!strcasecmp(v->value, "2400")) + baud_rate = B2400; + else if(!strcasecmp(v->value, "1200")) + baud_rate = B1200; + else { + ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno); + baud_rate = B9600; + } + } else if (!strcasecmp(v->name, "msdstrip")) { + if (!sscanf(v->value, "%d", &msdstrip)) { + ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno); + msdstrip = 0; + } else if (0 > msdstrip || msdstrip > 9) { + ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno); + msdstrip = 0; + } + } else if (!strcasecmp(v->name, "msgexpirytime")) { + if (!sscanf(v->value, "%ld", &msg_expiry)) { + ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno); + msg_expiry = SMDI_MSG_EXPIRY_TIME; + } + } else if (!strcasecmp(v->name, "paritybit")) { + if (!strcasecmp(v->value, "even")) + paritybit = PARENB; + else if (!strcasecmp(v->value, "odd")) + paritybit = PARENB | PARODD; + else if (!strcasecmp(v->value, "none")) + paritybit = ~PARENB; + else { + ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno); + paritybit = PARENB; + } + } else if (!strcasecmp(v->name, "charsize")) { + if (!strcasecmp(v->value, "7")) + charsize = CS7; + else if (!strcasecmp(v->value, "8")) + charsize = CS8; + else { + ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno); + charsize = CS7; + } + } else if (!strcasecmp(v->name, "twostopbits")) { + stopbits = ast_true(v->name); + } else if (!strcasecmp(v->name, "smdiport")) { + if (reload) { + /* we are reloading, check if we are already + * monitoring this interface, if we are we do + * not want to start it again. This also has + * the side effect of not updating different + * setting for the serial port, but it should + * be trivial to rewrite this section so that + * options on the port are changed without + * restarting the interface. Or the interface + * could be restarted with out emptying the + * queue. */ + if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) { + ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name); + ASTOBJ_UNMARK(iface); + ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); + continue; + } + } + + if (!(iface = ast_calloc(1, sizeof(*iface)))) + continue; + + ASTOBJ_INIT(iface); + ASTOBJ_CONTAINER_INIT(&iface->md_q); + ASTOBJ_CONTAINER_INIT(&iface->mwi_q); + + ast_copy_string(iface->name, v->value, sizeof(iface->name)); + + iface->thread = AST_PTHREADT_NULL; + + if (!(iface->file = fopen(iface->name, "r"))) { + ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno)); + ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); + continue; + } + + iface->fd = fileno(iface->file); + + /* Set the proper attributes for our serial port. */ + + /* get the current attributes from the port */ + if (tcgetattr(iface->fd, &iface->mode)) { + ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno)); + ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); + continue; + } + + /* set the desired speed */ + if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) { + ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno)); + ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); + continue; + } + + /* set the stop bits */ + if (stopbits) + iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB; /* set two stop bits */ + else + iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB; /* set one stop bit */ + + /* set the parity */ + iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit; + + /* set the character size */ + iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize; + + /* commit the desired attributes */ + if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) { + ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno)); + ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); + continue; + } + + /* set the msdstrip */ + iface->msdstrip = msdstrip; + + /* set the message expiry time */ + iface->msg_expiry = msg_expiry; + + /* start the listner thread */ + ast_verb(3, "Starting SMDI monitor thread for %s\n", iface->name); + if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) { + ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name); + ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); + continue; + } + + ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface); + ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); + ast_module_ref(ast_module_info->self); + } else { + ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file); + } + } + ast_config_destroy(conf); + + /* Prune any interfaces we should no longer monitor. */ + if (reload) + ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy); + + ASTOBJ_CONTAINER_RDLOCK(&smdi_ifaces); + /* TODO: this is bad, we need an ASTOBJ method for this! */ + if (!smdi_ifaces.head) + res = 1; + ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces); + + return res; +} + +static int load_module(void) +{ + int res; + + /* initialize our containers */ + memset(&smdi_ifaces, 0, sizeof(smdi_ifaces)); + ASTOBJ_CONTAINER_INIT(&smdi_ifaces); + + /* load the config and start the listener threads*/ + res = smdi_load(0); + if (res < 0) { + return res; + } else if (res == 1) { + ast_log(LOG_NOTICE, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n"); + return AST_MODULE_LOAD_DECLINE; + } else + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + /* this destructor stops any running smdi_read threads */ + ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy); + ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces); + + return 0; +} + +static int reload(void) +{ + int res; + + res = smdi_load(1); + + if (res < 0) { + return res; + } else if (res == 1) { + ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n"); + return 0; + } else + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource", + .load = load_module, + .unload = unload_module, + .reload = reload, + ); diff --git a/trunk/res/res_snmp.c b/trunk/res/res_snmp.c new file mode 100644 index 000000000..71a79f209 --- /dev/null +++ b/trunk/res/res_snmp.c @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2006 Voop as + * Thorsten Lockert <tholo@voop.as> + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief SNMP Agent / SubAgent support for Asterisk + * + * \author Thorsten Lockert <tholo@voop.as> + * + * \extref Uses the Net-SNMP libraries available at + * http://net-snmp.sourceforge.net/ + */ + +/*** MODULEINFO + <depend>netsnmp</depend> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/channel.h" +#include "asterisk/module.h" + +#include "snmp/agent.h" + +#define MODULE_DESCRIPTION "SNMP [Sub]Agent for Asterisk" + +int res_snmp_agentx_subagent; +int res_snmp_dont_stop; +int res_snmp_enabled; + +static pthread_t thread = AST_PTHREADT_NULL; + +/*! + * \brief Load res_snmp.conf config file + * \return 1 on load, 0 file does not exist +*/ +static int load_config(void) +{ + struct ast_variable *var; + struct ast_config *cfg; + struct ast_flags config_flags = { 0 }; + char *cat; + + res_snmp_enabled = 0; + res_snmp_agentx_subagent = 1; + cfg = ast_config_load("res_snmp.conf", config_flags); + if (!cfg) { + ast_log(LOG_WARNING, "Could not load res_snmp.conf\n"); + return 0; + } + cat = ast_category_browse(cfg, NULL); + while (cat) { + var = ast_variable_browse(cfg, cat); + + if (strcasecmp(cat, "general") == 0) { + while (var) { + if (strcasecmp(var->name, "subagent") == 0) { + if (ast_true(var->value)) + res_snmp_agentx_subagent = 1; + else if (ast_false(var->value)) + res_snmp_agentx_subagent = 0; + else { + ast_log(LOG_ERROR, "Value '%s' does not evaluate to true or false.\n", var->value); + ast_config_destroy(cfg); + return 1; + } + } else if (strcasecmp(var->name, "enabled") == 0) { + res_snmp_enabled = ast_true(var->value); + } else { + ast_log(LOG_ERROR, "Unrecognized variable '%s' in category '%s'\n", var->name, cat); + ast_config_destroy(cfg); + return 1; + } + var = var->next; + } + } else { + ast_log(LOG_ERROR, "Unrecognized category '%s'\n", cat); + ast_config_destroy(cfg); + return 1; + } + + cat = ast_category_browse(cfg, cat); + } + ast_config_destroy(cfg); + return 1; +} + +static int load_module(void) +{ + if(!load_config()) + return AST_MODULE_LOAD_DECLINE; + + ast_verb(1, "Loading [Sub]Agent Module\n"); + + res_snmp_dont_stop = 1; + if (res_snmp_enabled) + return ast_pthread_create_background(&thread, NULL, agent_thread, NULL); + else + return 0; +} + +static int unload_module(void) +{ + ast_verb(1, "Unloading [Sub]Agent Module\n"); + + res_snmp_dont_stop = 0; + return ((thread != AST_PTHREADT_NULL) ? pthread_join(thread, NULL) : 0); +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "SNMP [Sub]Agent for Asterisk", + .load = load_module, + .unload = unload_module, + ); diff --git a/trunk/res/res_speech.c b/trunk/res/res_speech.c new file mode 100644 index 000000000..902955da1 --- /dev/null +++ b/trunk/res/res_speech.c @@ -0,0 +1,344 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2006, Digium, Inc. + * + * Joshua Colp <jcolp@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief Generic Speech Recognition API + * + * \author Joshua Colp <jcolp@digium.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$"); + +#include "asterisk/channel.h" +#include "asterisk/module.h" +#include "asterisk/lock.h" +#include "asterisk/linkedlists.h" +#include "asterisk/cli.h" +#include "asterisk/term.h" +#include "asterisk/speech.h" + + +static AST_RWLIST_HEAD_STATIC(engines, ast_speech_engine); +static struct ast_speech_engine *default_engine = NULL; + +/*! \brief Find a speech recognition engine of specified name, if NULL then use the default one */ +static struct ast_speech_engine *find_engine(char *engine_name) +{ + struct ast_speech_engine *engine = NULL; + + /* If no name is specified -- use the default engine */ + if (ast_strlen_zero(engine_name)) + return default_engine; + + AST_RWLIST_RDLOCK(&engines); + AST_RWLIST_TRAVERSE(&engines, engine, list) { + if (!strcasecmp(engine->name, engine_name)) { + break; + } + } + AST_RWLIST_UNLOCK(&engines); + + return engine; +} + +/*! \brief Activate a loaded (either local or global) grammar */ +int ast_speech_grammar_activate(struct ast_speech *speech, char *grammar_name) +{ + return (speech->engine->activate ? speech->engine->activate(speech, grammar_name) : -1); +} + +/*! \brief Deactivate a loaded grammar on a speech structure */ +int ast_speech_grammar_deactivate(struct ast_speech *speech, char *grammar_name) +{ + return (speech->engine->deactivate ? speech->engine->deactivate(speech, grammar_name) : -1); +} + +/*! \brief Load a local grammar on a speech structure */ +int ast_speech_grammar_load(struct ast_speech *speech, char *grammar_name, char *grammar) +{ + return (speech->engine->load ? speech->engine->load(speech, grammar_name, grammar) : -1); +} + +/*! \brief Unload a local grammar from a speech structure */ +int ast_speech_grammar_unload(struct ast_speech *speech, char *grammar_name) +{ + return (speech->engine->unload ? speech->engine->unload(speech, grammar_name) : -1); +} + +/*! \brief Return the results of a recognition from the speech structure */ +struct ast_speech_result *ast_speech_results_get(struct ast_speech *speech) +{ + return (speech->engine->get ? speech->engine->get(speech) : NULL); +} + +/*! \brief Free a list of results */ +int ast_speech_results_free(struct ast_speech_result *result) +{ + struct ast_speech_result *current_result = result, *prev_result = NULL; + int res = 0; + + while (current_result != NULL) { + prev_result = current_result; + /* Deallocate what we can */ + if (current_result->text != NULL) { + ast_free(current_result->text); + current_result->text = NULL; + } + if (current_result->grammar != NULL) { + ast_free(current_result->grammar); + current_result->grammar = NULL; + } + /* Move on and then free ourselves */ + current_result = AST_LIST_NEXT(current_result, list); + ast_free(prev_result); + prev_result = NULL; + } + + return res; +} + +/*! \brief Start speech recognition on a speech structure */ +void ast_speech_start(struct ast_speech *speech) +{ + + /* Clear any flags that may affect things */ + ast_clear_flag(speech, AST_SPEECH_SPOKE); + ast_clear_flag(speech, AST_SPEECH_QUIET); + ast_clear_flag(speech, AST_SPEECH_HAVE_RESULTS); + + /* If results are on the structure, free them since we are starting again */ + if (speech->results) { + ast_speech_results_free(speech->results); + speech->results = NULL; + } + + /* If the engine needs to start stuff up, do it */ + if (speech->engine->start) + speech->engine->start(speech); + + return; +} + +/*! \brief Write in signed linear audio to be recognized */ +int ast_speech_write(struct ast_speech *speech, void *data, int len) +{ + /* Make sure the speech engine is ready to accept audio */ + if (speech->state != AST_SPEECH_STATE_READY) + return -1; + + return speech->engine->write(speech, data, len); +} + +/*! \brief Signal to the engine that DTMF was received */ +int ast_speech_dtmf(struct ast_speech *speech, const char *dtmf) +{ + int res = 0; + + if (speech->state != AST_SPEECH_STATE_READY) + return -1; + + if (speech->engine->dtmf != NULL) { + res = speech->engine->dtmf(speech, dtmf); + } + + return res; +} + +/*! \brief Change an engine specific attribute */ +int ast_speech_change(struct ast_speech *speech, char *name, const char *value) +{ + return (speech->engine->change ? speech->engine->change(speech, name, value) : -1); +} + +/*! \brief Create a new speech structure using the engine specified */ +struct ast_speech *ast_speech_new(char *engine_name, int formats) +{ + struct ast_speech_engine *engine = NULL; + struct ast_speech *new_speech = NULL; + int format = AST_FORMAT_SLINEAR; + + /* Try to find the speech recognition engine that was requested */ + if (!(engine = find_engine(engine_name))) + return NULL; + + /* Before even allocating the memory below do some codec negotiation, we choose the best codec possible and fall back to signed linear if possible */ + if ((format = (engine->formats & formats))) + format = ast_best_codec(format); + else if ((engine->formats & AST_FORMAT_SLINEAR)) + format = AST_FORMAT_SLINEAR; + else + return NULL; + + /* Allocate our own speech structure, and try to allocate a structure from the engine too */ + if (!(new_speech = ast_calloc(1, sizeof(*new_speech)))) + return NULL; + + /* Initialize the lock */ + ast_mutex_init(&new_speech->lock); + + /* Make sure no results are present */ + new_speech->results = NULL; + + /* Copy over our engine pointer */ + new_speech->engine = engine; + + /* Can't forget the format audio is going to be in */ + new_speech->format = format; + + /* We are not ready to accept audio yet */ + ast_speech_change_state(new_speech, AST_SPEECH_STATE_NOT_READY); + + /* Pass ourselves to the engine so they can set us up some more and if they error out then do not create a structure */ + if (engine->create(new_speech, format)) { + ast_mutex_destroy(&new_speech->lock); + ast_free(new_speech); + new_speech = NULL; + } + + return new_speech; +} + +/*! \brief Destroy a speech structure */ +int ast_speech_destroy(struct ast_speech *speech) +{ + int res = 0; + + /* Call our engine so we are destroyed properly */ + speech->engine->destroy(speech); + + /* Deinitialize the lock */ + ast_mutex_destroy(&speech->lock); + + /* If results exist on the speech structure, destroy them */ + if (speech->results) + ast_speech_results_free(speech->results); + + /* If a processing sound is set - free the memory used by it */ + if (speech->processing_sound) + ast_free(speech->processing_sound); + + /* Aloha we are done */ + ast_free(speech); + + return res; +} + +/*! \brief Change state of a speech structure */ +int ast_speech_change_state(struct ast_speech *speech, int state) +{ + int res = 0; + + switch (state) { + case AST_SPEECH_STATE_WAIT: + /* The engine heard audio, so they spoke */ + ast_set_flag(speech, AST_SPEECH_SPOKE); + default: + speech->state = state; + break; + } + + return res; +} + +/*! \brief Change the type of results we want */ +int ast_speech_change_results_type(struct ast_speech *speech, enum ast_speech_results_type results_type) +{ + speech->results_type = results_type; + + return (speech->engine->change_results_type ? speech->engine->change_results_type(speech, results_type) : 0); +} + +/*! \brief Register a speech recognition engine */ +int ast_speech_register(struct ast_speech_engine *engine) +{ + struct ast_speech_engine *existing_engine = NULL; + int res = 0; + + /* Confirm the engine meets the minimum API requirements */ + if (!engine->create || !engine->write || !engine->destroy) { + ast_log(LOG_WARNING, "Speech recognition engine '%s' did not meet minimum API requirements.\n", engine->name); + return -1; + } + + /* If an engine is already loaded with this name, error out */ + if ((existing_engine = find_engine(engine->name))) { + ast_log(LOG_WARNING, "Speech recognition engine '%s' already exists.\n", engine->name); + return -1; + } + + ast_verb(2, "Registered speech recognition engine '%s'\n", engine->name); + + /* Add to the engine linked list and make default if needed */ + AST_RWLIST_WRLOCK(&engines); + AST_RWLIST_INSERT_HEAD(&engines, engine, list); + if (!default_engine) { + default_engine = engine; + ast_verb(2, "Made '%s' the default speech recognition engine\n", engine->name); + } + AST_RWLIST_UNLOCK(&engines); + + return res; +} + +/*! \brief Unregister a speech recognition engine */ +int ast_speech_unregister(char *engine_name) +{ + struct ast_speech_engine *engine = NULL; + int res = -1; + + if (ast_strlen_zero(engine_name)) + return -1; + + AST_RWLIST_WRLOCK(&engines); + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&engines, engine, list) { + if (!strcasecmp(engine->name, engine_name)) { + /* We have our engine... removed it */ + AST_RWLIST_REMOVE_CURRENT(list); + /* If this was the default engine, we need to pick a new one */ + if (!default_engine) + default_engine = AST_RWLIST_FIRST(&engines); + ast_verb(2, "Unregistered speech recognition engine '%s'\n", engine_name); + /* All went well */ + res = 0; + break; + } + } + AST_RWLIST_TRAVERSE_SAFE_END; + AST_RWLIST_UNLOCK(&engines); + + return res; +} + +static int unload_module(void) +{ + /* We can not be unloaded */ + return -1; +} + +static int load_module(void) +{ + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Generic Speech Recognition API", + .load = load_module, + .unload = unload_module, + ); diff --git a/trunk/res/snmp/agent.c b/trunk/res/snmp/agent.c new file mode 100644 index 000000000..207acb8a1 --- /dev/null +++ b/trunk/res/snmp/agent.c @@ -0,0 +1,842 @@ +/* + * Copyright (C) 2006 Voop as + * Thorsten Lockert <tholo@voop.as> + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief SNMP Agent / SubAgent support for Asterisk + * + * \author Thorsten Lockert <tholo@voop.as> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +/* + * There is some collision collision between netsmp and asterisk names, + * causing build under AST_DEVMODE to fail. + * + * The following PACKAGE_* macros are one place. + * Also netsnmp has an improper check for HAVE_DMALLOC_H, using + * #if HAVE_DMALLOC_H instead of #ifdef HAVE_DMALLOC_H + * As a countermeasure we define it to 0, however this will fail + * when the proper check is implemented. + */ +#ifdef PACKAGE_NAME +#undef PACKAGE_NAME +#endif +#ifdef PACKAGE_BUGREPORT +#undef PACKAGE_BUGREPORT +#endif +#ifdef PACKAGE_STRING +#undef PACKAGE_STRING +#endif +#ifdef PACKAGE_TARNAME +#undef PACKAGE_TARNAME +#endif +#ifdef PACKAGE_VERSION +#undef PACKAGE_VERSION +#endif +#ifndef HAVE_DMALLOC_H +#define HAVE_DMALLOC_H 0 /* XXX we shouldn't do this */ +#endif + +#include <net-snmp/net-snmp-config.h> +#include <net-snmp/net-snmp-includes.h> +#include <net-snmp/agent/net-snmp-agent-includes.h> + +#include "asterisk/paths.h" /* need ast_config_AST_SOCKET */ +#include "asterisk/channel.h" +#include "asterisk/logger.h" +#include "asterisk/options.h" +#include "asterisk/indications.h" +#include "asterisk/version.h" +#include "asterisk/pbx.h" + +/* Colission between Net-SNMP and Asterisk */ +#define unload_module ast_unload_module +#include "asterisk/module.h" +#undef unload_module + +#include "agent.h" + +/* Helper functions in Net-SNMP, header file not installed by default */ +int header_generic(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **); +int header_simple_table(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **, int); +int register_sysORTable(oid *, size_t, const char *); +int unregister_sysORTable(oid *, size_t); + +/* Forward declaration */ +static void init_asterisk_mib(void); + +/* + * Anchor for all the Asterisk MIB values + */ +static oid asterisk_oid[] = { 1, 3, 6, 1, 4, 1, 22736, 1 }; + +/* + * MIB values -- these correspond to values in the Asterisk MIB, + * and MUST be kept in sync with the MIB for things to work as + * expected. + */ +#define ASTVERSION 1 +#define ASTVERSTRING 1 +#define ASTVERTAG 2 + +#define ASTCONFIGURATION 2 +#define ASTCONFUPTIME 1 +#define ASTCONFRELOADTIME 2 +#define ASTCONFPID 3 +#define ASTCONFSOCKET 4 +#define ASTCONFACTIVECALLS 5 +#define ASTCONFPROCESSEDCALLS 6 + +#define ASTMODULES 3 +#define ASTMODCOUNT 1 + +#define ASTINDICATIONS 4 +#define ASTINDCOUNT 1 +#define ASTINDCURRENT 2 + +#define ASTINDTABLE 3 +#define ASTINDINDEX 1 +#define ASTINDCOUNTRY 2 +#define ASTINDALIAS 3 +#define ASTINDDESCRIPTION 4 + +#define ASTCHANNELS 5 +#define ASTCHANCOUNT 1 + +#define ASTCHANTABLE 2 +#define ASTCHANINDEX 1 +#define ASTCHANNAME 2 +#define ASTCHANLANGUAGE 3 +#define ASTCHANTYPE 4 +#define ASTCHANMUSICCLASS 5 +#define ASTCHANBRIDGE 6 +#define ASTCHANMASQ 7 +#define ASTCHANMASQR 8 +#define ASTCHANWHENHANGUP 9 +#define ASTCHANAPP 10 +#define ASTCHANDATA 11 +#define ASTCHANCONTEXT 12 +#define ASTCHANMACROCONTEXT 13 +#define ASTCHANMACROEXTEN 14 +#define ASTCHANMACROPRI 15 +#define ASTCHANEXTEN 16 +#define ASTCHANPRI 17 +#define ASTCHANACCOUNTCODE 18 +#define ASTCHANFORWARDTO 19 +#define ASTCHANUNIQUEID 20 +#define ASTCHANCALLGROUP 21 +#define ASTCHANPICKUPGROUP 22 +#define ASTCHANSTATE 23 +#define ASTCHANMUTED 24 +#define ASTCHANRINGS 25 +#define ASTCHANCIDDNID 26 +#define ASTCHANCIDNUM 27 +#define ASTCHANCIDNAME 28 +#define ASTCHANCIDANI 29 +#define ASTCHANCIDRDNIS 30 +#define ASTCHANCIDPRES 31 +#define ASTCHANCIDANI2 32 +#define ASTCHANCIDTON 33 +#define ASTCHANCIDTNS 34 +#define ASTCHANAMAFLAGS 35 +#define ASTCHANADSI 36 +#define ASTCHANTONEZONE 37 +#define ASTCHANHANGUPCAUSE 38 +#define ASTCHANVARIABLES 39 +#define ASTCHANFLAGS 40 +#define ASTCHANTRANSFERCAP 41 + +#define ASTCHANTYPECOUNT 3 + +#define ASTCHANTYPETABLE 4 +#define ASTCHANTYPEINDEX 1 +#define ASTCHANTYPENAME 2 +#define ASTCHANTYPEDESC 3 +#define ASTCHANTYPEDEVSTATE 4 +#define ASTCHANTYPEINDICATIONS 5 +#define ASTCHANTYPETRANSFER 6 +#define ASTCHANTYPECHANNELS 7 + +#define ASTCHANSCALARS 5 +#define ASTCHANBRIDGECOUNT 1 + +void *agent_thread(void *arg) +{ + ast_verb(2, "Starting %sAgent\n", res_snmp_agentx_subagent ? "Sub" : ""); + + snmp_enable_stderrlog(); + + if (res_snmp_agentx_subagent) + netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, + NETSNMP_DS_AGENT_ROLE, + 1); + + init_agent("asterisk"); + + init_asterisk_mib(); + + init_snmp("asterisk"); + + if (!res_snmp_agentx_subagent) + init_master_agent(); + + while (res_snmp_dont_stop) + agent_check_and_process(1); + + snmp_shutdown("asterisk"); + + ast_verb(2, "Terminating %sAgent\n", res_snmp_agentx_subagent ? "Sub" : ""); + + return NULL; +} + +static u_char * +ast_var_channels(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + static unsigned long long_ret; + + if (header_generic(vp, name, length, exact, var_len, write_method)) + return NULL; + + if (vp->magic != ASTCHANCOUNT) + return NULL; + + long_ret = ast_active_channels(); + + return (u_char *)&long_ret; +} + +static u_char *ast_var_channels_table(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + static unsigned long long_ret; + static u_char bits_ret[2]; + static char string_ret[256]; + struct ast_channel *chan, *bridge; + struct timeval tval; + u_char *ret = NULL; + int i, bit; + struct ast_str *out = ast_str_alloca(2048); + + if (header_simple_table(vp, name, length, exact, var_len, write_method, ast_active_channels())) + return NULL; + + i = name[*length - 1] - 1; + for (chan = ast_channel_walk_locked(NULL); + chan && i; + chan = ast_channel_walk_locked(chan), i--) + ast_channel_unlock(chan); + if (chan == NULL) + return NULL; + *var_len = sizeof(long_ret); + + switch (vp->magic) { + case ASTCHANINDEX: + long_ret = name[*length - 1]; + ret = (u_char *)&long_ret; + break; + case ASTCHANNAME: + if (!ast_strlen_zero(chan->name)) { + strncpy(string_ret, chan->name, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANLANGUAGE: + if (!ast_strlen_zero(chan->language)) { + strncpy(string_ret, chan->language, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANTYPE: + strncpy(string_ret, chan->tech->type, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + break; + case ASTCHANMUSICCLASS: + if (!ast_strlen_zero(chan->musicclass)) { + strncpy(string_ret, chan->musicclass, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANBRIDGE: + if ((bridge = ast_bridged_channel(chan)) != NULL) { + strncpy(string_ret, bridge->name, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANMASQ: + if (chan->masq && !ast_strlen_zero(chan->masq->name)) { + strncpy(string_ret, chan->masq->name, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANMASQR: + if (chan->masqr && !ast_strlen_zero(chan->masqr->name)) { + strncpy(string_ret, chan->masqr->name, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANWHENHANGUP: + if (chan->whentohangup) { + gettimeofday(&tval, NULL); + long_ret = difftime(chan->whentohangup, tval.tv_sec) * 100 - tval.tv_usec / 10000; + ret= (u_char *)&long_ret; + } + break; + case ASTCHANAPP: + if (chan->appl) { + strncpy(string_ret, chan->appl, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANDATA: + if (chan->data) { + strncpy(string_ret, chan->data, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANCONTEXT: + strncpy(string_ret, chan->context, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + break; + case ASTCHANMACROCONTEXT: + strncpy(string_ret, chan->macrocontext, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + break; + case ASTCHANMACROEXTEN: + strncpy(string_ret, chan->macroexten, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + break; + case ASTCHANMACROPRI: + long_ret = chan->macropriority; + ret = (u_char *)&long_ret; + break; + case ASTCHANEXTEN: + strncpy(string_ret, chan->exten, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + break; + case ASTCHANPRI: + long_ret = chan->priority; + ret = (u_char *)&long_ret; + break; + case ASTCHANACCOUNTCODE: + if (!ast_strlen_zero(chan->accountcode)) { + strncpy(string_ret, chan->accountcode, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANFORWARDTO: + if (!ast_strlen_zero(chan->call_forward)) { + strncpy(string_ret, chan->call_forward, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANUNIQUEID: + strncpy(string_ret, chan->uniqueid, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + break; + case ASTCHANCALLGROUP: + long_ret = chan->callgroup; + ret = (u_char *)&long_ret; + break; + case ASTCHANPICKUPGROUP: + long_ret = chan->pickupgroup; + ret = (u_char *)&long_ret; + break; + case ASTCHANSTATE: + long_ret = chan->_state & 0xffff; + ret = (u_char *)&long_ret; + break; + case ASTCHANMUTED: + long_ret = chan->_state & AST_STATE_MUTE ? 1 : 2; + ret = (u_char *)&long_ret; + break; + case ASTCHANRINGS: + long_ret = chan->rings; + ret = (u_char *)&long_ret; + break; + case ASTCHANCIDDNID: + if (chan->cid.cid_dnid) { + strncpy(string_ret, chan->cid.cid_dnid, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANCIDNUM: + if (chan->cid.cid_num) { + strncpy(string_ret, chan->cid.cid_num, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANCIDNAME: + if (chan->cid.cid_name) { + strncpy(string_ret, chan->cid.cid_name, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANCIDANI: + if (chan->cid.cid_ani) { + strncpy(string_ret, chan->cid.cid_ani, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANCIDRDNIS: + if (chan->cid.cid_rdnis) { + strncpy(string_ret, chan->cid.cid_rdnis, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANCIDPRES: + long_ret = chan->cid.cid_pres; + ret = (u_char *)&long_ret; + break; + case ASTCHANCIDANI2: + long_ret = chan->cid.cid_ani2; + ret = (u_char *)&long_ret; + break; + case ASTCHANCIDTON: + long_ret = chan->cid.cid_ton; + ret = (u_char *)&long_ret; + break; + case ASTCHANCIDTNS: + long_ret = chan->cid.cid_tns; + ret = (u_char *)&long_ret; + break; + case ASTCHANAMAFLAGS: + long_ret = chan->amaflags; + ret = (u_char *)&long_ret; + break; + case ASTCHANADSI: + long_ret = chan->adsicpe; + ret = (u_char *)&long_ret; + break; + case ASTCHANTONEZONE: + if (chan->zone) { + strncpy(string_ret, chan->zone->country, sizeof(string_ret)); + string_ret[sizeof(string_ret) - 1] = '\0'; + *var_len = strlen(string_ret); + ret = (u_char *)string_ret; + } + break; + case ASTCHANHANGUPCAUSE: + long_ret = chan->hangupcause; + ret = (u_char *)&long_ret; + break; + case ASTCHANVARIABLES: + if (pbx_builtin_serialize_variables(chan, &out)) { + *var_len = strlen(out->str); + ret = (u_char *)out->str; + } + break; + case ASTCHANFLAGS: + bits_ret[0] = 0; + for (bit = 0; bit < 8; bit++) + bits_ret[0] |= ((chan->flags & (1 << bit)) >> bit) << (7 - bit); + bits_ret[1] = 0; + for (bit = 0; bit < 8; bit++) + bits_ret[1] |= (((chan->flags >> 8) & (1 << bit)) >> bit) << (7 - bit); + *var_len = 2; + ret = bits_ret; + break; + case ASTCHANTRANSFERCAP: + long_ret = chan->transfercapability; + ret = (u_char *)&long_ret; + default: + break; + } + ast_channel_unlock(chan); + return ret; +} + +static u_char *ast_var_channel_types(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + static unsigned long long_ret; + struct ast_variable *channel_types, *next; + + if (header_generic(vp, name, length, exact, var_len, write_method)) + return NULL; + + if (vp->magic != ASTCHANTYPECOUNT) + return NULL; + + for (long_ret = 0, channel_types = next = ast_channeltype_list(); next; next = next->next) + long_ret++; + ast_variables_destroy(channel_types); + + return (u_char *)&long_ret; +} + +static u_char *ast_var_channel_types_table(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + const struct ast_channel_tech *tech = NULL; + struct ast_variable *channel_types, *next; + static unsigned long long_ret; + struct ast_channel *chan; + u_long i; + + if (header_simple_table(vp, name, length, exact, var_len, write_method, -1)) + return NULL; + + channel_types = ast_channeltype_list(); + for (i = 1, next = channel_types; next && i != name[*length - 1]; next = next->next, i++) + ; + if (next != NULL) + tech = ast_get_channel_tech(next->name); + ast_variables_destroy(channel_types); + if (next == NULL || tech == NULL) + return NULL; + + switch (vp->magic) { + case ASTCHANTYPEINDEX: + long_ret = name[*length - 1]; + return (u_char *)&long_ret; + case ASTCHANTYPENAME: + *var_len = strlen(tech->type); + return (u_char *)tech->type; + case ASTCHANTYPEDESC: + *var_len = strlen(tech->description); + return (u_char *)tech->description; + case ASTCHANTYPEDEVSTATE: + long_ret = tech->devicestate ? 1 : 2; + return (u_char *)&long_ret; + case ASTCHANTYPEINDICATIONS: + long_ret = tech->indicate ? 1 : 2; + return (u_char *)&long_ret; + case ASTCHANTYPETRANSFER: + long_ret = tech->transfer ? 1 : 2; + return (u_char *)&long_ret; + case ASTCHANTYPECHANNELS: + long_ret = 0; + for (chan = ast_channel_walk_locked(NULL); chan; chan = ast_channel_walk_locked(chan)) { + ast_channel_unlock(chan); + if (chan->tech == tech) + long_ret++; + } + return (u_char *)&long_ret; + default: + break; + } + return NULL; +} + +static u_char *ast_var_channel_bridge(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + static unsigned long long_ret; + struct ast_channel *chan = NULL; + + long_ret = 0; + if (header_generic(vp, name, length, exact, var_len, write_method)) + return NULL; + + while ((chan = ast_channel_walk_locked(chan))) { + if (ast_bridged_channel(chan)) + long_ret++; + ast_channel_unlock(chan); + } + + *var_len = sizeof(long_ret); + + return (vp->magic == ASTCHANBRIDGECOUNT) ? (u_char *) &long_ret : NULL; +} + +static u_char *ast_var_Config(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + static unsigned long long_ret; + struct timeval tval; + + if (header_generic(vp, name, length, exact, var_len, write_method)) + return NULL; + + switch (vp->magic) { + case ASTCONFUPTIME: + gettimeofday(&tval, NULL); + long_ret = difftime(tval.tv_sec, ast_startuptime.tv_sec) * 100 + tval.tv_usec / 10000 - ast_startuptime.tv_usec / 10000; + return (u_char *)&long_ret; + case ASTCONFRELOADTIME: + gettimeofday(&tval, NULL); + if (ast_lastreloadtime.tv_sec) + long_ret = difftime(tval.tv_sec, ast_lastreloadtime.tv_sec) * 100 + tval.tv_usec / 10000 - ast_lastreloadtime.tv_usec / 10000; + else + long_ret = difftime(tval.tv_sec, ast_startuptime.tv_sec) * 100 + tval.tv_usec / 10000 - ast_startuptime.tv_usec / 10000; + return (u_char *)&long_ret; + case ASTCONFPID: + long_ret = getpid(); + return (u_char *)&long_ret; + case ASTCONFSOCKET: + *var_len = strlen(ast_config_AST_SOCKET); + return (u_char *)ast_config_AST_SOCKET; + case ASTCONFACTIVECALLS: + long_ret = ast_active_calls(); + return (u_char *)&long_ret; + case ASTCONFPROCESSEDCALLS: + long_ret = ast_processed_calls(); + return (u_char *)&long_ret; + default: + break; + } + return NULL; +} + +static u_char *ast_var_indications(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + static unsigned long long_ret; + struct ind_tone_zone *tz = NULL; + + if (header_generic(vp, name, length, exact, var_len, write_method)) + return NULL; + + switch (vp->magic) { + case ASTINDCOUNT: + long_ret = 0; + while ( (tz = ast_walk_indications(tz)) ) + long_ret++; + + return (u_char *)&long_ret; + case ASTINDCURRENT: + tz = ast_get_indication_zone(NULL); + if (tz) { + *var_len = strlen(tz->country); + return (u_char *)tz->country; + } + *var_len = 0; + return NULL; + default: + break; + } + return NULL; +} + +static u_char *ast_var_indications_table(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + static unsigned long long_ret; + struct ind_tone_zone *tz = NULL; + int i; + + if (header_simple_table(vp, name, length, exact, var_len, write_method, -1)) + return NULL; + + i = name[*length - 1] - 1; + while ( (tz = ast_walk_indications(tz)) && i ) + i--; + if (tz == NULL) + return NULL; + + switch (vp->magic) { + case ASTINDINDEX: + long_ret = name[*length - 1]; + return (u_char *)&long_ret; + case ASTINDCOUNTRY: + *var_len = strlen(tz->country); + return (u_char *)tz->country; + case ASTINDALIAS: + if (tz->alias) { + *var_len = strlen(tz->alias); + return (u_char *)tz->alias; + } + return NULL; + case ASTINDDESCRIPTION: + *var_len = strlen(tz->description); + return (u_char *)tz->description; + default: + break; + } + return NULL; +} + +static int countmodule(const char *mod, const char *desc, int use, const char *like) +{ + return 1; +} + +static u_char *ast_var_Modules(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + static unsigned long long_ret; + + if (header_generic(vp, name, length, exact, var_len, write_method)) + return NULL; + + if (vp->magic != ASTMODCOUNT) + return NULL; + + long_ret = ast_update_module_list(countmodule, NULL); + + return (u_char *)&long_ret; +} + +static u_char *ast_var_Version(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + static unsigned long long_ret; + + if (header_generic(vp, name, length, exact, var_len, write_method)) + return NULL; + + switch (vp->magic) { + case ASTVERSTRING: + { + const char *version = ast_get_version(); + *var_len = strlen(version); + return (u_char *)version; + } + case ASTVERTAG: + sscanf(ast_get_version_num(), "%lu", &long_ret); + return (u_char *)&long_ret; + default: + break; + } + return NULL; +} + +static int term_asterisk_mib(int majorID, int minorID, void *serverarg, void *clientarg) +{ + unregister_sysORTable(asterisk_oid, OID_LENGTH(asterisk_oid)); + return 0; +} + +static void init_asterisk_mib(void) +{ + static struct variable4 asterisk_vars[] = { + {ASTVERSTRING, ASN_OCTET_STR, RONLY, ast_var_Version, 2, {ASTVERSION, ASTVERSTRING}}, + {ASTVERTAG, ASN_UNSIGNED, RONLY, ast_var_Version, 2, {ASTVERSION, ASTVERTAG}}, + {ASTCONFUPTIME, ASN_TIMETICKS, RONLY, ast_var_Config, 2, {ASTCONFIGURATION, ASTCONFUPTIME}}, + {ASTCONFRELOADTIME, ASN_TIMETICKS, RONLY, ast_var_Config, 2, {ASTCONFIGURATION, ASTCONFRELOADTIME}}, + {ASTCONFPID, ASN_INTEGER, RONLY, ast_var_Config, 2, {ASTCONFIGURATION, ASTCONFPID}}, + {ASTCONFSOCKET, ASN_OCTET_STR, RONLY, ast_var_Config, 2, {ASTCONFIGURATION, ASTCONFSOCKET}}, + {ASTCONFACTIVECALLS, ASN_GAUGE, RONLY, ast_var_Config, 2, {ASTCONFIGURATION, ASTCONFACTIVECALLS}}, + {ASTCONFPROCESSEDCALLS, ASN_INTEGER, RONLY, ast_var_Config, 2, {ASTCONFIGURATION, ASTCONFPROCESSEDCALLS}}, + {ASTMODCOUNT, ASN_INTEGER, RONLY, ast_var_Modules , 2, {ASTMODULES, ASTMODCOUNT}}, + {ASTINDCOUNT, ASN_INTEGER, RONLY, ast_var_indications, 2, {ASTINDICATIONS, ASTINDCOUNT}}, + {ASTINDCURRENT, ASN_OCTET_STR, RONLY, ast_var_indications, 2, {ASTINDICATIONS, ASTINDCURRENT}}, + {ASTINDINDEX, ASN_INTEGER, RONLY, ast_var_indications_table, 4, {ASTINDICATIONS, ASTINDTABLE, 1, ASTINDINDEX}}, + {ASTINDCOUNTRY, ASN_OCTET_STR, RONLY, ast_var_indications_table, 4, {ASTINDICATIONS, ASTINDTABLE, 1, ASTINDCOUNTRY}}, + {ASTINDALIAS, ASN_OCTET_STR, RONLY, ast_var_indications_table, 4, {ASTINDICATIONS, ASTINDTABLE, 1, ASTINDALIAS}}, + {ASTINDDESCRIPTION, ASN_OCTET_STR, RONLY, ast_var_indications_table, 4, {ASTINDICATIONS, ASTINDTABLE, 1, ASTINDDESCRIPTION}}, + {ASTCHANCOUNT, ASN_INTEGER, RONLY, ast_var_channels, 2, {ASTCHANNELS, ASTCHANCOUNT}}, + {ASTCHANINDEX, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANINDEX}}, + {ASTCHANNAME, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANNAME}}, + {ASTCHANLANGUAGE, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANLANGUAGE}}, + {ASTCHANTYPE, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANTYPE}}, + {ASTCHANMUSICCLASS, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMUSICCLASS}}, + {ASTCHANBRIDGE, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANBRIDGE}}, + {ASTCHANMASQ, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMASQ}}, + {ASTCHANMASQR, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMASQR}}, + {ASTCHANWHENHANGUP, ASN_TIMETICKS, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANWHENHANGUP}}, + {ASTCHANAPP, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANAPP}}, + {ASTCHANDATA, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANDATA}}, + {ASTCHANCONTEXT, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCONTEXT}}, + {ASTCHANMACROCONTEXT, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMACROCONTEXT}}, + {ASTCHANMACROEXTEN, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMACROEXTEN}}, + {ASTCHANMACROPRI, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMACROPRI}}, + {ASTCHANEXTEN, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANEXTEN}}, + {ASTCHANPRI, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANPRI}}, + {ASTCHANACCOUNTCODE, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANACCOUNTCODE}}, + {ASTCHANFORWARDTO, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANFORWARDTO}}, + {ASTCHANUNIQUEID, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANUNIQUEID}}, + {ASTCHANCALLGROUP, ASN_UNSIGNED, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCALLGROUP}}, + {ASTCHANPICKUPGROUP, ASN_UNSIGNED, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANPICKUPGROUP}}, + {ASTCHANSTATE, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANSTATE}}, + {ASTCHANMUTED, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANMUTED}}, + {ASTCHANRINGS, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANRINGS}}, + {ASTCHANCIDDNID, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDDNID}}, + {ASTCHANCIDNUM, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDNUM}}, + {ASTCHANCIDNAME, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDNAME}}, + {ASTCHANCIDANI, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDANI}}, + {ASTCHANCIDRDNIS, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDRDNIS}}, + {ASTCHANCIDPRES, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDPRES}}, + {ASTCHANCIDANI2, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDANI2}}, + {ASTCHANCIDTON, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDTON}}, + {ASTCHANCIDTNS, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANCIDTNS}}, + {ASTCHANAMAFLAGS, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANAMAFLAGS}}, + {ASTCHANADSI, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANADSI}}, + {ASTCHANTONEZONE, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANTONEZONE}}, + {ASTCHANHANGUPCAUSE, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANHANGUPCAUSE}}, + {ASTCHANVARIABLES, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANVARIABLES}}, + {ASTCHANFLAGS, ASN_OCTET_STR, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANFLAGS}}, + {ASTCHANTRANSFERCAP, ASN_INTEGER, RONLY, ast_var_channels_table, 4, {ASTCHANNELS, ASTCHANTABLE, 1, ASTCHANTRANSFERCAP}}, + {ASTCHANTYPECOUNT, ASN_INTEGER, RONLY, ast_var_channel_types, 2, {ASTCHANNELS, ASTCHANTYPECOUNT}}, + {ASTCHANTYPEINDEX, ASN_INTEGER, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPEINDEX}}, + {ASTCHANTYPENAME, ASN_OCTET_STR, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPENAME}}, + {ASTCHANTYPEDESC, ASN_OCTET_STR, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPEDESC}}, + {ASTCHANTYPEDEVSTATE, ASN_INTEGER, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPEDEVSTATE}}, + {ASTCHANTYPEINDICATIONS, ASN_INTEGER, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPEINDICATIONS}}, + {ASTCHANTYPETRANSFER, ASN_INTEGER, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPETRANSFER}}, + {ASTCHANTYPECHANNELS, ASN_GAUGE, RONLY, ast_var_channel_types_table, 4, {ASTCHANNELS, ASTCHANTYPETABLE, 1, ASTCHANTYPECHANNELS}}, + {ASTCHANBRIDGECOUNT, ASN_GAUGE, RONLY, ast_var_channel_bridge, 3, {ASTCHANNELS, ASTCHANSCALARS, ASTCHANBRIDGECOUNT}}, + }; + + register_sysORTable(asterisk_oid, OID_LENGTH(asterisk_oid), + "ASTERISK-MIB implementation for Asterisk."); + + REGISTER_MIB("res_snmp", asterisk_vars, variable4, asterisk_oid); + + snmp_register_callback(SNMP_CALLBACK_LIBRARY, + SNMP_CALLBACK_SHUTDOWN, + term_asterisk_mib, NULL); +} + +/* + * Local Variables: + * c-basic-offset: 4 + * c-file-offsets: ((case-label . 0)) + * tab-width: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/trunk/res/snmp/agent.h b/trunk/res/snmp/agent.h new file mode 100644 index 000000000..21389d6c9 --- /dev/null +++ b/trunk/res/snmp/agent.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2006 Voop as + * Thorsten Lockert <tholo@voop.as> + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief SNMP Agent / SubAgent support for Asterisk + * + * \author Thorsten Lockert <tholo@voop.as> + */ + +/*! + * \internal + * \brief Thread running the SNMP Agent or Subagent + * \param Not used -- required by pthread_create + * \return A pointer with return status -- not used + * + * This represent the main thread of the SNMP [sub]agent, and + * will initialize SNMP and loop, processing requests until + * termination is requested by resetting the flag in + * \ref res_snmp_dontStop. + */ +void *agent_thread(void *); + +/*! + * \internal + * Flag saying whether we run as a Subagent or full Agent + */ +extern int res_snmp_agentx_subagent; + +/*! + * \internal + * Flag stating the agent thread should not terminate + */ +extern int res_snmp_dont_stop; |