Version 0.2.0 from FTP
authorMark Spencer <markster@digium.com>
Wed, 11 Sep 2002 17:09:48 +0000 (17:09 +0000)
committerMark Spencer <markster@digium.com>
Wed, 11 Sep 2002 17:09:48 +0000 (17:09 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@523 65c4cc65-6c06-0410-ace0-fbb531ad65f3

README.festival [new file with mode: 0755]
apps/Makefile
apps/app_festival.c [new file with mode: 0755]
apps/app_sql_postgres.c [new file with mode: 0755]
configs/festival.conf.sample [new file with mode: 0755]
contrib/README.festival [new file with mode: 0755]
contrib/festival-1.4.1-diff [new file with mode: 0755]
festival-1.4.1-diff [new file with mode: 0755]
pbx.c

diff --git a/README.festival b/README.festival
new file mode 100755 (executable)
index 0000000..56d985e
--- /dev/null
@@ -0,0 +1,22 @@
+
+app_festival is an application that allows one to send text-to-speech commands
+to a background festival server, and to obtain the resulting waveform which
+gets sent down to the respective channel. app_festival also employs a waveform 
+cache, so invariant text-to-speech strings ("Please press 1 for instructions") 
+do not need to be dynamically generated all the time. 
+
+You need : 
+
+1) festival, patched to produce 8khz waveforms on output. Patch for Festival
+1.4.1 RELEASE are included. The patch adds a new command to festival 
+(asterisk_tts). 
+
+2) My patches to asterisk that provide variable substitution and quoting to 
+the Asterisk Extension Logic. This is not really a requirement, but without
+this, app_festival is mostly useless (you could very well use prerecorded
+voices for static information). 
+
+3) Before running asterisk, you have to run festival-server with a command 
+like : 
+
+/usr/local/festival/bin/festival --server > /dev/null 2>&1 &
index 0bc6259..ce48c47 100755 (executable)
 APPS=app_dial.so app_playback.so app_voicemail.so app_directory.so app_intercom.so app_mp3.so \
      app_system.so app_echo.so app_record.so app_image.so app_url.so app_disa.so \
      app_agi.so app_qcall.so app_adsiprog.so app_getcpeid.so app_milliwatt.so \
-     app_zapateller.so
+     app_zapateller.so app_datetime.so app_setcallerid.so app_festival.so \
+     app_queue.so
+
+#APPS+=app_sql_postgres.so
+#APPS+=app_sql_odbc.so
 
 APPS+=$(shell if [ -f /usr/include/zap.h ]; then echo "app_zapras.so app_meetme.so" ; fi)
 
@@ -38,6 +42,14 @@ app_todd.o: app_todd.c
 app_todd.so: app_todd.o
        $(CC) -shared -Xlinker -x -o $@ $< -L/usr/local/ssl/lib -lssl -lcrypto
 
+app_sql_postgres.o: app_sql_postgres.c
+       $(CC) -pipe -I/usr/local/pgsql/include -Wall -Wmissing-prototypes -Wmissing-declarations -O6 -g  -Iinclude -I../include -D_REENTRANT -D_GNU_SOURCE -march=i686  -DASTERISK_VERSION=\"CVS-07/21/02-14:49:14\" -DDO_CRASH -DDEBUG_THREADS     -c -o app_sql_postgres.o app_sql_postgres.c
+
+app_sql_postgres.so: app_sql_postgres.o
+       $(CC) -shared -Xlinker -x -o $@ $< -L/usr/local/pgsql/lib -lpq
+
+app_sql_odbc.so: app_sql_odbc.o
+       $(CC) -shared -Xlinker -x -o $@ $< -lodbc
 
 look:  look.c
        gcc -pipe -O6 -g look.c -o look -lncurses
