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