Fri Mar 7 07:00:00 CET 2003
[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];
120         char *ourcontext,*ourcallerid;
121         struct ast_frame *f,wf;
122         struct timeval lastout, now, lastdigittime;
123         int res;
124         FILE *fp;
125         char *stringp=NULL;
126
127         if (ast_set_write_format(chan,AST_FORMAT_ULAW))
128         {
129                 ast_log(LOG_WARNING, "Unable to set write format to Mu-law on %s\n",chan->name);
130                 return -1;
131         }
132         if (ast_set_read_format(chan,AST_FORMAT_ULAW))
133         {
134                 ast_log(LOG_WARNING, "Unable to set read format to Mu-law on %s\n",chan->name);
135                 return -1;
136         }
137         lastout.tv_sec = lastout.tv_usec = 0;
138         if (!data || !strlen((char *)data)) {
139                 ast_log(LOG_WARNING, "disa requires an argument (passcode/passcode file)\n");
140                 return -1;
141         }
142         strncpy(tmp, (char *)data, sizeof(tmp)-1);
143         stringp=tmp;
144         strsep(&stringp, "|");
145         ourcontext = strsep(&stringp, "|");
146         /* if context specified, save 2nd arg and parse third */
147         if (ourcontext) {
148                 strcpy(arg2,ourcontext);
149                 ourcallerid = strsep(&stringp,"|");
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         acctcode[0] = 0;
166         /* can we access DISA without password? */ 
167         if (!strcasecmp(tmp, "no-password"))
168         {
169                 k = 1;
170                 ast_log(LOG_DEBUG, "DISA no-password login success\n");
171         }
172         gettimeofday(&lastdigittime,NULL);
173         for(;;)
174         {
175                 gettimeofday(&now,NULL);
176                   /* if outa time, give em reorder */
177                 if (ms_diff(&now,&lastdigittime) > 
178                     ((k) ? digittimeout : firstdigittimeout))
179                 {
180                         ast_log(LOG_DEBUG,"DISA %s entry timeout on chan %s\n",
181                                 ((k) ? "extension" : "password"),chan->name);
182                         goto reorder;
183                 }
184                 if ((res = ast_waitfor(chan, -1) < 0)) {
185                         ast_log(LOG_DEBUG, "Waitfor returned %d\n", res);
186                         continue;
187                 }
188                         
189                 f = ast_read(chan);
190                 if (f == NULL) 
191                 {
192                         LOCAL_USER_REMOVE(u);
193                         return -1;
194                 }
195                 if ((f->frametype == AST_FRAME_CONTROL) &&
196                     (f->subclass == AST_CONTROL_HANGUP))
197                 {
198                         ast_frfree(f);
199                         LOCAL_USER_REMOVE(u);
200                         return -1;
201                 }
202                 if (f->frametype == AST_FRAME_VOICE) {
203                         if (!i || (ast_ignore_pattern(ourcontext, exten) && k)) {
204                                 wf.frametype = AST_FRAME_VOICE;
205                                 wf.subclass = AST_FORMAT_ULAW;
206                                 wf.offset = AST_FRIENDLY_OFFSET;
207                                 wf.mallocd = 0;
208                                 wf.data = tone_block;
209                                 wf.datalen = f->datalen;
210                                 make_tone_block(tone_block, 350, 440, f->datalen, &x);
211                                 wf.samples = wf.datalen;
212                                 ast_frfree(f);
213                             if (ast_write(chan, &wf)) 
214                                 {
215                                 ast_log(LOG_WARNING, "DISA Failed to write frame on %s\n",chan->name);
216                                         LOCAL_USER_REMOVE(u);
217                                         return -1;
218                                 }
219                         } else
220                                 ast_frfree(f);
221                         continue;
222                 }
223                   /* if not DTMF, just do it again */
224                 if (f->frametype != AST_FRAME_DTMF) 
225                 {
226                         ast_frfree(f);
227                         continue;
228                 }
229
230                 j = f->subclass;  /* save digit */
231                 ast_frfree(f);
232                 
233                 gettimeofday(&lastdigittime,NULL);
234                   /* got a DTMF tone */
235                 if (i < AST_MAX_EXTENSION) /* if still valid number of digits */
236                 {
237                         if (!k) /* if in password state */
238                         {
239                                 if (j == '#') /* end of password */
240                                 {
241                                           /* see if this is an integer */
242                                         if (sscanf(tmp,"%d",&j) < 1)
243                                            { /* nope, it must be a filename */
244                                                 fp = fopen(tmp,"r");
245                                                 if (!fp)
246                                                    {
247                                                         ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",tmp,chan->name);
248                                                         LOCAL_USER_REMOVE(u);
249                                                         return -1;
250                                                    }
251                                                 tmp[0] = 0;
252                                                 while(fgets(tmp,sizeof(tmp) - 1,fp))
253                                                    {
254                                                         char *stringp=NULL,*stringp2;
255                                                         if (!tmp[0]) continue;
256                                                         if (tmp[strlen(tmp) - 1] == '\n') 
257                                                                 tmp[strlen(tmp) - 1] = 0;
258                                                         if (!tmp[0]) continue;
259                                                           /* skip comments */
260                                                         if (tmp[0] == '#') continue;
261                                                         if (tmp[0] == ';') continue;
262                                                         stringp=tmp;
263                                                         strsep(&stringp, "|");
264                                                         stringp2=strsep(&stringp, "|");
265                                                         if (stringp2) {
266                                                                 ourcontext=stringp2;
267                                                                 stringp2=strsep(&stringp, "|");
268                                                                 if (stringp2) ourcallerid=stringp2;
269                                                         }
270                                                           /* password must be in valid format (numeric) */
271                                                         if (sscanf(tmp,"%d",&j) < 1) continue;
272                                                           /* if we got it */
273                                                         if (!strcmp(exten,tmp)) break;
274                                                    }
275                                                 fclose(fp);
276                                            }
277                                           /* compare the two */
278                                         if (strcmp(exten,tmp))
279                                         {
280                                                 ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",chan->name,exten);
281                                                 goto reorder;
282
283                                         }
284                                          /* password good, set to dial state */
285                                                 ast_log(LOG_WARNING,"DISA on chan %s password is good\n",chan->name);
286                                         k = 1;
287                                         i = 0;  /* re-set buffer pointer */
288                                         exten[sizeof(acctcode)] = 0;
289                                         strcpy(acctcode,exten);
290                                         exten[0] = 0;
291                                         ast_log(LOG_DEBUG,"Successful DISA log-in on chan %s\n",chan->name);
292                                         continue;
293                                 }
294                         }
295                         exten[i++] = j;  /* save digit */
296                         exten[i] = 0;
297                         if (!k) continue; /* if getting password, continue doing it */
298                           /* if this exists */
299                         if (ast_exists_extension(chan,ourcontext,exten,1, chan->callerid))
300                         {
301                                 if (ourcallerid && *ourcallerid)
302                                 {
303                                         if (chan->callerid) free(chan->callerid);
304                                         chan->callerid = strdup(ourcallerid);
305                                 }
306                                 strcpy(chan->exten,exten);
307                                 strcpy(chan->context,ourcontext);
308                                 strcpy(chan->accountcode,acctcode);
309                                 chan->priority = 0;
310                                 ast_cdr_init(chan->cdr,chan);
311                                 LOCAL_USER_REMOVE(u);
312                                 return 0;
313                         }
314                           /* if can do some more, do it */
315                         if (ast_canmatch_extension(chan,ourcontext,exten,1, chan->callerid)) continue;
316                 }
317 reorder:
318                 
319                 /* something is invalid, give em reorder forever */
320                 x = 0;
321                 k = 0;  /* k = 0 means busy tone, k = 1 means silence) */
322                 i = 0;  /* Number of samples we've done */
323                 for(;;)
324                 {
325                         if (ast_waitfor(chan, -1) < 0)
326                                 break;
327                         f = ast_read(chan);
328                         if (!f)
329                                 break;
330                         if (f->frametype == AST_FRAME_VOICE) {
331                                 wf.frametype = AST_FRAME_VOICE;
332                                 wf.subclass = AST_FORMAT_ULAW;
333                                 wf.offset = AST_FRIENDLY_OFFSET;
334                                 wf.mallocd = 0;
335                                 wf.data = tone_block;
336                                 wf.datalen = f->datalen;
337                                 wf.samples = wf.datalen;
338                                 if (k) 
339                                         memset(tone_block, 0x7f, wf.datalen);
340                                 else
341                                         make_tone_block(tone_block,480.0, 620.0,wf.datalen, &x);
342                                 i += wf.datalen / 8;
343                                 if (i > 250) {
344                                         i = 0;
345                                         k = !k;
346                                 }
347                             if (ast_write(chan, &wf)) 
348                                 {
349                                 ast_log(LOG_WARNING, "DISA Failed to write frame on %s\n",chan->name);
350                                         LOCAL_USER_REMOVE(u);
351                                         return -1;
352                                 }
353                         }
354                         ast_frfree(f);
355                 }
356                 LOCAL_USER_REMOVE(u);
357                 return -1;
358         }
359 }
360
361 int unload_module(void)
362 {
363         STANDARD_HANGUP_LOCALUSERS;
364         return ast_unregister_application(app);
365 }
366
367 int load_module(void)
368 {
369         return ast_register_application(app, disa_exec, synopsis, descrip);
370 }
371
372 char *description(void)
373 {
374         return tdesc;
375 }
376
377 int usecount(void)
378 {
379         int res;
380         STANDARD_USECOUNT(res);
381         return res;
382 }
383
384 char *key()
385 {
386         return ASTERISK_GPL_KEY;
387 }