Fix permissions for festival app (bug #2334)
[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         if (!vdata || ast_strlen_zero(vdata)) {
309                 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
310                 return -1;
311         }
312         strncpy(data, vdata, sizeof(data) - 1);
313         if ((intstr = strchr(data, '|'))) {
314                 *intstr = '\0';
315                 intstr++;
316                 if (!strcasecmp(intstr, "any"))
317                         intstr = AST_DIGIT_ANY;
318         }
319         LOCAL_USER_ADD(u);
320         ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data);
321         /* Connect to local festival server */
322         
323         fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
324
325         if (fd < 0) {
326                 ast_log(LOG_WARNING,"festival_client: can't get socket\n");
327                 return -1;
328         }
329         memset(&serv_addr, 0, sizeof(serv_addr));
330         if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
331                 /* its a name rather than an ipnum */
332                 serverhost = ast_gethostbyname(host, &ahp);
333                 if (serverhost == (struct hostent *)0) {
334                         ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
335                         return -1;
336                 }
337                 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
338         }
339         serv_addr.sin_family = AF_INET;
340         serv_addr.sin_port = htons(port);
341
342         if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
343                 ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
344                 return -1;
345         }
346         
347         /* Compute MD5 sum of string */
348         MD5Init(&md5ctx);
349         MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
350         MD5Final(MD5Res,&md5ctx);
351                 MD5Hex[0] = '\0';
352         
353         /* Convert to HEX and look if there is any matching file in the cache 
354                 directory */
355         for (i=0;i<16;i++) {
356                 snprintf(koko, sizeof(koko), "%X",MD5Res[i]);
357                 strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
358         }
359         readcache=0;
360         writecache=0;
361         if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
362                 snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
363                 fdesc=open(cachefile,O_RDWR);
364                 if (fdesc==-1) {
365                         fdesc=open(cachefile,O_CREAT|O_RDWR,S_IRWXU);
366                         if (fdesc!=-1) {
367                                 writecache=1;
368                                 strln=strlen((char *)data);
369                                 ast_log(LOG_DEBUG,"line length : %d\n",strln);
370                                 write(fdesc,&strln,sizeof(int));
371                                 write(fdesc,data,strln);
372                                 seekpos=lseek(fdesc,0,SEEK_CUR);
373                                 ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
374                         }
375                 } else {
376                         read(fdesc,&strln,sizeof(int));
377                         ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data));
378                         if (strlen((char *)data)==strln) {
379                                 ast_log(LOG_DEBUG,"Size OK\n");
380                                 read(fdesc,&bigstring,strln);
381                                 bigstring[strln] = 0;
382                                 if (strcmp(bigstring,data)==0) { 
383                                         readcache=1;
384                                 } else {
385                                         ast_log(LOG_WARNING,"Strings do not match\n");
386                                 }
387                         } else {
388                                 ast_log(LOG_WARNING,"Size mismatch\n");
389                         }
390                 }
391         }
392                         
393         if (readcache==1) {
394                 close(fd);
395                 fd=fdesc;
396                 ast_log(LOG_DEBUG,"Reading from cache...\n");
397         } else {
398                 ast_log(LOG_DEBUG,"Passing text to festival...\n");
399                 fs=fdopen(dup(fd),"wb");
400                 fprintf(fs,festivalcommand,(char *)data);
401                 fflush(fs);
402                 fclose(fs);
403         }
404         
405         /* Write to cache and then pass it down */
406         if (writecache==1) {
407                 ast_log(LOG_DEBUG,"Writing result to cache...\n");
408                 while ((strln=read(fd,buffer,16384))!=0) {
409                         write(fdesc,buffer,strln);
410                 }
411                 close(fd);
412                 close(fdesc);
413                 fd=open(cachefile,O_RDWR);
414                 lseek(fd,seekpos,SEEK_SET);
415         }
416         
417         ast_log(LOG_DEBUG,"Passing data to channel...\n");
418         
419         /* Read back info from server */
420         /* This assumes only one waveform will come back, also LP is unlikely */
421         wave = 0;
422         do {
423                 for (n=0; n < 3; )
424                         n += read(fd,ack+n,3-n);
425                 ack[3] = '\0';
426                 if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
427                         ast_log(LOG_DEBUG,"Festival WV command\n");
428                         waveform = socket_receive_file_to_buff(fd,&filesize);
429                         res = send_waveform_to_channel(chan,waveform,filesize, intstr);
430                         free(waveform);
431                         break;
432                 }
433                 else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
434                         ast_log(LOG_DEBUG,"Festival LP command\n");
435                         waveform = socket_receive_file_to_buff(fd,&filesize);
436                         waveform[filesize]='\0';
437                         ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
438                         free(waveform);
439                 } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
440                         ast_log(LOG_WARNING,"Festival returned ER\n");
441                         res=-1;
442                         break;
443                 }
444         } while (strcmp(ack,"OK\n") != 0);
445         close(fd);
446         LOCAL_USER_REMOVE(u);                                                                                
447         return res;
448
449 }
450
451 int unload_module(void)
452 {
453         STANDARD_HANGUP_LOCALUSERS;
454         return ast_unregister_application(app);
455 }
456
457 int load_module(void)
458 {
459         
460         return ast_register_application(app, festival_exec, synopsis, descrip);
461 }
462
463 char *description(void)
464 {
465         return tdesc;
466 }
467
468 int usecount(void)
469 {
470         int res;
471         STANDARD_USECOUNT(res);
472         return res;
473 }
474
475 char *key()
476 {
477         return ASTERISK_GPL_KEY;
478 }