gethostbyname isn't reentrant, who knew...
[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         /* Answer if it's not already going */
159         if (chan->_state != AST_STATE_UP)
160                 ast_answer(chan);
161         ast_stopstream(chan);
162
163         owriteformat = chan->writeformat;
164         res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
165         if (res < 0) {
166                 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
167                 return -1;
168         }
169         
170         res=send_waveform_to_fd(waveform,length,fds[1]);
171         if (res >= 0) {
172                 pid = res;
173                 /* Order is important -- there's almost always going to be mp3...  we want to prioritize the
174                    user */
175                 for (;;) {
176                         ms = 1000;
177                         res = ast_waitfor(chan, ms);
178                         if (res < 1) {
179                                 res = -1;
180                                 break;
181                         }
182                         f = ast_read(chan);
183                         if (!f) {
184                                 ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
185                                 res = -1;
186                                 break;
187                         }
188                         if (f->frametype == AST_FRAME_DTMF) {
189                                 ast_log(LOG_DEBUG, "User pressed a key\n");
190                                 if (intkeys && strchr(intkeys, f->subclass)) {
191                                         res = f->subclass;
192                                         ast_frfree(f);
193                                         break;
194                                 }
195                         }
196                         if (f->frametype == AST_FRAME_VOICE) {
197                                 /* Treat as a generator */
198                                 needed = f->samples * 2;
199                                 if (needed > sizeof(myf.frdata)) {
200                                         ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
201                                                 sizeof(myf.frdata) / 2, needed/2);
202                                         needed = sizeof(myf.frdata);
203                                 }
204                                 res = read(fds[0], myf.frdata, needed);
205                                 if (res > 0) {
206                                         myf.f.frametype = AST_FRAME_VOICE;
207                                         myf.f.subclass = AST_FORMAT_SLINEAR;
208                                         myf.f.datalen = res;
209                                         myf.f.samples = res / 2;
210                                         myf.f.mallocd = 0;
211                                         myf.f.offset = AST_FRIENDLY_OFFSET;
212                                         myf.f.src = __PRETTY_FUNCTION__;
213                                         myf.f.data = myf.frdata;
214                                         if (ast_write(chan, &myf.f) < 0) {
215                                                 res = -1;
216                                                 break;
217                                         }
218                                         if (res < needed) { // last frame
219                                                 ast_log(LOG_DEBUG, "Last frame\n");
220                                                 res=0;
221                                                 break;
222                                         }
223                                 } else {
224                                         ast_log(LOG_DEBUG, "No more waveform\n");
225                                         res = 0;
226                                 }
227                         }
228                         ast_frfree(f);
229                 }
230         }
231         close(fds[0]);
232         close(fds[1]);
233 //      if (pid > -1)
234 //              kill(pid, SIGKILL);
235         if (!res && owriteformat)
236                 ast_set_write_format(chan, owriteformat);
237         return res;
238 }
239
240 #define MAXLEN 180
241 #define MAXFESTLEN 2048
242
243
244
245
246 static int festival_exec(struct ast_channel *chan, void *vdata)
247 {
248         int usecache;
249         int res=0;
250         struct localuser *u;
251         struct sockaddr_in serv_addr;
252         struct hostent *serverhost;
253         struct ast_hostent ahp;
254         int fd;
255         FILE *fs;
256         char *host;
257         char *cachedir;
258         char *temp;
259         char *festivalcommand;
260         int port=1314;
261         int n;
262         char ack[4];
263         char *waveform;
264         int filesize;
265         int wave;
266         char bigstring[MAXFESTLEN];
267         int i;
268         struct MD5Context md5ctx;
269         unsigned char MD5Res[16];
270         char MD5Hex[33];
271         char koko[4];
272         char cachefile[MAXFESTLEN];
273         int readcache=0;
274         int writecache=0;
275         int strln;
276         int fdesc = -1;
277         char buffer[16384];
278         int seekpos = 0;        
279         char data[256] = "";
280         char *intstr;
281         
282         struct ast_config *cfg;
283         cfg = ast_load(FESTIVAL_CONFIG);
284         if (!cfg) {
285                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
286                 return -1;
287         }
288         if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
289                 host = "localhost";
290         }
291         if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
292                 port = 1314;
293         } else {
294                 port = atoi(temp);
295         }
296         if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
297                 usecache=0;
298         } else {
299                 usecache = ast_true(temp);
300         }
301         if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
302                 cachedir = "/tmp/";
303         }
304         if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
305                 festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
306         }
307         
308                 
309
310         if (!vdata || !strlen(vdata)) {
311                 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
312                 return -1;
313         }
314         strncpy(data, vdata, sizeof(data) - 1);
315         if ((intstr = strchr(data, '|'))) {
316                 *intstr = '\0';
317                 intstr++;
318                 if (!strcasecmp(intstr, "any"))
319                         intstr = AST_DIGIT_ANY;
320         }
321         LOCAL_USER_ADD(u);
322         ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data);
323         /* Connect to local festival server */
324         
325         fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
326
327         if (fd < 0) {
328                 ast_log(LOG_WARNING,"festival_client: can't get socket\n");
329                 return -1;
330         }
331         memset(&serv_addr, 0, sizeof(serv_addr));
332         if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
333                 /* its a name rather than an ipnum */
334                 serverhost = ast_gethostbyname(host, &ahp);
335                 if (serverhost == (struct hostent *)0) {
336                         ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
337                         return -1;
338                 }
339                 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
340         }
341         serv_addr.sin_family = AF_INET;
342         serv_addr.sin_port = htons(port);
343
344         if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
345                 ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
346                 return -1;
347         }
348         
349         /* Compute MD5 sum of string */
350         MD5Init(&md5ctx);
351         MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
352         MD5Final(MD5Res,&md5ctx);
353         strcpy(MD5Hex,"");
354         
355         /* Convert to HEX and look if there is any matching file in the cache 
356                 directory */
357         for (i=0;i<16;i++) {
358                 sprintf(koko,"%X",MD5Res[i]);
359                 strcat(MD5Hex,koko);
360         }
361         readcache=0;
362         writecache=0;
363         if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
364                 sprintf(cachefile,"%s/%s",cachedir,MD5Hex);
365                 fdesc=open(cachefile,O_RDWR);
366                 if (fdesc==-1) {
367                         fdesc=open(cachefile,O_CREAT|O_RDWR,0);
368                         if (fdesc!=-1) {
369                                 writecache=1;
370                                 strln=strlen((char *)data);
371                                 ast_log(LOG_DEBUG,"line length : %d\n",strln);
372                                 write(fdesc,&strln,sizeof(int));
373                                 write(fdesc,data,strln);
374                                 seekpos=lseek(fdesc,0,SEEK_CUR);
375                                 ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
376                         }
377                 } else {
378                         read(fdesc,&strln,sizeof(int));
379                         ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,strlen((char *)data));
380                         if (strlen((char *)data)==strln) {
381                                 ast_log(LOG_DEBUG,"Size OK\n");
382                                 read(fdesc,&bigstring,strln);
383                                 bigstring[strln] = 0;
384                                 if (strcmp(bigstring,data)==0) { 
385                                         readcache=1;
386                                 } else {
387                                         ast_log(LOG_WARNING,"Strings do not match\n");
388                                 }
389                         } else {
390                                 ast_log(LOG_WARNING,"Size mismatch\n");
391                         }
392                 }
393         }
394                         
395         if (readcache==1) {
396                 close(fd);
397                 fd=fdesc;
398                 ast_log(LOG_DEBUG,"Reading from cache...\n");
399         } else {
400                 ast_log(LOG_DEBUG,"Passing text to festival...\n");
401                 fs=fdopen(dup(fd),"wb");
402                 fprintf(fs,festivalcommand,(char *)data);
403                 fflush(fs);
404                 fclose(fs);
405         }
406         
407         /* Write to cache and then pass it down */
408         if (writecache==1) {
409                 ast_log(LOG_DEBUG,"Writing result to cache...\n");
410                 while ((strln=read(fd,buffer,16384))!=0) {
411                         write(fdesc,buffer,strln);
412                 }
413                 close(fd);
414                 close(fdesc);
415                 fd=open(cachefile,O_RDWR);
416                 lseek(fd,seekpos,SEEK_SET);
417         }
418         
419         ast_log(LOG_DEBUG,"Passing data to channel...\n");
420         
421         /* Read back info from server */
422         /* This assumes only one waveform will come back, also LP is unlikely */
423         wave = 0;
424         do {
425                 for (n=0; n < 3; )
426                         n += read(fd,ack+n,3-n);
427                 ack[3] = '\0';
428                 if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
429                         ast_log(LOG_DEBUG,"Festival WV command\n");
430                         waveform = socket_receive_file_to_buff(fd,&filesize);
431                         res = send_waveform_to_channel(chan,waveform,filesize, intstr);
432                         free(waveform);
433                         break;
434                 }
435                 else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
436                         ast_log(LOG_DEBUG,"Festival LP command\n");
437                         waveform = socket_receive_file_to_buff(fd,&filesize);
438                         waveform[filesize]='\0';
439                         ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
440                         free(waveform);
441                 } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
442                         ast_log(LOG_WARNING,"Festival returned ER\n");
443                         res=-1;
444                         break;
445                 }
446         } while (strcmp(ack,"OK\n") != 0);
447         close(fd);
448         LOCAL_USER_REMOVE(u);                                                                                
449         return res;
450
451 }
452
453 int unload_module(void)
454 {
455         STANDARD_HANGUP_LOCALUSERS;
456         return ast_unregister_application(app);
457 }
458
459 int load_module(void)
460 {
461         
462         return ast_register_application(app, festival_exec, synopsis, descrip);
463 }
464
465 char *description(void)
466 {
467         return tdesc;
468 }
469
470 int usecount(void)
471 {
472         int res;
473         STANDARD_USECOUNT(res);
474         return res;
475 }
476
477 char *key()
478 {
479         return ASTERISK_GPL_KEY;
480 }