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