diff --git a/apps/app_festival.c b/apps/app_festival.c
new file mode 100755 (executable)
index 0000000..d45988c
--- /dev/null
@@ -0,0 +1,480 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Connect to festival
+ * 
+ * Copyright (C) 2002, Christos Ricudis
+ *
+ * Christos Ricudis <ricudis@paiko.gr>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+#include <asterisk/file.h>
+#include <asterisk/logger.h>
+#include <asterisk/channel.h>
+#include <asterisk/pbx.h>
+#include <asterisk/module.h>
+#include <asterisk/md5.h>
+#include <asterisk/config.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <pthread.h>
+
+
+#define FESTIVAL_CONFIG "festival.conf"
+
+static char *tdesc = "Simple Festival Interface";
+
+static char *app = "Festival";
+
+static char *synopsis = "Say text to the user";
+
+static char *descrip = 
+"  Festival():  Connect to Festival, send the argument, get back the waveform,"
+"play it to the user.\n";
+
+STANDARD_LOCAL_USER;
+
+LOCAL_USER_DECL;
+
+static char *socket_receive_file_to_buff(int fd,int *size)
+{
+    /* Receive file (probably a waveform file) from socket using   */
+    /* Festival key stuff technique, but long winded I know, sorry */
+    /* but will receive any file without closeing the stream or    */
+    /* using OOB data                                              */
+    static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
+    char *buff;
+    int bufflen;
+    int n,k,i;
+    char c;
+
+    bufflen = 1024;
+    buff = (char *)malloc(bufflen);
+    *size=0;
+
+    for (k=0; file_stuff_key[k] != '\0';)
+    {
+        n = read(fd,&c,1);
+        if (n==0) break;  /* hit stream eof before end of file */
+        if ((*size)+k+1 >= bufflen)
+        {   /* +1 so you can add a NULL if you want */
+            bufflen += bufflen/4;
+            buff = (char *)realloc(buff,bufflen);
+        }
+        if (file_stuff_key[k] == c)
+            k++;
+        else if ((c == 'X') && (file_stuff_key[k+1] == '\0'))
+        {   /* It looked like the key but wasn't */
+            for (i=0; i < k; i++,(*size)++)
+                buff[*size] = file_stuff_key[i];
+            k=0;
+            /* omit the stuffed 'X' */
+        }
+        else
+        {
+            for (i=0; i < k; i++,(*size)++)
+                buff[*size] = file_stuff_key[i];
+            k=0;
+            buff[*size] = c;
+            (*size)++;
+        }
+
+    }
+
+    return buff;
+}
+
+static int send_waveform_to_fd(char *waveform, int length, int fd) {
+
+        int res;
+        int x;
+        res = fork();
+        if (res < 0)
+                ast_log(LOG_WARNING, "Fork failed\n");
+        if (res)
+                return res;
+        for (x=0;x<256;x++) {
+                if (x != fd)
+                        close(x);
+        }
+       write(fd,waveform,length);
+       write(fd,"a",1);
+       close(fd);
+       exit(0);
+}
+
+
+static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length) {
+       int res=0;
+       int fds[2];
+       int rfds[1 + AST_MAX_FDS];
+       int ms = -1;
+       int pid = -1;
+       int us;
+       int exception;
+       int owriteformat;
+       struct timeval tv;
+       struct timeval last;
+       struct ast_frame *f;
+       int x;
+       struct myframe {
+               struct ast_frame f;
+               char offset[AST_FRIENDLY_OFFSET];
+               char frdata[160];
+       } myf;
+       last.tv_usec = 0;
+       last.tv_sec = 0;
+       
+        if (pipe(fds)) {
+                 ast_log(LOG_WARNING, "Unable to create pipe\n");
+               return -1;
+        }
+                                                       
+       ast_stopstream(chan);
+
+       owriteformat = chan->writeformat;
+       res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
+       if (res < 0) {
+               ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
+               return -1;
+       }
+       
+       res=send_waveform_to_fd(waveform,length,fds[1]);
+       if (res >= 0) {
+               pid = res;
+               /* Order is important -- there's almost always going to be mp3...  we want to prioritize the
+                  user */
+               rfds[AST_MAX_FDS] = fds[0];
+               for (;;) {
+                       CHECK_BLOCKING(chan);
+                       for (x=0;x<AST_MAX_FDS;x++) 
+                               rfds[x] = chan->fds[x];
+                       res = ast_waitfor_n_fd(rfds, AST_MAX_FDS+1, &ms, &exception);
+                       chan->blocking = 0;
+                       if (res < 1) {
+                               ast_log(LOG_DEBUG, "Hangup detected\n");
+                               res = -1;
+                               break;
+                       }
+                       for(x=0;x<AST_MAX_FDS;x++) 
+                               if (res == chan->fds[x])
+                                       break;
+
+                       if (x < AST_MAX_FDS) {
+                               if (exception)
+                                       chan->exception = 1;
+                               f = ast_read(chan);
+                               if (!f) {
+                                       ast_log(LOG_DEBUG, "Null frame == hangup() detected\n");
+                                       res = -1;
+                                       break;
+                               }
+                               if (f->frametype == AST_FRAME_DTMF) {
+                                       ast_log(LOG_DEBUG, "User pressed a key\n");
+                                       ast_frfree(f);
+                                       res = 0;
+                                       break;
+                               }
+                               ast_frfree(f);
+                       } else if (res == fds[0]) {
+                               gettimeofday(&tv, NULL);
+                               if (last.tv_sec || last.tv_usec) {
+                                       /* We should wait at least a frame length */
+                                       us = sizeof(myf.frdata) / 16 * 1000;
+                                       /* Subtract 1,000,000 us for each second late we've passed */
+                                       us -= (tv.tv_sec - last.tv_sec) * 1000000;
+                                       /* And one for each us late we've passed */
+                                       us -= (tv.tv_usec - last.tv_usec);
+                                       /* Sleep that long if needed */
+                                       if (us > 0)
+                                               usleep(us);
+                               }
+                               last = tv;
+                               res = read(fds[0], myf.frdata, sizeof(myf.frdata));
+                               if (res > 0) {
+                                       myf.f.frametype = AST_FRAME_VOICE;
+                                       myf.f.subclass = AST_FORMAT_SLINEAR;
+                                       myf.f.datalen = res;
+                                       myf.f.timelen = res / 16;
+                                       myf.f.mallocd = 0;
+                                       myf.f.offset = AST_FRIENDLY_OFFSET;
+                                       myf.f.src = __PRETTY_FUNCTION__;
+                                       myf.f.data = myf.frdata;
+                                       if (ast_write(chan, &myf.f) < 0) {
+                                               res = -1;
+                                               break;
+                                       }
+                                       if (res < sizeof(myf.frdata)) { // last frame
+                                               ast_log(LOG_WARNING, "Last frame\n");
+                                               res=0;
+                                               break;
+                                       }
+                               } else {
+                                       ast_log(LOG_WARNING, "No more waveform\n");
+                                       res = 0;
+                               }
+                       } else {
+                               ast_log(LOG_DEBUG, "HuhHHH?\n");
+                               res = -1;
+                               break;
+                       }
+               }
+       }
+       close(fds[0]);
+       close(fds[1]);
+//     if (pid > -1)
+//             kill(pid, SIGKILL);
+       if (!res && owriteformat)
+               ast_set_write_format(chan, owriteformat);
+       return res;
+}
+
+#define MAXLEN 180
+#define MAXFESTLEN 2048
+
+
+
+
+static int festival_exec(struct ast_channel *chan, void *data)
+{
+       int usecache;
+       int res=0;
+       struct localuser *u;
+       struct sockaddr_in serv_addr;
+       struct hostent *serverhost;
+       int fd;
+       FILE *fs;
+       char *host;
+       char *cachedir;
+       char *temp;
+       char *festivalcommand;
+       int port=1314;
+       int n;
+       char ack[4];
+       char *waveform;
+       int filesize;
+       int wave;
+       char bigstring[MAXFESTLEN];
+       int i;
+       struct MD5Context md5ctx;
+       unsigned char MD5Res[16];
+       char MD5Hex[32];
+       char koko[4];
+       char cachefile[MAXFESTLEN];
+       int readcache=0;
+       int writecache=0;
+       int strln;
+       int fdesc;
+       char buffer[16384];
+       int seekpos;    
+       
+       struct ast_config *cfg;
+       cfg = ast_load(FESTIVAL_CONFIG);
+       if (!cfg) {
+               ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
+               return -1;
+       }
+       if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
+               host = "localhost";
+       }
+       if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
+               port = 1314;
+       } else {
+               port = atoi(temp);
+       }
+       if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
+               usecache=0;
+       } else {
+               if (strcasecmp(temp,"yes")==0) {
+                       usecache=1;
+               }
+       }
+       if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
+               cachedir = "/tmp/";
+       }
+       if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
+               festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
+       }
+       
+               
+
+       if (!data) {
+               ast_log(LOG_WARNING, "festival requires an argument (text)\n");
+               return -1;
+       }
+       LOCAL_USER_ADD(u);
+       ast_log(LOG_WARNING, "Text passed to festival server : %s\n",(char *)data);
+       /* Connect to local festival server */
+       
+       fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+
+       if (fd < 0) {
+               ast_log(LOG_WARNING,"festival_client: can't get socket\n");
+               return -1;
+       }
+        memset(&serv_addr, 0, sizeof(serv_addr));
+        if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
+               /* its a name rather than an ipnum */
+               serverhost = gethostbyname(host);
+               if (serverhost == (struct hostent *)0) {
+                       ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
+                       return -1;
+               }
+               memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
+       }
+       serv_addr.sin_family = AF_INET;
+       serv_addr.sin_port = htons(port);
+
+       if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
+               ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
+               return -1;
+       }
+       
+       /* Compute MD5 sum of string */
+       MD5Init(&md5ctx);
+       MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
+       MD5Final(MD5Res,&md5ctx);
+       strcpy(MD5Hex,"");
+       
+       /* Convert to HEX and look if there is any matching file in the cache 
+               directory */
+       for (i=0;i<16;i++) {
+               sprintf(koko,"%X",MD5Res[i]);
+               strcat(MD5Hex,koko);
+       }
+       readcache=0;
+       writecache=0;
+       if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==1)) {
+               sprintf(cachefile,"%s/%s",cachedir,MD5Hex);
+               fdesc=open(cachefile,O_RDWR);
+               if (fdesc==-1) {
+                       fdesc=open(cachefile,O_CREAT|O_RDWR,0);
+                       if (fdesc!=-1) {
+                               writecache=1;
+                               strln=strlen((char *)data);
+                               ast_log(LOG_WARNING,"line length : %d\n",strln);
+                               write(fdesc,&strln,sizeof(int));
+                               write(fdesc,data,strln);
+                               seekpos=lseek(fdesc,0,SEEK_CUR);
+                               ast_log(LOG_WARNING,"Seek position : %d\n",seekpos);
+                       }
+               } else {
+                       read(fdesc,&strln,sizeof(int));
+                       ast_log(LOG_WARNING,"Cache file exists, strln=%d, strlen=%d\n",strln,strlen((char *)data));
+                       if (strlen((char *)data)==strln) {
+                               ast_log(LOG_WARNING,"Size OK\n");
+                               read(fdesc,&bigstring,strln);
+                               if (strcmp(bigstring,data)==0) { 
+                                       readcache=1;
+                               } else {
+                                       ast_log(LOG_WARNING,"Strings do not match\n");
+                               }
+                       } else {
+                               ast_log(LOG_WARNING,"Size mismatch\n");
+                       }
+               }
+       }
+                       
+       if (readcache==1) {
+               close(fd);
+               fd=fdesc;
+               ast_log(LOG_WARNING,"Reading from cache...\n");
+       } else {
+               ast_log(LOG_WARNING,"Passing text to festival...\n");
+               fs=fdopen(dup(fd),"wb");
+               fprintf(fs,festivalcommand,(char *)data);
+               fflush(fs);
+               fclose(fs);
+       }
+       
+       /* Write to cache and then pass it down */
+       if (writecache==1) {
+               ast_log(LOG_WARNING,"Writing result to cache...\n");
+               while ((strln=read(fd,buffer,16384))!=0) {
+                       write(fdesc,buffer,strln);
+               }
+               close(fd);
+               close(fdesc);
+               fd=open(cachefile,O_RDWR);
+               lseek(fd,seekpos,SEEK_SET);
+       }
+       
+       ast_log(LOG_WARNING,"Passing data to channel...\n");
+       
+       /* Read back info from server */
+       /* This assumes only one waveform will come back, also LP is unlikely */
+       wave = 0;
+       do {
+               for (n=0; n < 3; )
+                       n += read(fd,ack+n,3-n);
+               ack[3] = '\0';
+               if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
+                       ast_log(LOG_WARNING,"Festival WV command");
+                       waveform = socket_receive_file_to_buff(fd,&filesize);
+                       send_waveform_to_channel(chan,waveform,filesize);
+                       free(waveform);
+                       res=0;
+                       break;
+               }
+               else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
+                       ast_log(LOG_WARNING,"Festival LP command");
+                       waveform = socket_receive_file_to_buff(fd,&filesize);
+                       waveform[filesize]='\0';
+                       ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
+                       free(waveform);
+               } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
+                       ast_log(LOG_WARNING,"Festival returned ER\n");
+                       res=-1;
+                       break;
+               }
+       } while (strcmp(ack,"OK\n") != 0);
+       close(fd);
+       LOCAL_USER_REMOVE(u);                                                                                
+       return res;
+
+}
+
+int unload_module(void)
+{
+       STANDARD_HANGUP_LOCALUSERS;
+       return ast_unregister_application(app);
+}
+
+int load_module(void)
+{
+       
+       return ast_register_application(app, festival_exec, synopsis, descrip);
+}
+
+char *description(void)
+{
+       return tdesc;
+}
+
+int usecount(void)
+{
+       int res;
+       STANDARD_USECOUNT(res);
+       return res;
+}
+
+char *key()
+{
+       return ASTERISK_GPL_KEY;
+}
diff --git a/apps/app_sql_postgres.c b/apps/app_sql_postgres.c
new file mode 100755 (executable)
index 0000000..628987a
--- /dev/null
@@ -0,0 +1,546 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * Connect to PostgreSQL
+ * 
+ * Copyright (C) 2002, Christos Ricudis
+ *
+ * Christos Ricudis <ricudis@paiko.gr>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+#include <asterisk/file.h>
+#include <asterisk/logger.h>
+#include <asterisk/channel.h>
+#include <asterisk/pbx.h>
+#include <asterisk/module.h>
+#include <asterisk/linkedlists.h>
+#include <asterisk/chanvars.h>
+#include <asterisk/lock.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <pthread.h>
+#include "libpq-fe.h"
+
+
+
+
+static char *tdesc = "Simple PostgreSQL Interface";
+
+static char *app = "PGSQL";
+
+static char *synopsis = "Do several SQLy things";
+
+static char *descrip = 
+"  PGSQL():  Do several SQLy things\n";
+
+/*
+
+Syntax of SQL commands : 
+
+       Connect #var option-string
+       
+       Connects to a database using the option-string and stores the 
+       connection identifier in $var
+       
+       
+       Query var connection-identifier query-string
+       
+       Submits query-string to database backend and stores the result
+       identifier in ${var}
+       
+       
+       Fetch statusvar result-identifier var1 var2 var3 ... varn
+       
+       Fetches a row from the query and stores end-of-table status in 
+       ${statusvar} and columns in ${var1}..${varn}
+       
+       
+       Clear result-identifier
+
+       Clears data structures associated with result-identifier
+       
+       
+       Disconnect connection-identifier
+       
+       Disconnects from named connection
+       
+       
+EXAMPLES OF USE : 
+
+(
+ $2 = Connection Identifier
+ $3 = Result Identifier
+ $4 = Fetch Status Identifier (0 = no more rows)
+ $5, $6 = Data variables
+)
+
+exten => s,2,PGSQL,"Connect connid host=localhost user=asterisk dbname=credit";
+exten => s,3,PGSQL,"Query resultid ${connid} SELECT username,credit FROM credit WHERE callerid=${callerid}";
+exten => s,4,PGSQL,"Fetch fetchid ${resultid} datavar1 datavar2";
+exten => s,5,GotoIf,"${fetchid}=1?s|6:s|8";
+exten => s,6,blablabla ${datavar1} ${datavar2} (does blablabla, datavar1 = username, datavar2 = credit);
+exten => s,7,Goto,s|4
+exten => s,8,PGSQL,"Clear ${resultid}";
+exten => s,9,PGSQL,"Disconnect ${connid}";
+
+*/
+
+STANDARD_LOCAL_USER;
+
+LOCAL_USER_DECL;
+
+extern void pbx_builtin_setvar_helper(struct ast_channel *chan, char *name, char *value); 
+
+#define AST_PGSQL_ID_DUMMY 0
+#define AST_PGSQL_ID_CONNID 1
+#define AST_PGSQL_ID_RESID 2
+#define AST_PGSQL_ID_FETCHID 3
+
+struct ast_PGSQL_id {
+       int identifier_type; /* 0=dummy, 1=connid, 2=resultid */
+       int identifier;
+       void *data;
+       AST_LIST_ENTRY(ast_PGSQL_id) entries;
+} *ast_PGSQL_id;
+
+AST_LIST_HEAD(PGSQLidshead,ast_PGSQL_id) PGSQLidshead;
+
+static void *find_identifier(int identifier,int identifier_type) {
+       struct PGSQLidshead *headp;
+       struct ast_PGSQL_id *i;
+       void *res=NULL;
+       int found=0;
+       
+       headp=&PGSQLidshead;
+       
+       if (AST_LIST_LOCK(headp)) {
+               ast_log(LOG_WARNING,"Unable to lock identifiers list\n");
+       } else {
+               AST_LIST_TRAVERSE(headp,i,entries) {
+                       if ((i->identifier==identifier) && (i->identifier_type==identifier_type)) {
+                               found=1;
+                               res=i->data;
+                               break;
+                       }
+               }
+               if (!found) {
+                       ast_log(LOG_WARNING,"Identifier %d, identifier_type %d not found in identifier list\n",identifier,identifier_type);
+               }
+               AST_LIST_UNLOCK(headp);
+       }
+       
+       return(res);
+}
+
+static int add_identifier(int identifier_type,void *data) {
+       struct ast_PGSQL_id *i,*j;
+       struct PGSQLidshead *headp;
+       int maxidentifier=0;
+       
+       headp=&PGSQLidshead;
+       i=NULL;
+       j=NULL;
+       
+       if (AST_LIST_LOCK(headp)) {
+               ast_log(LOG_WARNING,"Unable to lock identifiers list\n");
+               return(-1);
+       } else {
+               i=malloc(sizeof(struct ast_PGSQL_id));
+               AST_LIST_TRAVERSE(headp,j,entries) {
+                       if (j->identifier>maxidentifier) {
+                               maxidentifier=j->identifier;
+                       }
+               }
+               
+               i->identifier=maxidentifier+1;
+               i->identifier_type=identifier_type;
+               i->data=data;
+               AST_LIST_INSERT_HEAD(headp,i,entries);
+               AST_LIST_UNLOCK(headp);
+       }
+       return(i->identifier);
+}
+
+static int del_identifier(int identifier,int identifier_type) {
+       struct ast_PGSQL_id *i;
+       struct PGSQLidshead *headp;
+       int found=0;
+       
+        headp=&PGSQLidshead;
+        
+        if (AST_LIST_LOCK(headp)) {
+               ast_log(LOG_WARNING,"Unable to lock identifiers list\n");
+       } else {
+               AST_LIST_TRAVERSE(headp,i,entries) {
+                       if ((i->identifier==identifier) && 
+                           (i->identifier_type==identifier_type)) {
+                               AST_LIST_REMOVE(headp,i,ast_PGSQL_id,entries);
+                               free(i);
+                               found=1;
+                               break;
+                       }
+               }
+               AST_LIST_UNLOCK(headp);
+       }
+                       
+       if (found==0) {
+               ast_log(LOG_WARNING,"Could not find identifier %d, identifier_type %d in list to delete\n",identifier,identifier_type);
+               return(-1);
+       } else {
+               return(0);
+       }
+}
+
+static int aPGSQL_connect(struct ast_channel *chan, void *data) {
+       
+       char *ptrptr;
+       char *s1,*s4;
+       char s[100];
+       char *optionstring;
+       char *var;
+       int l;
+       int res;
+       PGconn *karoto;
+       int id;
+        
+       
+       res=0;
+       l=strlen(data)+2;
+       s1=malloc(l);
+       strncpy(s1,data,l);
+       strtok_r(s1," ",&ptrptr); // eat the first token, we already know it :P 
+       var=strtok_r(NULL," ",&ptrptr);
+       optionstring=strtok_r(NULL,"\n",&ptrptr);
+               
+       karoto = PQconnectdb(optionstring);
+        if (PQstatus(karoto) == CONNECTION_BAD) {
+               ast_log(LOG_WARNING,"Connection to database using '%s' failed. postgress reports : %s\n", optionstring,
+                                                 PQerrorMessage(karoto));
+               res=-1;
+        } else {
+               ast_log(LOG_WARNING,"adding identifier\n");
+               id=add_identifier(AST_PGSQL_ID_CONNID,karoto);
+               s4=&s[0];
+               sprintf(s4,"%d",id);
+               pbx_builtin_setvar_helper(chan,var,s);
+       }
+       
+       free(s1);
+       return res;
+}
+
+static int aPGSQL_query(struct ast_channel *chan, void *data) {
+       
+       char *ptrptr;
+       char *s1,*s2,*s3,*s4,*s5;
+       char s[100];
+       char *querystring;
+       char *var;
+       int l;
+       int res,nres;
+       PGconn *karoto;
+       PGresult *PGSQLres;
+       int id,id1;
+        
+       
+       res=0;
+       l=strlen(data)+2;
+       s1=malloc(l);
+       s2=malloc(l);
+       strcpy(s1,data);
+       strtok_r(s1," ",&ptrptr); // eat the first token, we already know it :P 
+       s3=strtok_r(NULL," ",&ptrptr);
+       while (1) {     // ugly trick to make branches with break;
+               var=s3;
+               s4=strtok_r(NULL," ",&ptrptr);
+               id=atoi(s4);
+               querystring=strtok_r(NULL,"\n",&ptrptr);
+               if ((karoto=find_identifier(id,AST_PGSQL_ID_CONNID))==NULL) {
+                       ast_log(LOG_WARNING,"Invalid connection identifier %d passed in aPGSQL_query\n",id);
+                       res=-1;
+                       break;
+               }
+               PGSQLres=PQexec(karoto,querystring);
+               if (PGSQLres==NULL) {
+                       ast_log(LOG_WARNING,"aPGSQL_query: Connection Error (connection identifier = %d, error message : %s)\n",id,PQerrorMessage(karoto));
+                       res=-1;
+                       break;
+               }
+               if (PQresultStatus(PGSQLres) == PGRES_BAD_RESPONSE ||
+                   PQresultStatus(PGSQLres) == PGRES_NONFATAL_ERROR ||
+                   PQresultStatus(PGSQLres) == PGRES_FATAL_ERROR) {
+                       ast_log(LOG_WARNING,"aPGSQL_query: Query Error (connection identifier : %d, error message : %s)\n",id,PQcmdStatus(PGSQLres));
+                       res=-1;
+                       break;
+               }
+               nres=PQnfields(PGSQLres); 
+               id1=add_identifier(AST_PGSQL_ID_RESID,PGSQLres);
+               s5=&s[0];
+               sprintf(s5,"%d",id1);
+               pbx_builtin_setvar_helper(chan,var,s);
+               break;
+       }
+       
+       free(s1);
+       free(s2);
+       return(res);
+}
+
+
+static int aPGSQL_fetch(struct ast_channel *chan, void *data) {
+       
+       char *ptrptr;
+       char *s1,*s2,*s3,*s4,*s5,*s6,*s7;
+       char s[100];
+       char *var;
+       int l;
+       int res;
+       PGresult *PGSQLres;
+       int id,id1,i,j,fnd;
+       int *lalares=NULL;
+       int nres;
+        struct ast_var_t *variables;
+        struct varshead *headp;
+        
+        headp=&chan->varshead;
+       
+       res=0;
+       l=strlen(data)+2;
+       s7=NULL;
+       s1=malloc(l);
+       s2=malloc(l);
+       strcpy(s1,data);
+       strtok_r(s1," ",&ptrptr); // eat the first token, we already know it :P 
+       s3=strtok_r(NULL," ",&ptrptr);
+       while (1) {     // ugly trick to make branches with break;
+               var=s3; // fetchid
+               fnd=0;
+               
+               AST_LIST_TRAVERSE(headp,variables,entries) {
+                       if (strncasecmp(ast_var_name(variables),s3,strlen(s3))==0) {
+                               s7=ast_var_value(variables);
+                               fnd=1;
+                                break;
+                       }
+               }
+               
+               if (fnd==0) { 
+                       s7="0";
+                       pbx_builtin_setvar_helper(chan,s3,s7);
+               }
+
+               s4=strtok_r(NULL," ",&ptrptr);
+               id=atoi(s4); // resultid
+               if ((PGSQLres=find_identifier(id,AST_PGSQL_ID_RESID))==NULL) {
+                       ast_log(LOG_WARNING,"Invalid result identifier %d passed in aPGSQL_fetch\n",id);
+                       res=-1;
+                       break;
+               }
+               id=atoi(s7); //fetchid
+               if ((lalares=find_identifier(id,AST_PGSQL_ID_FETCHID))==NULL) {
+                       i=0;
+               } else {
+                       i=*lalares;
+                       free(lalares);
+                       del_identifier(id,AST_PGSQL_ID_FETCHID);
+               }
+               nres=PQnfields(PGSQLres); 
+               ast_log(LOG_WARNING,"ast_PGSQL_fetch : nres = %d i = %d ;\n",nres,i);
+               for (j=0;j<nres;j++) {
+                       s5=strtok_r(NULL," ",&ptrptr);
+                       if (s5==NULL) {
+                               ast_log(LOG_WARNING,"ast_PGSQL_fetch : More tuples (%d) than variables (%d)\n",nres,j);
+                               break;
+                       }
+                       
+                       s6=PQgetvalue(PGSQLres,i,j);
+                       if (s6==NULL) { 
+                               ast_log(LOG_WARNING,"PWgetvalue(res,%d,%d) returned NULL in ast_PGSQL_fetch\n",i,j);
+                               break;
+                       }
+                       ast_log(LOG_WARNING,"===setting variable '%s' to '%s'\n",s5,s6);
+                       pbx_builtin_setvar_helper(chan,s5,s6);
+               }
+               i++;
+               if (i<PQntuples(PGSQLres)) {
+                       lalares=malloc(sizeof(int));
+                       *lalares=i;
+                       id1=add_identifier(AST_PGSQL_ID_FETCHID,lalares);
+               } else {
+                       id1=0;
+               }
+               s5=&s[0];
+               sprintf(s5,"%d",id1);
+               ast_log(LOG_WARNING,"Setting var '%s' to value '%s'\n",s3,s);
+               pbx_builtin_setvar_helper(chan,s3,s);
+               break;
+       }
+       
+       free(s1);
+       free(s2);
+       return(res);
+}
+
+static int aPGSQL_reset(struct ast_channel *chan, void *data) {
+       
+       char *ptrptr;
+       char *s1,*s3;
+       int l;
+       PGconn *karoto;
+       int id;
+        
+       
+       l=strlen(data)+2;
+       s1=malloc(l);
+       strcpy(s1,data);
+       strtok_r(s1," ",&ptrptr); // eat the first token, we already know it :P 
+       s3=strtok_r(NULL," ",&ptrptr);
+       id=atoi(s3);
+       if ((karoto=find_identifier(id,AST_PGSQL_ID_CONNID))==NULL) {
+               ast_log(LOG_WARNING,"Invalid connection identifier %d passed in aPGSQL_reset\n",id);
+       } else {
+               PQreset(karoto);
+       } 
+       free(s1);
+       return(0);
+       
+}
+
+static int aPGSQL_clear(struct ast_channel *chan, void *data) {
+       
+       char *ptrptr;
+       char *s1,*s3;
+       int l;
+       PGresult *karoto;
+       int id;
+        
+       
+       l=strlen(data)+2;
+       s1=malloc(l);
+       strcpy(s1,data);
+       strtok_r(s1," ",&ptrptr); // eat the first token, we already know it :P 
+       s3=strtok_r(NULL," ",&ptrptr);
+       id=atoi(s3);
+       if ((karoto=find_identifier(id,AST_PGSQL_ID_RESID))==NULL) {
+               ast_log(LOG_WARNING,"Invalid result identifier %d passed in aPGSQL_clear\n",id);
+       } else {
+               PQclear(karoto);
+               del_identifier(id,AST_PGSQL_ID_RESID);
+       }
+       free(s1);
+       return(0);
+       
+}
+
+          
+          
+       
+static int aPGSQL_disconnect(struct ast_channel *chan, void *data) {
+       
+       char *ptrptr;
+       char *s1,*s3;
+       int l;
+       PGconn *karoto;
+       int id;
+        
+       
+       l=strlen(data)+2;
+       s1=malloc(l);
+       strcpy(s1,data);
+       strtok_r(s1," ",&ptrptr); // eat the first token, we already know it :P 
+       s3=strtok_r(NULL," ",&ptrptr);
+       id=atoi(s3);
+       if ((karoto=find_identifier(id,AST_PGSQL_ID_CONNID))==NULL) {
+               ast_log(LOG_WARNING,"Invalid connection identifier %d passed in aPGSQL_disconnect\n",id);
+       } else {
+               PQfinish(karoto);
+               del_identifier(id,AST_PGSQL_ID_CONNID);
+       } 
+       free(s1);
+       return(0);
+       
+}
+
+static int aPGSQL_debug(struct ast_channel *chan, void *data) {
+       ast_log(LOG_WARNING,"Debug : %s\n",(char *)data);
+       return(0);
+}
+               
+       
+
+static int PGSQL_exec(struct ast_channel *chan, void *data)
+{
+       struct localuser *u;
+       int result;
+
+       if (!data) {
+               ast_log(LOG_WARNING, "APP_PGSQL requires an argument (see manual)\n");
+               return -1;
+       }
+       LOCAL_USER_ADD(u);
+       result=0;
+       
+       if (strncasecmp("connect",data,strlen("connect"))==0) {
+               result=(aPGSQL_connect(chan,data));
+       } else  if (strncasecmp("query",data,strlen("query"))==0) {
+               result=(aPGSQL_query(chan,data));
+       } else  if (strncasecmp("fetch",data,strlen("fetch"))==0) {
+               result=(aPGSQL_fetch(chan,data));
+       } else  if (strncasecmp("reset",data,strlen("reset"))==0) {
+               result=(aPGSQL_reset(chan,data));
+       } else  if (strncasecmp("clear",data,strlen("clear"))==0) {
+               result=(aPGSQL_clear(chan,data));
+       } else  if (strncasecmp("debug",data,strlen("debug"))==0) {
+               result=(aPGSQL_debug(chan,data));
+       } else  if (strncasecmp("disconnect",data,strlen("disconnect"))==0) {
+               result=(aPGSQL_disconnect(chan,data));
+       } else {
+               ast_log(LOG_WARNING, "Unknown APP_PGSQL argument : %s\n",(char *)data);
+               result=-1;      
+       }
+               
+       LOCAL_USER_REMOVE(u);                                                                                
+       return result;
+
+}
+
+int unload_module(void)
+{
+       STANDARD_HANGUP_LOCALUSERS;
+       return ast_unregister_application(app);
+}
+
+int load_module(void)
+{
+       struct PGSQLidshead *headp;
+       
+        headp=&PGSQLidshead;
+        
+       AST_LIST_HEAD_INIT(headp);
+       return ast_register_application(app, PGSQL_exec, synopsis, descrip);
+}
+
+char *description(void)
+{
+       return tdesc;
+}
+
+int usecount(void)
+{
+       int res;
+       STANDARD_USECOUNT(res);
+       return res;
+}
+
+char *key()
+{
+       return ASTERISK_GPL_KEY;
+}
diff --git a/configs/festival.conf.sample b/configs/festival.conf.sample
new file mode 100755 (executable)
index 0000000..774f1a1
--- /dev/null
@@ -0,0 +1,35 @@
+;
+; Festival Configuration
+;
+[general]
+;
+; Host which runs the festival server (default : localhost);
+;
+;host=localhost
+;
+; Port on host where the festival server runs (default : 1314)
+;
+;port=1314
+;
+; Use cache (yes, no - defaults to no)
+;
+;usecache=yes
+;
+; If usecache=yes, a directory to store waveform cache files. 
+; The cache is never cleared (yet), so you must take care of cleaning it
+; yourself (just delete any or all files from the cache). 
+; THIS DIRECTORY *MUST* EXIST and must be writable from the asterisk process.
+; Defaults to /tmp/
+;
+;cachedir=/var/lib/asterisk/festivalcache/
+;
+; Festival command to send to the server.
+; Defaults to: (tts_textasterisk "%s" 'file)(quit)\n
+; %s is replaced by the desired text to say. The command MUST end with a 
+; (quit) directive, or the cache handling mechanism will hang. Do not 
+; forget the \n at the end. 
+; 
+;festivalcommand=(tts_textasterisk "%s" 'file)(quit)\n
+;
+;
+
diff --git a/contrib/README.festival b/contrib/README.festival
new file mode 100755 (executable)
index 0000000..56d985e
--- /dev/null
@@ -0,0 +1,22 @@
+
+app_festival is an application that allows one to send text-to-speech commands
+to a background festival server, and to obtain the resulting waveform which
+gets sent down to the respective channel. app_festival also employs a waveform 
+cache, so invariant text-to-speech strings ("Please press 1 for instructions") 
+do not need to be dynamically generated all the time. 
+
+You need : 
+
+1) festival, patched to produce 8khz waveforms on output. Patch for Festival
+1.4.1 RELEASE are included. The patch adds a new command to festival 
+(asterisk_tts). 
+
+2) My patches to asterisk that provide variable substitution and quoting to 
+the Asterisk Extension Logic. This is not really a requirement, but without
+this, app_festival is mostly useless (you could very well use prerecorded
+voices for static information). 
+
+3) Before running asterisk, you have to run festival-server with a command 
+like : 
+
+/usr/local/festival/bin/festival --server > /dev/null 2>&1 &
diff --git a/contrib/festival-1.4.1-diff b/contrib/festival-1.4.1-diff
new file mode 100755 (executable)
index 0000000..c8543d7
--- /dev/null
@@ -0,0 +1,76 @@
+diff -ruN festival/lib/tts.scm myfestival/lib/tts.scm
+--- festival/lib/tts.scm       Sun May 30 16:40:00 1999
++++ myfestival/lib/tts.scm     Wed Apr 17 22:29:34 2002
+@@ -200,6 +200,15 @@
+    (utt.synth
+     (eval (list 'Utterance 'Text string)))))
++(define (tts_textasterisk string mode)
++  "(tts_textasterisk STRING MODE)
++Apply tts to STRING.  This function is specifically designed for
++use in server mode so a single function call may synthesize the string.
++This function name maybe added to the server safe functions."
++  (utt.send.wave.asterisk 
++   (utt.synth
++    (eval (list 'Utterance 'Text string)))))
++
+ (define (tts_return_to_client)
+   "(tts_return_to_client)
+ This function is called by clients who wish to return waveforms of
+diff -ruN festival/src/arch/festival/wave.cc myfestival/src/arch/festival/wave.cc
+--- festival/src/arch/festival/wave.cc Sat Jun 12 10:30:30 1999
++++ myfestival/src/arch/festival/wave.cc       Thu Apr 18 10:55:32 2002
+@@ -375,6 +375,38 @@
+       type = "nist";
+     else
+       type = get_c_string(ltype);
++
++    w->save(tmpfile,type);
++    write(ft_server_socket,"WV\n",3);
++    socket_send_file(ft_server_socket,tmpfile);
++    unlink(tmpfile);
++
++    return utt;
++}
++
++static LISP utt_send_wave_asterisk(LISP utt)
++{
++    // Send the waveform to a client (must be acting as server)
++    EST_Utterance *u = utterance(utt);
++    EST_Wave *w;
++    EST_String tmpfile = make_tmp_filename();
++    LISP ltype;
++    EST_String type;
++
++    w = get_utt_wave(u);
++    if (ft_server_socket == -1)
++    {
++      cerr << "utt_send_wave_client: not in server mode" << endl;
++      festival_error();
++    }
++      
++    ltype = ft_get_param("Wavefiletype");
++    if (ltype == NIL)
++      type = "nist";
++    else
++      type = get_c_string(ltype);
++    w->resample(8000);
++    w->rescale(5);
+     w->save(tmpfile,type);
+     write(ft_server_socket,"WV\n",3);
+     socket_send_file(ft_server_socket,tmpfile);
+@@ -434,6 +466,13 @@
+  "(utt.send.wave.client UTT)\n\
+   Sends wave in UTT to client.  If not in server mode gives an error\n\
+   Note the client must be expecting to receive the waveform.");
++
++    init_subr_1("utt.send.wave.asterisk",utt_send_wave_asterisk,
++ "(utt.send.wave.asterisk UTT)\n\
++  Sends wave in UTT to client.  If not in server mode gives an error\n\
++  Note the client must be expecting to receive the waveform. The waveform
++  is rescaled and resampled according to what asterisk needs");
++
+     init_subr_2("utt.save.f0",utt_save_f0,
+  "(utt.save.f0 UTT FILENAME)\n\
+  Save F0 of UTT as esps track file in FILENAME.");
diff --git a/festival-1.4.1-diff b/festival-1.4.1-diff
new file mode 100755 (executable)
index 0000000..c8543d7
--- /dev/null
@@ -0,0 +1,76 @@
+diff -ruN festival/lib/tts.scm myfestival/lib/tts.scm
+--- festival/lib/tts.scm       Sun May 30 16:40:00 1999
++++ myfestival/lib/tts.scm     Wed Apr 17 22:29:34 2002
+@@ -200,6 +200,15 @@
+    (utt.synth
+     (eval (list 'Utterance 'Text string)))))
++(define (tts_textasterisk string mode)
++  "(tts_textasterisk STRING MODE)
++Apply tts to STRING.  This function is specifically designed for
++use in server mode so a single function call may synthesize the string.
++This function name maybe added to the server safe functions."
++  (utt.send.wave.asterisk 
++   (utt.synth
++    (eval (list 'Utterance 'Text string)))))
++
+ (define (tts_return_to_client)
+   "(tts_return_to_client)
+ This function is called by clients who wish to return waveforms of
+diff -ruN festival/src/arch/festival/wave.cc myfestival/src/arch/festival/wave.cc
+--- festival/src/arch/festival/wave.cc Sat Jun 12 10:30:30 1999
++++ myfestival/src/arch/festival/wave.cc       Thu Apr 18 10:55:32 2002
+@@ -375,6 +375,38 @@
+       type = "nist";
+     else
+       type = get_c_string(ltype);
++
++    w->save(tmpfile,type);
++    write(ft_server_socket,"WV\n",3);
++    socket_send_file(ft_server_socket,tmpfile);
++    unlink(tmpfile);
++
++    return utt;
++}
++
++static LISP utt_send_wave_asterisk(LISP utt)
++{
++    // Send the waveform to a client (must be acting as server)
++    EST_Utterance *u = utterance(utt);
++    EST_Wave *w;
++    EST_String tmpfile = make_tmp_filename();
++    LISP ltype;
++    EST_String type;
++
++    w = get_utt_wave(u);
++    if (ft_server_socket == -1)
++    {
++      cerr << "utt_send_wave_client: not in server mode" << endl;
++      festival_error();
++    }
++      
++    ltype = ft_get_param("Wavefiletype");
++    if (ltype == NIL)
++      type = "nist";
++    else
++      type = get_c_string(ltype);
++    w->resample(8000);
++    w->rescale(5);
+     w->save(tmpfile,type);
+     write(ft_server_socket,"WV\n",3);
+     socket_send_file(ft_server_socket,tmpfile);
+@@ -434,6 +466,13 @@
+  "(utt.send.wave.client UTT)\n\
+   Sends wave in UTT to client.  If not in server mode gives an error\n\
+   Note the client must be expecting to receive the waveform.");
++
++    init_subr_1("utt.send.wave.asterisk",utt_send_wave_asterisk,
++ "(utt.send.wave.asterisk UTT)\n\
++  Sends wave in UTT to client.  If not in server mode gives an error\n\
++  Note the client must be expecting to receive the waveform. The waveform
++  is rescaled and resampled according to what asterisk needs");
++
+     init_subr_2("utt.save.f0",utt_save_f0,
+  "(utt.save.f0 UTT FILENAME)\n\
+  Save F0 of UTT as esps track file in FILENAME.");
diff --git a/pbx.c b/pbx.c
index ec52c05..b0ac72a 100755 (executable)
--- a/pbx.c
+++ b/pbx.c
 #include <asterisk/callerid.h>
 #include <asterisk/cdr.h>
 #include <asterisk/term.h>
+#include <asterisk/manager.h>
+#include <asterisk/ast_expr.h>
+#include <asterisk/channel_pvt.h>
+#include <asterisk/linkedlists.h>
 #include <string.h>
 #include <unistd.h>
 #include <stdlib.h>
@@ -140,6 +144,10 @@ static int pbx_builtin_setlanguage(struct ast_channel *, void *);
 static int pbx_builtin_ringing(struct ast_channel *, void *);
 static int pbx_builtin_congestion(struct ast_channel *, void *);
 static int pbx_builtin_busy(struct ast_channel *, void *);
+static int pbx_builtin_setvar(struct ast_channel *, void *);
+static int pbx_builtin_gotoif(struct ast_channel *, void *);
+void pbx_builtin_setvar_helper(struct ast_channel *chan, char *name, char *value);
+
 
 static struct pbx_builtin {
        char name[AST_MAX_APP];
@@ -248,6 +256,17 @@ static struct pbx_builtin {
 "  Busy(): Requests that the channel indicate busy condition and then waits\n"
 "for the user to hang up.  Always returns -1." },
 
+       { "Setvar", pbx_builtin_setvar,
+"Set variable to value",
+"  Setvar(#n=value): Sets variable n to value" },
+
+       { "GotoIf", pbx_builtin_gotoif,
+"Conditional goto",
+"  GotoIf(Condition?label1:label2): Go to label 1 if condition is\n"
+"true, to label2 if condition is false. Either label1 or label2 may be\n"
+"omitted (in that case, we just don't take the particular branch) but not\n"
+"both.  Look for the condition syntax in examples or documentation." },
+
 };
 
 /* Lock for the application list */
@@ -623,12 +642,224 @@ static struct ast_exten *pbx_find_extension(struct ast_channel *chan, char *cont
        return NULL;
 }
 
+static void *pbx_substitute_variables(struct ast_channel *c, struct ast_exten *e) {
+       char *cp1,*cp3,*cp4,*cp5;
+       void *cp2;
+       char c1,c2;
+       int m,mve,origlen,quoted,dolsign,docopy;
+       struct ast_var_t *variables;
+       struct varshead *headp;
+       char pri[80];
+        
+        headp=&c->varshead;
+        origlen=strlen(e->data)+1;
+       cp2=malloc(origlen);
+        memset(cp2,0,origlen);
+        
+       if ((strchr(e->data,'$')==NULL) && (strchr(e->data,'[')==NULL)) {
+               strncpy(cp2,e->data,strlen(e->data));
+               return(cp2);
+               /* No variables or expressions in e->data, so why scan it? */
+       }
+       
+       cp4=NULL;
+       cp1=e->data;
+       quoted=0;
+       dolsign=0;
+       docopy=1;
+       
+       /* First stage, variable substitution */
+       
+       do {
+               c1=*cp1;
+               mve=0;
+               switch (c1) {
+                       case '\\' :
+                               dolsign=0;
+                               if (quoted==1) {
+                                       quoted=0;
+                                       docopy=1;
+                               } else {
+                                       quoted=1;
+                                       docopy=0;
+                               }
+                               break;
+                       case '$' :
+                               if (quoted==1) {
+                                       quoted=0;
+                                       docopy=1;
+                                       dolsign=0;
+                               } else {
+                                       docopy=0;
+                                       dolsign=1;
+                               }
+                               break;
+                       case '{' : 
+                               if (quoted==1) {
+                                       quoted=0;
+                                       dolsign=0;
+                                       docopy=1;
+                                       break;
+                               }
+                               if (dolsign==0) {
+                                       docopy=1;
+                                       break;
+                               }
+                               docopy=0;
+                               dolsign=0;
+                               m=0;
+                               cp1++;
+                               while (((c2=*(cp1+m))!='}') && (c2!='\0')) {
+                                       m++;
+                               }
+                               mve=1;
+                               cp3=malloc(m+2);
+                               strncpy(cp3,cp1,m);
+                               cp3[m]='\0';
+                               cp1+=m;
+                               /* Now we have the variable name on cp3 */
+                               if (!strcmp(cp3, "CALLERID")) {
+                                               cp4 = c->callerid;
+                                               break;
+                                       } else if (!strcmp(cp3, "EXTEN")) {
+                                               cp4 = c->exten;
+                                               break;
+                                       } else if (!strcmp(cp3, "CONTEXT")) {
+                                               cp4 = c->context;
+                                               break;
+                                       } else if (!strcmp(cp3, "PRIORITY")) {
+                                               snprintf(pri, sizeof(pri), "%d", c->priority);
+                                               cp4 = pri;
+                                               break;
+                                       } else {
+                                       AST_LIST_TRAVERSE(headp,variables,entries) {
+//                                             ast_log(LOG_WARNING,"Comparing variable '%s' with '%s'\n",cp3,ast_var_name(variables));
+                                               if (strncasecmp(ast_var_name(variables),cp3,m)==0) {
+                                                       cp4=ast_var_value(variables);
+                                                       break;
+                                               }
+                                       }
+                               }
+                               free(cp3);
+                               break;
+                       default :
+                               if (dolsign==1) {
+                                       strncat((char *)cp2,"$",1);
+                               } 
+                               if (quoted==1) {
+                                       quoted=0;
+                               }
+                               mve=0;
+                               dolsign=0;
+                               docopy=1;
+                               break;
+               }
+               if (cp1!='\0') {
+                       if (mve==0) {
+                               if (docopy==1) {
+                                       strncat((char *)cp2,&c1,1);
+                               }
+                       } else {
+                               if (cp4!=NULL) {
+                                       cp2=realloc(cp2,origlen+strlen(cp4)+1);
+                                       strncat((char *)cp2,cp4,strlen(cp4));
+                               } else {
+                                       ast_log(LOG_WARNING,"mve!=0 and cp4=NULL, something gone astray\n");
+                               }
+                       }
+               }
+                       
+       } while (*cp1++!='\0');
+       
+       /* Second stage, expression evaluation */
+               
+       if ((strstr(cp2,"$[")==NULL)) {
+               /* No expressions in cp2, return it */
+               return(cp2);
+       }
+       /* else, do expression evaluation */
+       
+       dolsign=0;
+       docopy=1;
+       origlen=strlen(cp2)+1;
+       
+       cp5=malloc(origlen);
+       memset(cp5,0,origlen);
+       cp4=NULL;
+       cp1=cp2;
+       quoted=0;
+       
+       do {
+               c1=*cp1;
+               mve=0;
+               switch (c1) {
+                       case '$' : 
+                               dolsign=1;
+                               docopy=0;
+                               break;
+                       case '[' :
+                               if (dolsign==0) {
+                                       docopy=1;
+                                       dolsign=0;
+                                       break;
+                               }
+                               dolsign=0;
+                               docopy=0;
+                               m=0;
+                               mve=1;
+                               cp1++;
+                               
+                               while (((c2=*(cp1+m))!=']') && (c2!='\0')) {
+                                       m++;
+                               }
+                               cp3=malloc(m+2);
+                               strncpy(cp3,cp1,m);
+                               cp3[m]='\0';
+                               cp1+=m;
+                               /* Now we have the expression to evaluate on cp3 */
+                               cp4=ast_expr(cp3);
+                               free(cp3);
+                               break;
+                       default :
+                               if (dolsign==1) {
+                                       strncat((char *)cp5,"$",1);
+                               }
+                               dolsign=0;
+                               docopy=1;
+                               mve=0;
+                               break;
+               }
+               if (cp1!='\0') {
+                       if (mve==0) {
+                               if (docopy==1) {
+                                       strncat((char *)cp5,&c1,1);
+                               }
+                       } else {
+                               if (cp4!=NULL) {
+                                       cp5=realloc(cp5,origlen+strlen(cp4)+1);
+                                       strncat((char *)cp5,cp4,strlen(cp4));
+                                       free(cp4);
+                               } else {
+                                       ast_log(LOG_WARNING,"mve!=0 and cp4=NULL, something gone astray\n");
+                               }
+                       }
+               }
+                       
+       } while (*cp1++!='\0');
+       free(cp2);
+       return(cp5);
+}
+                               
+                       
+                                                               
+
 static int pbx_extension_helper(struct ast_channel *c, char *context, char *exten, int priority, char *callerid, int action) 
 {
        struct ast_exten *e;
        struct ast_app *app;
        struct ast_switch *sw;
        char *data;
+       char *newdata;
        int newstack = 0;
        int res;
        int status = 0;
@@ -663,23 +894,24 @@ static int pbx_extension_helper(struct ast_channel *c, char *context, char *exte
                                strncpy(c->context, context, sizeof(c->context-1));
                                strncpy(c->exten, exten, sizeof(c->exten)-1);
                                c->priority = priority;
+                               newdata=pbx_substitute_variables(c,e);
                                if (option_debug)
                                                ast_log(LOG_DEBUG, "Launching '%s'\n", app->name);
                                else if (option_verbose > 2)
                                                ast_verbose( VERBOSE_PREFIX_3 "Executing %s(\"%s\", \"%s\") %s\n", 
                                                                term_color(tmp, app->name, COLOR_BRCYAN, 0, sizeof(tmp)),
                                                                term_color(tmp2, c->name, COLOR_BRMAGENTA, 0, sizeof(tmp2)),
-                                                               term_color(tmp3, (e->data ? (char *)e->data : NULL), COLOR_BRMAGENTA, 0, sizeof(tmp3)),
+                                                               term_color(tmp3, (newdata ? (char *)newdata : NULL), COLOR_BRMAGENTA, 0, sizeof(tmp3)),
                                                                (newstack ? "in new stack" : "in same stack"));
-                               res = pbx_exec(c, app, e->data, newstack);
+                               res = pbx_exec(c, app, newdata, newstack);
+                               free(newdata);
                                return res;
                        } else {
                                ast_log(LOG_WARNING, "No application '%s' for extension (%s, %s, %d)\n", e->app, context, exten, priority);
                                return -1;
                        }
                default:
-                       ast_log(LOG_WARNING, "Huh (%d)?\n", action);
-                       return -1;
+                       ast_log(LOG_WARNING, "Huh (%d)?\n", action);                    return -1;
                }
        } else if (sw) {
                switch(action) {
@@ -804,6 +1036,12 @@ int ast_pbx_run(struct ast_channel *c)
                digit = 0;
                while(ast_exists_extension(c, c->context, c->exten, c->priority, c->callerid)) {
                        memset(exten, 0, sizeof(exten));
+                       manager_event(EVENT_FLAG_CALL, "Newexten", 
+                               "Channel: %s\r\n"
+                               "Context: %s\r\n"
+                               "Extension: %s\r\n"
+                               "Priority: %d\r\n",
+                               c->name, c->context, c->exten, c->priority);                    
                        if ((res = ast_spawn_extension(c, c->context, c->exten, c->priority, c->callerid))) {
                                /* Something bad happened, or a hangup has been requested. */
                                switch(res) {
@@ -812,17 +1050,22 @@ int ast_pbx_run(struct ast_channel *c)
                                                ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited KEEPALIVE on '%s'\n", c->context, c->exten, c->priority, c->name);
                                        else if (option_verbose > 1)
                                                ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited KEEPALIVE on '%s'\n", c->context, c->exten, c->priority, c->name);
-                               break;
+                                       goto out;
+                                       break;
                                default:
                                        if (option_debug)
                                                ast_log(LOG_DEBUG, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n", c->context, c->exten, c->priority, c->name);
                                        else if (option_verbose > 1)
                                                ast_verbose( VERBOSE_PREFIX_2 "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", c->context, c->exten, c->priority, c->name);
+                                       if (c->_softhangup == AST_SOFTHANGUP_ASYNCGOTO) {
+                                               c->_softhangup =0;
+                                               break;
+                                       }
+                                       goto out;
                                }
-                               goto out;
                        }
-                       if (c->softhangup) {
-                               ast_log(LOG_WARNING, "Extension %s, priority %d returned normally even though call was hung up\n",
+                       if (c->_softhangup) {
+                               ast_log(LOG_DEBUG, "Extension %s, priority %d returned normally even though call was hung up\n",
                                        c->exten, c->priority);
                                goto out;
                        }
@@ -830,15 +1073,19 @@ int ast_pbx_run(struct ast_channel *c)
                        if (c->stream) {
                                digit = ast_waitstream(c, AST_DIGIT_ANY);
                                ast_stopstream(c);
-                               /* Hang up if something goes wrong */
-                               if (digit < 0) {
-                                       if (option_verbose > 2)
-                                               ast_verbose(VERBOSE_PREFIX_3 "Lost connection on %s\n", c->name);
-                                       goto out;
-                               }
-                               else if (digit) {
-                                       exten[pos++] = digit;
-                                       break;
+                               if (c->_softhangup == AST_SOFTHANGUP_ASYNCGOTO) {
+                                       c->_softhangup = 0;
+                               } else {
+                                       /* Hang up if something goes wrong */
+                                       if (digit < 0) {
+                                               if (option_verbose > 2)
+                                                       ast_verbose(VERBOSE_PREFIX_3 "Lost connection on %s\n", c->name);
+                                               goto out;
+                                       }
+                                       else if (digit) {
+                                               exten[pos++] = digit;
+                                               break;
+                                       }
                                }
                        }
                        firstpass = 0;
@@ -867,14 +1114,18 @@ int ast_pbx_run(struct ast_channel *c)
                                /* As long as we're willing to wait, and as long as it's not defined, 
                                   keep reading digits until we can't possibly get a right answer anymore.  */
                                digit = ast_waitfordigit(c, waittime * 1000);
-                               if (!digit)
-                                       /* No entry */
-                                       break;
-                               if (digit < 0)
-                                       /* Error, maybe a  hangup */
-                                       goto out;
-                               exten[pos++] = digit;
-                               waittime = c->pbx->dtimeout;
+                               if (c->_softhangup == AST_SOFTHANGUP_ASYNCGOTO) {
+                                       c->_softhangup = 0;
+                               } else {
+                                       if (!digit)
+                                               /* No entry */
+                                               break;
+                                       if (digit < 0)
+                                               /* Error, maybe a  hangup */
+                                               goto out;
+                                       exten[pos++] = digit;
+                                       waittime = c->pbx->dtimeout;
+                               }
                        }
                        if (ast_exists_extension(c, c->context, exten, 1, c->callerid)) {
                                /* Prepare the next cycle */
@@ -955,6 +1206,7 @@ int ast_pbx_start(struct ast_channel *c)
                ast_log(LOG_WARNING, "Asked to start thread on NULL channel?\n");
                return -1;
        }
+          
        /* Start a new thread, and get something handling this channel. */
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
@@ -1864,7 +2116,7 @@ int ast_context_add_include(char *context, char *include, char *registrar)
 do { \
        c = info; \
        while(*c && (*c != '|')) c++; \
-       if (*c) *c = '\0'; else c = NULL; \
+       if (*c) { *c = '\0'; c++; } else c = NULL; \
 } while(0)
 
 static void get_timerange(struct ast_include *i, char *times)
@@ -1935,7 +2187,7 @@ static unsigned int get_dow(char *dow)
        char *c;
        /* The following line is coincidence, really! */
        int s, e, x;
-       unsigned mask;
+       unsigned int mask;
        /* Check for all days */
        if (!strlen(dow) || !strcmp(dow, "*"))
                return (1 << 7) - 1;
@@ -1944,7 +2196,8 @@ static unsigned int get_dow(char *dow)
        if (c) {
                *c = '\0';
                c++;
-       }
+       } else
+               c = NULL;
        /* Find the start */
        s = 0;
        while((s < 7) && strcasecmp(dow, days[s])) s++;
@@ -1954,7 +2207,7 @@ static unsigned int get_dow(char *dow)
        }
        if (c) {
                e = 0;
-               while((e < 7) && strcasecmp(dow, days[e])) e++;
+               while((e < 7) && strcasecmp(c, days[e])) e++;
                if (e >= 7) {
                        ast_log(LOG_WARNING, "Invalid day '%s', assuming none\n", c);
                        return 0;
@@ -1979,6 +2232,7 @@ static unsigned int get_day(char *day)
        /* Check for all days */
        if (!strlen(day) || !strcmp(day, "*")) {
                mask = (1 << 30)  + ((1 << 30) - 1);
+               return mask;
        }
        /* Get start and ending days */
        c = strchr(day, '-');
@@ -2085,7 +2339,6 @@ static void build_timing(struct ast_include *i, char *info)
        i->dowmask = (1 << 7) - 1;
        /* Avoid using strtok */
        FIND_NEXT;
-
        /* Info has the time range, start with that */
        get_timerange(i, info);
        info = c;
@@ -2440,6 +2693,86 @@ int ast_add_extension(char *context, int replace, char *extension, int priority,
        return -1;
 }
 
+int ast_async_goto(struct ast_channel *chan, char *context, char *exten, int priority, int needlock)
+{
+       int res = 0;
+       if (needlock)
+               ast_pthread_mutex_lock(&chan->lock);
+       if (chan->pbx) {
+               /* This channel is currently in the PBX */
+               if (context && strlen(context))
+                       strncpy(chan->context, context, sizeof(chan->context) - 1);
+               if (exten && strlen(exten))
+                       strncpy(chan->exten, exten, sizeof(chan->context) - 1);
+               if (priority)
+                       chan->priority = priority - 1;
+               ast_softhangup_nolock(chan, AST_SOFTHANGUP_ASYNCGOTO);
+               if (needlock)
+                       ast_pthread_mutex_unlock(&chan->lock);
+       } else {
+               /* In order to do it when the channel doesn't really exist within
+                  the PBX, we have to make a new channel, masquerade, and start the PBX
+                  at the new location */
+               struct ast_channel *tmpchan;
+               struct ast_frame *f;
+               tmpchan = ast_channel_alloc(0);
+               if (tmpchan) {
+                       snprintf(tmpchan->name, sizeof(tmpchan->name), "AsyncGoto/%s", chan->name);
+                       /* Make formats okay */
+                       tmpchan->readformat = chan->readformat;
+                       tmpchan->writeformat = chan->writeformat;
+                       /* Setup proper location */
+                       if (context && strlen(context))
+                               strncpy(tmpchan->context, context, sizeof(tmpchan->context) - 1);
+                       else
+                               strncpy(tmpchan->context, chan->context, sizeof(tmpchan->context) - 1);
+                       if (exten && strlen(exten))
+                               strncpy(tmpchan->exten, exten, sizeof(tmpchan->exten) - 1);
+                       else
+                               strncpy(tmpchan->exten, chan->exten, sizeof(tmpchan->exten) - 1);
+                       if (priority)
+                               tmpchan->priority = priority;
+                       else
+                               tmpchan->priority = chan->priority;
+                       if (needlock)
+                               ast_pthread_mutex_unlock(&chan->lock);
+                       
+                       /* Masquerade into temp channel */
+                       ast_channel_masquerade(tmpchan, chan);
+                       
+                       /* Make the masquerade happen by reading a frame from the tmp channel */
+                       f = ast_read(tmpchan);
+                       if (f)
+                               ast_frfree(f);
+                       /* Start the PBX going on our stolen channel */
+                       if (ast_pbx_start(tmpchan)) {
+                               ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmpchan->name);
+                               ast_hangup(tmpchan);
+                               res = -1;
+                       }
+               } else {
+                       res = -1;
+                       if (needlock)
+                               ast_pthread_mutex_unlock(&chan->lock);
+               }
+       }
+       return res;
+}
+
+int ast_async_goto_by_name(char *channame, char *context, char *exten, int priority)
+{
+       struct ast_channel *chan;
+       chan = ast_channel_walk(NULL);
+       while(chan) {
+               if (!strcasecmp(channame, chan->name))
+                       break;
+               chan = ast_channel_walk(chan);
+       }
+       if (chan)
+               return ast_async_goto(chan, context, exten, priority, 1);
+       return -1;
+}
+
 static void ext_strncpy(char *dst, char *src, int len)
 {
        int count=0;
@@ -2640,6 +2973,112 @@ int ast_add_extension2(struct ast_context *con,
        return 0;       
 }
 
+struct async_stat {
+       pthread_t p;
+       struct ast_channel *chan;
+       char context[AST_MAX_EXTENSION];
+       char exten[AST_MAX_EXTENSION];
+       int priority;
+       int timeout;
+       char app[AST_MAX_EXTENSION];
+       char data[1024];
+};
+
+static void *async_wait(void *data) 
+{
+       struct async_stat *as = data;
+       struct ast_channel *chan = as->chan;
+       int timeout = as->timeout;
+       int res;
+       struct ast_frame *f;
+       
+       while(timeout && (chan->_state != AST_STATE_UP)) {
+               res = ast_waitfor(chan, timeout);
+               if (res < 1) 
+                       break;
+               if (timeout > -1)
+                       timeout = res;
+               f = ast_read(chan);
+               if (!f)
+                       break;
+               if (f->frametype == AST_FRAME_CONTROL) {
+                       if ((f->subclass == AST_CONTROL_BUSY)  ||
+                               (f->subclass == AST_CONTROL_CONGESTION) )
+                                       break;
+               }
+               ast_frfree(f);
+       }
+       if (chan->_state == AST_STATE_UP) {
+               if (strlen(as->context))
+                       strncpy(chan->context, as->context, sizeof(chan->context) - 1);
+               if (strlen(as->exten))
+                       strncpy(chan->exten, as->exten, sizeof(chan->exten) - 1);
+               if (as->priority > 0)
+                       chan->priority = as->priority;
+               /* Run the PBX */
+               if (ast_pbx_run(chan)) {
+                       ast_log(LOG_WARNING, "Failed to start PBX on %s\n", chan->name);
+               } else {
+                       /* PBX will have taken care of this */
+                       chan = NULL;
+               }
+                       
+       }
+       free(as);
+       if (chan)
+               ast_hangup(chan);
+       return NULL;
+}
+
+int ast_pbx_outgoing_exten(char *type, int format, void *data, int timeout, char *context, char *exten, int priority, int *reason, int sync)
+{
+       struct ast_channel *chan;
+       struct async_stat *as;
+       int res = -1;
+       if (sync) {
+               chan = ast_request_and_dial(type, format, data, timeout, reason);
+               if (chan) {
+                       if (chan->_state == AST_STATE_UP) {
+                               res = 0;
+                               if (option_verbose > 3)
+                                       ast_verbose(VERBOSE_PREFIX_4 "Channel %s was answered.\n", chan->name);
+                               if (ast_pbx_start(chan)) {
+                                       ast_log(LOG_WARNING, "Unable to start PBX on %s\n", chan->name);
+                                       ast_hangup(chan);
+                                       res = -1;
+                               } 
+                       } else {
+                               if (option_verbose > 3)
+                                       ast_verbose(VERBOSE_PREFIX_4 "Channel %s was never answered.\n", chan->name);
+                               ast_hangup(chan);
+                       }
+               }
+       } else {
+               as = malloc(sizeof(struct async_stat));
+               if (!as)
+                       return -1;
+               memset(as, 0, sizeof(struct async_stat));
+               chan = ast_request_and_dial(type, format, data, 0, reason);
+               if (!chan) {
+                       free(as);
+                       return -1;
+               }
+               as->chan = chan;
+               strncpy(as->context, context, sizeof(as->context) - 1);
+               strncpy(as->exten,  exten, sizeof(as->exten) - 1);
+               as->priority = priority;
+               as->timeout = timeout;
+               if (pthread_create(&as->p, NULL, async_wait, as)) {
+                       ast_log(LOG_WARNING, "Failed to start async wait\n");
+                       free(as);
+                       ast_hangup(chan);
+                       return -1;
+               }
+               res = 0;
+       }
+       return res;
+}
+
 void ast_context_destroy(struct ast_context *con, char *registrar)
 {
        struct ast_context *tmp, *tmpl=NULL;
@@ -2788,7 +3227,7 @@ static int pbx_builtin_background(struct ast_channel *chan, void *data)
 {
        int res;
        /* Answer if need be */
-       if (chan->state != AST_STATE_UP)
+       if (chan->_state != AST_STATE_UP)
                if (ast_answer(chan))
                        return -1;
        /* Stop anything playing */
@@ -2867,6 +3306,96 @@ static int pbx_builtin_goto(struct ast_channel *chan, void *data)
        return 0;
 }
 
+void pbx_builtin_setvar_helper(struct ast_channel *chan, char *name, char *value) {
+       struct ast_var_t *newvariable;
+        struct varshead *headp;
+        
+        headp=&chan->varshead;
+                
+       AST_LIST_TRAVERSE (headp,newvariable,entries) {
+               if (strncasecmp(ast_var_name(newvariable),name,strlen(name))==0) {
+                       /* there is already such a variable, delete it */
+                       AST_LIST_REMOVE(headp,newvariable,ast_var_t,entries);
+                       ast_var_delete(newvariable);
+                       break;
+               }
+       } 
+       
+       newvariable=ast_var_assign(name,value); 
+       AST_LIST_INSERT_HEAD(headp,newvariable,entries);
+       
+        return;
+}
+
+static int pbx_builtin_setvar(struct ast_channel *chan, void *data)
+{
+       char *name;
+       char *value;
+       char *ptrptr;
+                
+       if (!data || !strlen(data)) {
+               ast_log(LOG_WARNING, "Ignoring, since there is no variable to set\n");
+               return 0;
+       }
+       
+       name=strtok_r(data,"=",&ptrptr);
+       value=strtok_r(NULL,"\0",&ptrptr); 
+       
+       pbx_builtin_setvar_helper(chan,name,value);
+                       
+        return(0);
+}
+
+static int pbx_checkcondition(char *condition) {
+       char *s;
+       int ret;
+       
+       s=strdup(condition);
+       
+       ret=1;
+       
+       if ((strcasecmp(s,"0")) || (strlen(s)==0)) {
+               ret=0;
+       }
+       
+       free(s);
+       return(ret);
+}
+
+
+static int pbx_builtin_gotoif(struct ast_channel *chan, void *data)
+{
+       char *condition,*branch1,*branch2,*branch;
+       char *s;
+       char *ptrptr;
+       int rc;
+
+       if (!data || !strlen(data)) {
+               ast_log(LOG_WARNING, "Ignoring, since there is no variable to set\n");
+               return 0;
+       }
+       
+       s=strdup(data);
+       condition=strtok_r(s,"?",&ptrptr);
+       branch1=strtok_r(NULL,":",&ptrptr);
+       branch2=strtok_r(NULL,"",&ptrptr);
+       
+       if (pbx_checkcondition(condition)) {
+               branch=branch2;
+       } else {
+               branch=branch1;
+       }
+       
+       if ((branch==NULL) || (strlen(branch)==0)) {
+               ast_log(LOG_WARNING, "Not taking any branch\n");
+               return(0);
+       }
+       
+       rc=pbx_builtin_goto(chan,branch);
+       free(s);
+       return(rc);
+}           
+       
 int load_pbx(void)
 {
        int x;