remove the uses of the deprecated STANDARD_LOCAL_USER
[asterisk/asterisk.git] / apps / app_festival.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2002, Christos Ricudis
5  *
6  * Christos Ricudis <ricudis@itc.auth.gr>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Connect to festival
22  *
23  * \author Christos Ricudis <ricudis@itc.auth.gr>
24  * 
25  * \ingroup applications
26  */
27
28 #include <sys/types.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <netdb.h>
36 #include <netinet/in.h>
37 #include <arpa/inet.h>
38 #include <stdio.h>
39 #include <signal.h>
40 #include <stdlib.h>
41 #include <unistd.h>
42 #include <fcntl.h>
43 #include <ctype.h>
44
45 #include "asterisk.h"
46
47 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
48
49 #include "asterisk/file.h"
50 #include "asterisk/logger.h"
51 #include "asterisk/channel.h"
52 #include "asterisk/pbx.h"
53 #include "asterisk/module.h"
54 #include "asterisk/md5.h"
55 #include "asterisk/config.h"
56 #include "asterisk/utils.h"
57 #include "asterisk/lock.h"
58
59 #define FESTIVAL_CONFIG "festival.conf"
60
61 static char *tdesc = "Simple Festival Interface";
62
63 static char *app = "Festival";
64
65 static char *synopsis = "Say text to the user";
66
67 static char *descrip = 
68 "  Festival(text[|intkeys]):  Connect to Festival, send the argument, get back the waveform,"
69 "play it to the user, allowing any given interrupt keys to immediately terminate and return\n"
70 "the value, or 'any' to allow any number back (useful in dialplan)\n";
71
72 LOCAL_USER_DECL;
73
74 static char *socket_receive_file_to_buff(int fd,int *size)
75 {
76     /* Receive file (probably a waveform file) from socket using   */
77     /* Festival key stuff technique, but long winded I know, sorry */
78     /* but will receive any file without closeing the stream or    */
79     /* using OOB data                                              */
80     static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
81     char *buff;
82     int bufflen;
83     int n,k,i;
84     char c;
85
86     bufflen = 1024;
87     if (!(buff = ast_malloc(bufflen)))
88     {
89         /* TODO: Handle memory allocation failure */
90     }
91     *size=0;
92
93     for (k=0; file_stuff_key[k] != '\0';)
94     {
95         n = read(fd,&c,1);
96         if (n==0) break;  /* hit stream eof before end of file */
97         if ((*size)+k+1 >= bufflen)
98         {   /* +1 so you can add a NULL if you want */
99             bufflen += bufflen/4;
100             if (!(buff = ast_realloc(buff, bufflen)))
101             {
102                 /* TODO: Handle memory allocation failure */
103             }
104         }
105         if (file_stuff_key[k] == c)
106             k++;
107         else if ((c == 'X') && (file_stuff_key[k+1] == '\0'))
108         {   /* It looked like the key but wasn't */
109             for (i=0; i < k; i++,(*size)++)
110                 buff[*size] = file_stuff_key[i];
111             k=0;
112             /* omit the stuffed 'X' */
113         }
114         else
115         {
116             for (i=0; i < k; i++,(*size)++)
117                 buff[*size] = file_stuff_key[i];
118             k=0;
119             buff[*size] = c;
120             (*size)++;
121         }
122
123     }
124
125     return buff;
126 }
127
128 static int send_waveform_to_fd(char *waveform, int length, int fd) {
129
130         int res;
131         int x;
132 #ifdef __PPC__ 
133         char c;
134 #endif
135
136         res = fork();
137         if (res < 0)
138                 ast_log(LOG_WARNING, "Fork failed\n");
139         if (res)
140                 return res;
141         for (x=0;x<256;x++) {
142                 if (x != fd)
143                         close(x);
144         }
145 /*IAS */
146 #ifdef __PPC__  
147         for( x=0; x<length; x+=2)
148         {
149                 c = *(waveform+x+1);
150                 *(waveform+x+1)=*(waveform+x);
151                 *(waveform+x)=c;
152         }
153 #endif
154         
155         write(fd,waveform,length);
156         close(fd);
157         exit(0);
158 }
159
160
161 static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys) {
162         int res=0;
163         int fds[2];
164         int ms = -1;
165         int pid = -1;
166         int needed = 0;
167         int owriteformat;
168         struct ast_frame *f;
169         struct myframe {
170                 struct ast_frame f;
171                 char offset[AST_FRIENDLY_OFFSET];
172                 char frdata[2048];
173         } myf;
174         
175         if (pipe(fds)) {
176                  ast_log(LOG_WARNING, "Unable to create pipe\n");
177                 return -1;
178         }
179                                                         
180         /* Answer if it's not already going */
181         if (chan->_state != AST_STATE_UP)
182                 ast_answer(chan);
183         ast_stopstream(chan);
184         ast_indicate(chan, -1);
185         
186         owriteformat = chan->writeformat;
187         res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
188         if (res < 0) {
189                 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
190                 return -1;
191         }
192         
193         res=send_waveform_to_fd(waveform,length,fds[1]);
194         if (res >= 0) {
195                 pid = res;
196                 /* Order is important -- there's almost always going to be mp3...  we want to prioritize the
197                    user */
198                 for (;;) {
199                         ms = 1000;
200                         res = ast_waitfor(chan, ms);
201                         if (res < 1) {
202                                 res = -1;
203                                 break;
204                         }
205                         f = ast_read(chan);
206                         if (!f) {
207                                 ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
208                                 res = -1;
209                                 break;
210                         }
211                         if (f->frametype == AST_FRAME_DTMF) {
212                                 ast_log(LOG_DEBUG, "User pressed a key\n");
213                                 if (intkeys && strchr(intkeys, f->subclass)) {
214                                         res = f->subclass;
215                                         ast_frfree(f);
216                                         break;
217                                 }
218                         }
219                         if (f->frametype == AST_FRAME_VOICE) {
220                                 /* Treat as a generator */
221                                 needed = f->samples * 2;
222                                 if (needed > sizeof(myf.frdata)) {
223                                         ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
224                                                 (int)sizeof(myf.frdata) / 2, needed/2);
225                                         needed = sizeof(myf.frdata);
226                                 }
227                                 res = read(fds[0], myf.frdata, needed);
228                                 if (res > 0) {
229                                         myf.f.frametype = AST_FRAME_VOICE;
230                                         myf.f.subclass = AST_FORMAT_SLINEAR;
231                                         myf.f.datalen = res;
232                                         myf.f.samples = res / 2;
233                                         myf.f.mallocd = 0;
234                                         myf.f.offset = AST_FRIENDLY_OFFSET;
235                                         myf.f.src = __PRETTY_FUNCTION__;
236                                         myf.f.data = myf.frdata;
237                                         if (ast_write(chan, &myf.f) < 0) {
238                                                 res = -1;
239                                                 break;
240                                         }
241                                         if (res < needed) { /* last frame */
242                                                 ast_log(LOG_DEBUG, "Last frame\n");
243                                                 res=0;
244                                                 break;
245                                         }
246                                 } else {
247                                         ast_log(LOG_DEBUG, "No more waveform\n");
248                                         res = 0;
249                                 }
250                         }
251                         ast_frfree(f);
252                 }
253         }
254         close(fds[0]);
255         close(fds[1]);
256
257 /*      if (pid > -1) */
258 /*              kill(pid, SIGKILL); */
259         if (!res && owriteformat)
260                 ast_set_write_format(chan, owriteformat);
261         return res;
262 }
263
264 #define MAXLEN 180
265 #define MAXFESTLEN 2048
266
267
268
269
270 static int festival_exec(struct ast_channel *chan, void *vdata)
271 {
272         int usecache;
273         int res=0;
274         struct localuser *u;
275         struct sockaddr_in serv_addr;
276         struct hostent *serverhost;
277         struct ast_hostent ahp;
278         int fd;
279         FILE *fs;
280         char *host;
281         char *cachedir;
282         char *temp;
283         char *festivalcommand;
284         int port=1314;
285         int n;
286         char ack[4];
287         char *waveform;
288         int filesize;
289         int wave;
290         char bigstring[MAXFESTLEN];
291         int i;
292         struct MD5Context md5ctx;
293         unsigned char MD5Res[16];
294         char MD5Hex[33] = "";
295         char koko[4] = "";
296         char cachefile[MAXFESTLEN]="";
297         int readcache=0;
298         int writecache=0;
299         int strln;
300         int fdesc = -1;
301         char buffer[16384];
302         int seekpos = 0;        
303         char *data;     
304         char *intstr;
305         struct ast_config *cfg;
306
307         if (ast_strlen_zero(vdata)) {
308                 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
309                 return -1;
310         }
311
312         LOCAL_USER_ADD(u);
313
314         cfg = ast_config_load(FESTIVAL_CONFIG);
315         if (!cfg) {
316                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
317                 LOCAL_USER_REMOVE(u);
318                 return -1;
319         }
320         if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
321                 host = "localhost";
322         }
323         if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
324                 port = 1314;
325         } else {
326                 port = atoi(temp);
327         }
328         if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
329                 usecache=0;
330         } else {
331                 usecache = ast_true(temp);
332         }
333         if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
334                 cachedir = "/tmp/";
335         }
336         if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
337                 festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
338         }
339         
340         if (!(data = ast_strdupa(vdata))) {
341                 ast_config_destroy(cfg);
342                 LOCAL_USER_REMOVE(u);
343                 return -1;
344         }
345
346         intstr = strchr(data, '|');
347         if (intstr) {   
348                 *intstr = '\0';
349                 intstr++;
350                 if (!strcasecmp(intstr, "any"))
351                         intstr = AST_DIGIT_ANY;
352         }
353         
354         ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data);
355         /* Connect to local festival server */
356         
357         fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
358
359         if (fd < 0) {
360                 ast_log(LOG_WARNING,"festival_client: can't get socket\n");
361                 ast_config_destroy(cfg);
362                 LOCAL_USER_REMOVE(u);
363                 return -1;
364         }
365         memset(&serv_addr, 0, sizeof(serv_addr));
366         if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
367                 /* its a name rather than an ipnum */
368                 serverhost = ast_gethostbyname(host, &ahp);
369                 if (serverhost == (struct hostent *)0) {
370                         ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
371                         ast_config_destroy(cfg);
372                         LOCAL_USER_REMOVE(u);
373                         return -1;
374                 }
375                 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
376         }
377         serv_addr.sin_family = AF_INET;
378         serv_addr.sin_port = htons(port);
379
380         if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
381                 ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
382                 ast_config_destroy(cfg);
383                 LOCAL_USER_REMOVE(u);
384                 return -1;
385         }
386         
387         /* Compute MD5 sum of string */
388         MD5Init(&md5ctx);
389         MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
390         MD5Final(MD5Res,&md5ctx);
391                 MD5Hex[0] = '\0';
392         
393         /* Convert to HEX and look if there is any matching file in the cache 
394                 directory */
395         for (i=0;i<16;i++) {
396                 snprintf(koko, sizeof(koko), "%X",MD5Res[i]);
397                 strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
398         }
399         readcache=0;
400         writecache=0;
401         if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
402                 snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
403                 fdesc=open(cachefile,O_RDWR);
404                 if (fdesc==-1) {
405                         fdesc=open(cachefile,O_CREAT|O_RDWR,0777);
406                         if (fdesc!=-1) {
407                                 writecache=1;
408                                 strln=strlen((char *)data);
409                                 ast_log(LOG_DEBUG,"line length : %d\n",strln);
410                                 write(fdesc,&strln,sizeof(int));
411                                 write(fdesc,data,strln);
412                                 seekpos=lseek(fdesc,0,SEEK_CUR);
413                                 ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
414                         }
415                 } else {
416                         read(fdesc,&strln,sizeof(int));
417                         ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data));
418                         if (strlen((char *)data)==strln) {
419                                 ast_log(LOG_DEBUG,"Size OK\n");
420                                 read(fdesc,&bigstring,strln);
421                                 bigstring[strln] = 0;
422                                 if (strcmp(bigstring,data)==0) { 
423                                         readcache=1;
424                                 } else {
425                                         ast_log(LOG_WARNING,"Strings do not match\n");
426                                 }
427                         } else {
428                                 ast_log(LOG_WARNING,"Size mismatch\n");
429                         }
430                 }
431         }
432                         
433         if (readcache==1) {
434                 close(fd);
435                 fd=fdesc;
436                 ast_log(LOG_DEBUG,"Reading from cache...\n");
437         } else {
438                 ast_log(LOG_DEBUG,"Passing text to festival...\n");
439                 fs=fdopen(dup(fd),"wb");
440                 fprintf(fs,festivalcommand,(char *)data);
441                 fflush(fs);
442                 fclose(fs);
443         }
444         
445         /* Write to cache and then pass it down */
446         if (writecache==1) {
447                 ast_log(LOG_DEBUG,"Writing result to cache...\n");
448                 while ((strln=read(fd,buffer,16384))!=0) {
449                         write(fdesc,buffer,strln);
450                 }
451                 close(fd);
452                 close(fdesc);
453                 fd=open(cachefile,O_RDWR);
454                 lseek(fd,seekpos,SEEK_SET);
455         }
456         
457         ast_log(LOG_DEBUG,"Passing data to channel...\n");
458         
459         /* Read back info from server */
460         /* This assumes only one waveform will come back, also LP is unlikely */
461         wave = 0;
462         do {
463                int read_data;
464                 for (n=0; n < 3; )
465                {
466                        read_data = read(fd,ack+n,3-n);
467                        /* this avoids falling in infinite loop
468                         * in case that festival server goes down
469                         * */
470                        if ( read_data == -1 )
471                        {
472                                ast_log(LOG_WARNING,"Unable to read from cache/festival fd");
473                                return -1;
474                        }
475                        n += read_data;
476                }
477                 ack[3] = '\0';
478                 if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
479                         ast_log(LOG_DEBUG,"Festival WV command\n");
480                         waveform = socket_receive_file_to_buff(fd,&filesize);
481                         res = send_waveform_to_channel(chan,waveform,filesize, intstr);
482                         free(waveform);
483                         break;
484                 }
485                 else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
486                         ast_log(LOG_DEBUG,"Festival LP command\n");
487                         waveform = socket_receive_file_to_buff(fd,&filesize);
488                         waveform[filesize]='\0';
489                         ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
490                         free(waveform);
491                 } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
492                         ast_log(LOG_WARNING,"Festival returned ER\n");
493                         res=-1;
494                         break;
495                 }
496         } while (strcmp(ack,"OK\n") != 0);
497         close(fd);
498         ast_config_destroy(cfg);
499         LOCAL_USER_REMOVE(u);
500         return res;
501
502 }
503
504 int unload_module(void)
505 {
506         int res;
507
508         res = ast_unregister_application(app);
509
510         STANDARD_HANGUP_LOCALUSERS;
511
512         return res;
513 }
514
515 int load_module(void)
516 {
517         
518         return ast_register_application(app, festival_exec, synopsis, descrip);
519 }
520
521 char *description(void)
522 {
523         return tdesc;
524 }
525
526 int usecount(void)
527 {
528         int res;
529         STANDARD_USECOUNT(res);
530         return res;
531 }
532
533 char *key()
534 {
535         return ASTERISK_GPL_KEY;
536 }