Version 0.2.0 from FTP
[asterisk/asterisk.git] / apps / app_disa.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * DISA -- Direct Inward System Access Application  6/20/2001
5  * 
6  * Copyright (C) 2001, Linux Support Services, Inc.
7  *
8  * Jim Dixon <jim@lambdatel.com>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13  
14 #include <asterisk/lock.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/translate.h>
21 #include <asterisk/ulaw.h>
22 #include <string.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <math.h>
26 #include <pthread.h>
27 #include <sys/time.h>
28
29 /*
30 #define TONE_BLOCK_SIZE 320
31 */
32
33 static char *tdesc = "DISA (Direct Inward System Access) Application";
34
35 static char *app = "DISA";
36
37 static char *synopsis = "DISA (Direct Inward System Access)";
38
39 static char *descrip = 
40         "DISA (Direct Inward System Access) -- Allows someone from outside\n"
41         "the telephone switch (PBX) to obtain an \"internal\" system dialtone\n"
42         "and to place calls from it as if they were placing a call from within\n"
43         "the switch. A user calls a number that connects to the DISA application\n"
44         "and is given dialtone. The user enters their passcode, followed by the\n"
45         "pound sign (#). If the passcode is correct, the user is then given\n"
46         "system dialtone on which a call may be placed. Obviously, this type\n"
47         "of access has SERIOUS security implications, and GREAT care must be\n"
48         "taken NOT to compromise your security.\n\n"
49         "There is a possibility of accessing DISA without password. Simply\n"
50         "exchange your password with no-password.\n\n"
51         "  Example: exten => s,1,DISA,no-password|local\n\n"
52         "but be aware of using this for your security compromising.\n\n"
53         "The arguments to this application (in extensions.conf) allow either\n"
54         "specification of a single global password (that everyone uses), or\n"
55         "individual passwords contained in a file. It also allow specification\n"
56         "of the context on which the user will be dialing. If no context is\n"
57         "specified, the DISA application defaults the context to \"disa\"\n"
58         "presumably that a normal system will have a special context set up\n"
59         "for DISA use with some or a lot of restrictions. The arguments are\n"
60         "one of the following:\n\n"
61         "    numeric-passcode\n"
62         "    numeric-passcode|context\n"
63         "    full-pathname-of-file-that-contains-passcodes\n\n"
64         "The file that contains the passcodes (if used) allows specification\n"
65         "of either just a passcode (defaulting to the \"disa\" context, or\n"
66         "passcode|context on each line of the file. The file may contain blank\n"
67         "lines, or comments starting with \"#\" or \";\". In addition, the\n"
68         "above arguments may have |new-callerid-string appended to them, to\n"
69         "specify a new (different) callerid to be used for this call, for\n"
70         "example: numeric-passcode|context|\"My Phone\" <(234) 123-4567> or \n"
71         "full-pathname-of-passcode-file|\"My Phone\" <(234) 123-4567>. Note that\n"
72         "in the case of specifying the numeric-passcode, the context must be\n"
73         "specified if the callerid is specified also.\n\n"
74         "If login is successful, the application parses the dialed number in\n"
75         "the specified (or default) context, and returns 0 with the new extension\n"
76         "context filled-in and the priority set to 1, so that the PBX may\n"
77         "re-apply the routing tables to it and complete the call normally.";
78
79
80 STANDARD_LOCAL_USER;
81
82 LOCAL_USER_DECL;
83
84 static float loudness=4096.0;
85
86 int firstdigittimeout = 20000; /* 20 seconds first digit timeout */
87 int digittimeout = 10000; /* 10 seconds subsequent digit timeout */
88
89 static void make_tone_block(unsigned char *data, float f1, float f2, int len, int *x)
90 {
91 int     i;
92 float   val;
93
94         for(i = 0; i < len; i++)
95         {
96                 val = loudness * sin((f1 * 2.0 * M_PI * (*x))/8000.0);
97                 val += loudness * sin((f2 * 2.0 * M_PI * (*x)++)/8000.0);
98                 data[i] = AST_LIN2MU((int)val);
99          }              
100           /* wrap back around from 8000 */
101         if (*x >= 8000) *x = 0;
102         return;
103 }
104
105 static int ms_diff(struct timeval *tv1, struct timeval *tv2)
106 {
107 int     ms;
108         
109         ms = (tv1->tv_sec - tv2->tv_sec) * 1000;
110         ms += (tv1->tv_usec - tv2->tv_usec) / 1000;
111         return(ms);
112 }
113
114 static int disa_exec(struct ast_channel *chan, void *data)
115 {
116         int i,j,k,x;
117         struct localuser *u;
118         char tmp[256],arg2[256],exten[AST_MAX_EXTENSION],acctcode[20];
119         unsigned char tone_block[640],sil_block[640];
120         char *ourcontext,*ourcallerid;
121         struct ast_frame *f,wf;
122         struct timeval lastout, now, lastdigittime;
123         int res;
124         FILE *fp;
125
126         if (ast_set_write_format(chan,AST_FORMAT_ULAW))
127         {
128                 ast_log(LOG_WARNING, "Unable to set write format to Mu-law on %s\n",chan->name);
129                 return -1;
130         }
131         if (ast_set_read_format(chan,AST_FORMAT_ULAW))
132         {
133                 ast_log(LOG_WARNING, "Unable to set read format to Mu-law on %s\n",chan->name);
134                 return -1;
135         }
136         lastout.tv_sec = lastout.tv_usec = 0;
137           /* make block of silence */
138         memset(sil_block,0x7f,sizeof(sil_block));
139         if (!data || !strlen((char *)data)) {
140                 ast_log(LOG_WARNING, "disa requires an argument (passcode/passcode file)\n");
141                 return -1;
142         }
143         strncpy(tmp, (char *)data, sizeof(tmp)-1);
144         strtok(tmp, "|");
145         ourcontext = strtok(NULL, "|");
146         /* if context specified, save 2nd arg and parse third */
147         if (ourcontext) {
148                 strcpy(arg2,ourcontext);
149                 ourcallerid = strtok(NULL,"|");
150         }
151           /* if context not specified, use "disa" */
152         else {
153                 arg2[0] = 0;
154                 ourcallerid = NULL;
155                 ourcontext = "disa";
156         }
157         LOCAL_USER_ADD(u);
158         if (chan->_state != AST_STATE_UP)
159         {
160                 /* answer */
161                 ast_answer(chan);
162         }
163         i = k = x = 0; /* k is 0 for pswd entry, 1 for ext entry */
164         exten[0] = 0;
165         /* can we access DISA without password? */ 
166         if (!strcasecmp(tmp, "no-password"))
167         {
168                 k = 1;
169                 ast_log(LOG_DEBUG, "DISA no-password login success\n");
170         }
171         gettimeofday(&lastdigittime,NULL);
172         for(;;)
173         {
174                 gettimeofday(&now,NULL);
175                   /* if outa time, give em reorder */
176                 if (ms_diff(&now,&lastdigittime) > 
177                     ((k) ? digittimeout : firstdigittimeout))
178                 {
179                         ast_log(LOG_DEBUG,"DISA %s entry timeout on chan %s\n",
180                                 ((k) ? "extension" : "password"),chan->name);
181                         goto reorder;
182                 }
183                 if ((res = ast_waitfor(chan, -1) < 0)) {
184                         ast_log(LOG_DEBUG, "Waitfor returned %d\n", res);
185                         continue;
186                 }
187                         
188                 f = ast_read(chan);
189                 if (f == NULL) 
190                 {
191                         LOCAL_USER_REMOVE(u);
192                         return -1;
193                 }
194                 if ((f->frametype == AST_FRAME_CONTROL) &&
195                     (f->subclass == AST_CONTROL_HANGUP))
196                 {
197                         ast_frfree(f);
198                         LOCAL_USER_REMOVE(u);
199                         return -1;
200                 }
201                 if (f->frametype == AST_FRAME_VOICE) {
202                         if (!i || (ast_ignore_pattern(ourcontext, exten) && k)) {
203                                 wf.frametype = AST_FRAME_VOICE;
204                                 wf.subclass = AST_FORMAT_ULAW;
205                                 wf.offset = AST_FRIENDLY_OFFSET;
206                                 wf.mallocd = 0;
207                                 wf.data = tone_block;
208                                 wf.datalen = f->datalen;
209                                 make_tone_block(tone_block, 350, 440, f->datalen, &x);
210                                 wf.timelen = wf.datalen / 8;
211                                 ast_frfree(f);
212                             if (ast_write(chan, &wf)) 
213                                 {
214                                 ast_log(LOG_WARNING, "DISA Failed to write frame on %s\n",chan->name);
215                                         LOCAL_USER_REMOVE(u);
216                                         return -1;
217                                 }
218                         } else
219                                 ast_frfree(f);
220                         continue;
221                 }
222                   /* if not DTMF, just do it again */
223                 if (f->frametype != AST_FRAME_DTMF) 
224                 {
225                         ast_frfree(f);
226                         continue;
227                 }
228
229                 j = f->subclass;  /* save digit */
230                 ast_frfree(f);
231                 
232                 gettimeofday(&lastdigittime,NULL);
233                   /* got a DTMF tone */
234                 if (i < AST_MAX_EXTENSION) /* if still valid number of digits */
235                 {
236                         if (!k) /* if in password state */
237                         {
238                                 if (j == '#') /* end of password */
239                                 {
240                                           /* see if this is an integer */
241                                         if (sscanf(tmp,"%d",&j) < 1)
242                                            { /* nope, it must be a filename */
243                                                 fp = fopen(tmp,"r");
244                                                 if (!fp)
245                                                    {
246                                                         ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",tmp,chan->name);
247                                                         LOCAL_USER_REMOVE(u);
248                                                         return -1;
249                                                    }
250                                                 tmp[0] = 0;
251                                                 while(fgets(tmp,sizeof(tmp) - 1,fp))
252                                                    {
253                                                         if (!tmp[0]) continue;
254                                                         if (tmp[strlen(tmp) - 1] == '\n') 
255                                                                 tmp[strlen(tmp) - 1] = 0;
256                                                         if (!tmp[0]) continue;
257                                                           /* skip comments */
258                                                         if (tmp[0] == '#') continue;
259                                                         if (tmp[0] == ';') continue;
260                                                         strtok(tmp, "|");
261                                                         /* save 2nd arg as clid */
262                                                         ourcallerid = arg2;
263                                                         ourcontext = strtok(NULL, "|");
264                                                           /* password must be in valid format (numeric) */
265                                                         if (sscanf(tmp,"%d",&j) < 1) continue;
266                                                           /* if we got it */
267                                                         if (!strcmp(exten,tmp)) break;
268                                                    }
269                                                 fclose(fp);
270                                            }
271                                           /* compare the two */
272                                         if (strcmp(exten,tmp))
273                                         {
274                                                 ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",chan->name,exten);
275                                                 goto reorder;
276
277                                         }
278                                          /* password good, set to dial state */
279                                                 ast_log(LOG_WARNING,"DISA on chan %s password is good\n",chan->name);
280                                         k = 1;
281                                         i = 0;  /* re-set buffer pointer */
282                                         exten[sizeof(acctcode)] = 0;
283                                         strcpy(acctcode,exten);
284                                         exten[0] = 0;
285                                         ast_log(LOG_DEBUG,"Successful DISA log-in on chan %s\n",chan->name);
286                                         continue;
287                                 }
288                         }
289                         exten[i++] = j;  /* save digit */
290                         exten[i] = 0;
291                         if (!k) continue; /* if getting password, continue doing it */
292                           /* if this exists */
293                         if (ast_exists_extension(chan,ourcontext,exten,1, chan->callerid))
294                         {
295                                 if (ourcallerid && *ourcallerid)
296                                 {
297                                         if (chan->callerid) free(chan->callerid);
298                                         chan->callerid = strdup(ourcallerid);
299                                 }
300                                 strcpy(chan->exten,exten);
301                                 strcpy(chan->context,ourcontext);
302                                 strcpy(chan->accountcode,acctcode);
303                                 chan->priority = 0;
304                                 ast_cdr_init(chan->cdr,chan);
305                                 LOCAL_USER_REMOVE(u);
306                                 return 0;
307                         }
308                           /* if can do some more, do it */
309                         if (ast_canmatch_extension(chan,ourcontext,exten,1, chan->callerid)) continue;
310                 }
311 reorder:
312                 
313                 /* something is invalid, give em reorder forever */
314                 x = 0;
315                 k = 0;  /* k = 0 means busy tone, k = 1 means silence) */
316                 i = 0;  /* Number of samples we've done */
317                 for(;;)
318                 {
319                         if (ast_waitfor(chan, -1) < 0)
320                                 break;
321                         f = ast_read(chan);
322                         if (!f)
323                                 break;
324                         if (f->frametype == AST_FRAME_VOICE) {
325                                 wf.frametype = AST_FRAME_VOICE;
326                                 wf.subclass = AST_FORMAT_ULAW;
327                                 wf.offset = AST_FRIENDLY_OFFSET;
328                                 wf.mallocd = 0;
329                                 wf.data = tone_block;
330                                 wf.datalen = f->datalen;
331                                 wf.timelen = wf.datalen / 8;
332                                 if (k) 
333                                         memset(tone_block, 0x7f, wf.datalen);
334                                 else
335                                         make_tone_block(tone_block,480.0, 620.0,wf.datalen, &x);
336                                 i += wf.datalen / 8;
337                                 if (i > 250) {
338                                         i = 0;
339                                         k = !k;
340                                 }
341                             if (ast_write(chan, &wf)) 
342                                 {
343                                 ast_log(LOG_WARNING, "DISA Failed to write frame on %s\n",chan->name);
344                                         LOCAL_USER_REMOVE(u);
345                                         return -1;
346                                 }
347                         }
348                         ast_frfree(f);
349                 }
350                 LOCAL_USER_REMOVE(u);
351                 return -1;
352         }
353 }
354
355 int unload_module(void)
356 {
357         STANDARD_HANGUP_LOCALUSERS;
358         return ast_unregister_application(app);
359 }
360
361 int load_module(void)
362 {
363         return ast_register_application(app, disa_exec, synopsis, descrip);
364 }
365
366 char *description(void)
367 {
368         return tdesc;
369 }
370
371 int usecount(void)
372 {
373         int res;
374         STANDARD_USECOUNT(res);
375         return res;
376 }
377
378 char *key()
379 {
380         return ASTERISK_GPL_KEY;
381 }