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