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