Version 0.3.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];
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;
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                                                         /* save 2nd arg as clid */
265                                                         ourcallerid = arg2;
266                                                         ourcontext = strsep(&stringp, "|");
267                                                           /* password must be in valid format (numeric) */
268                                                         if (sscanf(tmp,"%d",&j) < 1) continue;
269                                                           /* if we got it */
270                                                         if (!strcmp(exten,tmp)) break;
271                                                    }
272                                                 fclose(fp);
273                                            }
274                                           /* compare the two */
275                                         if (strcmp(exten,tmp))
276                                         {
277                                                 ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",chan->name,exten);
278                                                 goto reorder;
279
280                                         }
281                                          /* password good, set to dial state */
282                                                 ast_log(LOG_WARNING,"DISA on chan %s password is good\n",chan->name);
283                                         k = 1;
284                                         i = 0;  /* re-set buffer pointer */
285                                         exten[sizeof(acctcode)] = 0;
286                                         strcpy(acctcode,exten);
287                                         exten[0] = 0;
288                                         ast_log(LOG_DEBUG,"Successful DISA log-in on chan %s\n",chan->name);
289                                         continue;
290                                 }
291                         }
292                         exten[i++] = j;  /* save digit */
293                         exten[i] = 0;
294                         if (!k) continue; /* if getting password, continue doing it */
295                           /* if this exists */
296                         if (ast_exists_extension(chan,ourcontext,exten,1, chan->callerid))
297                         {
298                                 if (ourcallerid && *ourcallerid)
299                                 {
300                                         if (chan->callerid) free(chan->callerid);
301                                         chan->callerid = strdup(ourcallerid);
302                                 }
303                                 strcpy(chan->exten,exten);
304                                 strcpy(chan->context,ourcontext);
305                                 strcpy(chan->accountcode,acctcode);
306                                 chan->priority = 0;
307                                 ast_cdr_init(chan->cdr,chan);
308                                 LOCAL_USER_REMOVE(u);
309                                 return 0;
310                         }
311                           /* if can do some more, do it */
312                         if (ast_canmatch_extension(chan,ourcontext,exten,1, chan->callerid)) continue;
313                 }
314 reorder:
315                 
316                 /* something is invalid, give em reorder forever */
317                 x = 0;
318                 k = 0;  /* k = 0 means busy tone, k = 1 means silence) */
319                 i = 0;  /* Number of samples we've done */
320                 for(;;)
321                 {
322                         if (ast_waitfor(chan, -1) < 0)
323                                 break;
324                         f = ast_read(chan);
325                         if (!f)
326                                 break;
327                         if (f->frametype == AST_FRAME_VOICE) {
328                                 wf.frametype = AST_FRAME_VOICE;
329                                 wf.subclass = AST_FORMAT_ULAW;
330                                 wf.offset = AST_FRIENDLY_OFFSET;
331                                 wf.mallocd = 0;
332                                 wf.data = tone_block;
333                                 wf.datalen = f->datalen;
334                                 wf.samples = wf.datalen;
335                                 if (k) 
336                                         memset(tone_block, 0x7f, wf.datalen);
337                                 else
338                                         make_tone_block(tone_block,480.0, 620.0,wf.datalen, &x);
339                                 i += wf.datalen / 8;
340                                 if (i > 250) {
341                                         i = 0;
342                                         k = !k;
343                                 }
344                             if (ast_write(chan, &wf)) 
345                                 {
346                                 ast_log(LOG_WARNING, "DISA Failed to write frame on %s\n",chan->name);
347                                         LOCAL_USER_REMOVE(u);
348                                         return -1;
349                                 }
350                         }
351                         ast_frfree(f);
352                 }
353                 LOCAL_USER_REMOVE(u);
354                 return -1;
355         }
356 }
357
358 int unload_module(void)
359 {
360         STANDARD_HANGUP_LOCALUSERS;
361         return ast_unregister_application(app);
362 }
363
364 int load_module(void)
365 {
366         return ast_register_application(app, disa_exec, synopsis, descrip);
367 }
368
369 char *description(void)
370 {
371         return tdesc;
372 }
373
374 int usecount(void)
375 {
376         int res;
377         STANDARD_USECOUNT(res);
378         return res;
379 }
380
381 char *key()
382 {
383         return ASTERISK_GPL_KEY;
384 }