DISA enhancemnets (bug #2766)
[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-2004, Digium, Inc.
7  *
8  * Jim Dixon <jim@lambdatel.com>
9  *
10  * Made only slightly more sane by Mark Spencer <markster@digium.com>
11  *
12  * This program is free software, distributed under the terms of
13  * the GNU General Public License
14  */
15  
16 #include <asterisk/lock.h>
17 #include <asterisk/file.h>
18 #include <asterisk/logger.h>
19 #include <asterisk/channel.h>
20 #include <asterisk/app.h>
21 #include <asterisk/indications.h>
22 #include <asterisk/pbx.h>
23 #include <asterisk/module.h>
24 #include <asterisk/translate.h>
25 #include <asterisk/ulaw.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <math.h>
30 #include <sys/time.h>
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>.  Last\n"
72         "but not least, |mailbox[@context] may be appended, which will cause\n"
73         "a stutter-dialtone (indication \"dialrecall\") to be used, if the\n"
74         "specified mailbox contains any new messages, for example:\n"
75         "numeric-passcode|context||1234 (w/a changing callerid).  Note that\n"
76         "in the case of specifying the numeric-passcode, the context must be\n"
77         "specified if the callerid is specified also.\n\n"
78         "If login is successful, the application parses the dialed number in\n"
79         "the specified (or default) context, and returns 0 with the new extension\n"
80         "context filled-in and the priority set to 1, so that the PBX may\n"
81         "re-apply the routing tables to it and complete the call normally.";
82
83
84 STANDARD_LOCAL_USER;
85
86 LOCAL_USER_DECL;
87
88 static int ms_diff(struct timeval *tv1, struct timeval *tv2)
89 {
90 int     ms;
91         
92         ms = (tv1->tv_sec - tv2->tv_sec) * 1000;
93         ms += (tv1->tv_usec - tv2->tv_usec) / 1000;
94         return(ms);
95 }
96
97 static void play_dialtone(struct ast_channel *chan, char *mailbox)
98 {
99         const struct tone_zone_sound *ts = NULL;
100         if(ast_app_has_voicemail(mailbox, NULL))
101                 ts = ast_get_indication_tone(chan->zone, "dialrecall");
102         else
103                 ts = ast_get_indication_tone(chan->zone, "dial");
104         if (ts)
105                 ast_playtones_start(chan, 0, ts->data, 0);
106         else
107                 ast_tonepair_start(chan, 350, 440, 0, 0);
108 }
109
110 static int disa_exec(struct ast_channel *chan, void *data)
111 {
112         int i,j,k,x,did_ignore;
113         int firstdigittimeout = 20000;
114         int digittimeout = 10000;
115         struct localuser *u;
116         char tmp[256],arg2[256]="",exten[AST_MAX_EXTENSION],acctcode[20]="";
117         char *ourcontext,*ourcallerid,*mailbox;
118         struct ast_frame *f;
119         struct timeval lastout, now, lastdigittime;
120         int res;
121         time_t rstart;
122         FILE *fp;
123         char *stringp=NULL;
124
125         if (chan->pbx) {
126                 firstdigittimeout = chan->pbx->rtimeout*1000;
127                 digittimeout = chan->pbx->dtimeout*1000;
128         }
129         
130         if (ast_set_write_format(chan,AST_FORMAT_ULAW))
131         {
132                 ast_log(LOG_WARNING, "Unable to set write format to Mu-law on %s\n",chan->name);
133                 return -1;
134         }
135         if (ast_set_read_format(chan,AST_FORMAT_ULAW))
136         {
137                 ast_log(LOG_WARNING, "Unable to set read format to Mu-law on %s\n",chan->name);
138                 return -1;
139         }
140         lastout.tv_sec = lastout.tv_usec = 0;
141         if (!data || !strlen((char *)data)) {
142                 ast_log(LOG_WARNING, "disa requires an argument (passcode/passcode file)\n");
143                 return -1;
144         }
145         ast_log(LOG_DEBUG, "Digittimeout: %i\n", digittimeout);
146         ast_log(LOG_DEBUG, "Responsetimeout: %i\n", firstdigittimeout);
147         strncpy(tmp, (char *)data, sizeof(tmp)-1);
148         stringp=tmp;
149         strsep(&stringp, "|");
150         ourcontext = strsep(&stringp, "|");
151         /* if context specified, save 2nd arg and parse third */
152         if (ourcontext) {
153                 strncpy(arg2,ourcontext, sizeof(arg2) - 1);
154                 ourcallerid = strsep(&stringp,"|");
155         }
156           /* if context not specified, use "disa" */
157         else {
158                 arg2[0] = 0;
159                 ourcallerid = NULL;
160                 ourcontext = "disa";
161         }
162         (char *)mailbox = strsep(&stringp, "|");
163         if (!mailbox)
164                 mailbox = "";
165         ast_log(LOG_DEBUG, "Mailbox: %s\n",mailbox);
166         LOCAL_USER_ADD(u);
167         if (chan->_state != AST_STATE_UP)
168         {
169                 /* answer */
170                 ast_answer(chan);
171         }
172         i = k = x = 0; /* k is 0 for pswd entry, 1 for ext entry */
173         did_ignore = 0;
174         exten[0] = 0;
175         acctcode[0] = 0;
176         /* can we access DISA without password? */ 
177
178         ast_log(LOG_DEBUG, "Context: %s\n",ourcontext);
179
180         if (!strcasecmp(tmp, "no-password"))
181         {;
182                 k = 1;
183                 ast_log(LOG_DEBUG, "DISA no-password login success\n");
184         }
185         gettimeofday(&lastdigittime,NULL);
186
187         play_dialtone(chan, mailbox);
188
189         for(;;)
190         {
191                 gettimeofday(&now,NULL);
192                   /* if outa time, give em reorder */
193                 if (ms_diff(&now,&lastdigittime) > 
194                     ((k) ? digittimeout : firstdigittimeout))
195                 {
196                         ast_log(LOG_DEBUG,"DISA %s entry timeout on chan %s\n",
197                                 ((k) ? "extension" : "password"),chan->name);
198                         break;
199                 }
200                 if ((res = ast_waitfor(chan, -1) < 0)) {
201                         ast_log(LOG_DEBUG, "Waitfor returned %d\n", res);
202                         continue;
203                 }
204                         
205                 f = ast_read(chan);
206                 if (f == NULL) 
207                 {
208                         LOCAL_USER_REMOVE(u);
209                         return -1;
210                 }
211                 if ((f->frametype == AST_FRAME_CONTROL) &&
212                     (f->subclass == AST_CONTROL_HANGUP))
213                 {
214                         ast_frfree(f);
215                         LOCAL_USER_REMOVE(u);
216                         return -1;
217                 }
218                 if (f->frametype == AST_FRAME_VOICE) {
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                 if (i == 0) 
232                         ast_playtones_stop(chan);
233
234                 gettimeofday(&lastdigittime,NULL);
235                   /* got a DTMF tone */
236                 if (i < AST_MAX_EXTENSION) /* if still valid number of digits */
237                 {
238                         if (!k) /* if in password state */
239                         {
240                                 if (j == '#') /* end of password */
241                                 {
242                                           /* see if this is an integer */
243                                         if (sscanf(tmp,"%d",&j) < 1)
244                                            { /* nope, it must be a filename */
245                                                 fp = fopen(tmp,"r");
246                                                 if (!fp)
247                                                    {
248                                                         ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",tmp,chan->name);
249                                                         LOCAL_USER_REMOVE(u);
250                                                         return -1;
251                                                    }
252                                                 tmp[0] = 0;
253                                                 while(fgets(tmp,sizeof(tmp) - 1,fp))
254                                                    {
255                                                         char *stringp=NULL,*stringp2;
256                                                         if (!tmp[0]) continue;
257                                                         if (tmp[strlen(tmp) - 1] == '\n') 
258                                                                 tmp[strlen(tmp) - 1] = 0;
259                                                         if (!tmp[0]) continue;
260                                                           /* skip comments */
261                                                         if (tmp[0] == '#') continue;
262                                                         if (tmp[0] == ';') continue;
263                                                         stringp=tmp;
264                                                         strsep(&stringp, "|");
265                                                         stringp2=strsep(&stringp, "|");
266                                                         if (stringp2) {
267                                                                 ourcontext=stringp2;
268                                                                 stringp2=strsep(&stringp, "|");
269                                                                 if (stringp2) ourcallerid=stringp2;
270                                                         }
271                                                           /* password must be in valid format (numeric) */
272                                                         if (sscanf(tmp,"%d",&j) < 1) continue;
273                                                           /* if we got it */
274                                                         if (!strcmp(exten,tmp)) break;
275                                                    }
276                                                 fclose(fp);
277                                            }
278                                           /* compare the two */
279                                         if (strcmp(exten,tmp))
280                                         {
281                                                 ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",chan->name,exten);
282                                                 goto reorder;
283
284                                         }
285                                          /* password good, set to dial state */
286                                         ast_log(LOG_DEBUG,"DISA on chan %s password is good\n",chan->name);
287                                         play_dialtone(chan, mailbox);
288
289                                         k = 1;
290                                         i = 0;  /* re-set buffer pointer */
291                                         exten[sizeof(acctcode)] = 0;
292                                         strncpy(acctcode,exten, sizeof(acctcode) - 1);
293                                         exten[0] = 0;
294                                         ast_log(LOG_DEBUG,"Successful DISA log-in on chan %s\n",chan->name);
295                                         continue;
296                                 }
297                         }
298
299                         exten[i++] = j;  /* save digit */
300                         exten[i] = 0;
301                         if (!k) continue; /* if getting password, continue doing it */
302                           /* if this exists */
303
304                         if (ast_ignore_pattern(ourcontext, exten)) {
305                                 play_dialtone(chan, "");
306                                 did_ignore = 1;
307                         } else
308                                 if (did_ignore) {
309                                         ast_playtones_stop(chan);
310                                         did_ignore = 0;
311                                 }
312
313                           /* if can do some more, do it */
314                         if (!ast_matchmore_extension(chan,ourcontext,exten,1, chan->cid.cid_num)) {
315                                 break;
316                         }
317                 }
318         }
319
320         if (k && ast_exists_extension(chan,ourcontext,exten,1, chan->cid.cid_num))
321         {
322                 ast_playtones_stop(chan);
323                 /* We're authenticated and have a valid extension */
324                 if (ourcallerid && *ourcallerid)
325                 {
326                         if (chan->cid.cid_num) free(chan->cid.cid_num);
327                         chan->cid.cid_num = strdup(ourcallerid);
328                 }
329                 strncpy(chan->exten, exten, sizeof(chan->exten) - 1);
330                 strncpy(chan->context, ourcontext, sizeof(chan->context) - 1);
331                 strncpy(chan->accountcode, acctcode, sizeof(chan->accountcode) - 1);
332                 chan->priority = 0;
333                 ast_cdr_init(chan->cdr,chan);
334                 LOCAL_USER_REMOVE(u);
335                 return 0;
336         }
337
338 reorder:
339
340         ast_indicate(chan,AST_CONTROL_CONGESTION);
341         /* something is invalid, give em reorder for several seconds */
342         time(&rstart);
343         while(time(NULL) < rstart + 10)
344         {
345                 if (ast_waitfor(chan, -1) < 0)
346                         break;
347                 f = ast_read(chan);
348                 if (!f)
349                         break;
350                 ast_frfree(f);
351         }
352         ast_playtones_stop(chan);
353         LOCAL_USER_REMOVE(u);
354         return -1;
355 }
356
357 int unload_module(void)
358 {
359         STANDARD_HANGUP_LOCALUSERS;
360         return ast_unregister_application(app);
361 }
362
363 int load_module(void)
364 {
365         return ast_register_application(app, disa_exec, synopsis, descrip);
366 }
367
368 char *description(void)
369 {
370         return tdesc;
371 }
372
373 int usecount(void)
374 {
375         int res;
376         STANDARD_USECOUNT(res);
377         return res;
378 }
379
380 char *key(void)
381 {
382         return ASTERISK_GPL_KEY;
383 }