DISA fix, makefile fix (bug #3049)
[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 <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;
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) ? digittimeout : firstdigittimeout))
196                 {
197                         ast_log(LOG_DEBUG,"DISA %s entry timeout on chan %s\n",
198                                 ((k) ? "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                         ast_playtones_stop(chan);
234
235                 gettimeofday(&lastdigittime,NULL);
236                   /* got a DTMF tone */
237                 if (i < AST_MAX_EXTENSION) /* if still valid number of digits */
238                 {
239                         if (!k) /* if in password state */
240                         {
241                                 if (j == '#') /* end of password */
242                                 {
243                                           /* see if this is an integer */
244                                         if (sscanf(tmp,"%d",&j) < 1)
245                                            { /* nope, it must be a filename */
246                                                 fp = fopen(tmp,"r");
247                                                 if (!fp)
248                                                    {
249                                                         ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",tmp,chan->name);
250                                                         LOCAL_USER_REMOVE(u);
251                                                         return -1;
252                                                    }
253                                                 tmp[0] = 0;
254                                                 while(fgets(tmp,sizeof(tmp) - 1,fp))
255                                                    {
256                                                         char *stringp=NULL,*stringp2;
257                                                         if (!tmp[0]) continue;
258                                                         if (tmp[strlen(tmp) - 1] == '\n') 
259                                                                 tmp[strlen(tmp) - 1] = 0;
260                                                         if (!tmp[0]) continue;
261                                                           /* skip comments */
262                                                         if (tmp[0] == '#') continue;
263                                                         if (tmp[0] == ';') continue;
264                                                         stringp=tmp;
265                                                         strsep(&stringp, "|");
266                                                         stringp2=strsep(&stringp, "|");
267                                                         if (stringp2) {
268                                                                 ourcontext=stringp2;
269                                                                 stringp2=strsep(&stringp, "|");
270                                                                 if (stringp2) ourcallerid=stringp2;
271                                                         }
272                                                           /* password must be in valid format (numeric) */
273                                                         if (sscanf(tmp,"%d",&j) < 1) continue;
274                                                           /* if we got it */
275                                                         if (!strcmp(exten,tmp)) break;
276                                                    }
277                                                 fclose(fp);
278                                            }
279                                           /* compare the two */
280                                         if (strcmp(exten,tmp))
281                                         {
282                                                 ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",chan->name,exten);
283                                                 goto reorder;
284
285                                         }
286                                          /* password good, set to dial state */
287                                         ast_log(LOG_DEBUG,"DISA on chan %s password is good\n",chan->name);
288                                         play_dialtone(chan, mailbox);
289
290                                         k = 1;
291                                         i = 0;  /* re-set buffer pointer */
292                                         exten[sizeof(acctcode)] = 0;
293                                         strncpy(acctcode,exten, sizeof(acctcode) - 1);
294                                         exten[0] = 0;
295                                         ast_log(LOG_DEBUG,"Successful DISA log-in on chan %s\n",chan->name);
296                                         continue;
297                                 }
298                         }
299
300                         exten[i++] = j;  /* save digit */
301                         exten[i] = 0;
302                         if (!k) continue; /* if getting password, continue doing it */
303                           /* if this exists */
304
305                         if (ast_ignore_pattern(ourcontext, exten)) {
306                                 play_dialtone(chan, "");
307                                 did_ignore = 1;
308                         } else
309                                 if (did_ignore) {
310                                         ast_playtones_stop(chan);
311                                         did_ignore = 0;
312                                 }
313
314                           /* if can do some more, do it */
315                         if (!ast_matchmore_extension(chan,ourcontext,exten,1, chan->cid.cid_num)) {
316                                 break;
317                         }
318                 }
319         }
320
321         if (k && ast_exists_extension(chan,ourcontext,exten,1, chan->cid.cid_num))
322         {
323                 ast_playtones_stop(chan);
324                 /* We're authenticated and have a valid extension */
325                 if (ourcallerid && *ourcallerid)
326                 {
327                         ast_callerid_split(ourcallerid, ourcidname, sizeof(ourcidname), ourcidnum, sizeof(ourcidnum));
328                         ast_set_callerid(chan, ourcidnum, ourcidname, ourcidnum);
329                 }
330                 strncpy(chan->exten, exten, sizeof(chan->exten) - 1);
331                 strncpy(chan->context, ourcontext, sizeof(chan->context) - 1);
332                 strncpy(chan->accountcode, acctcode, sizeof(chan->accountcode) - 1);
333                 chan->priority = 0;
334                 ast_cdr_reset(chan->cdr,AST_CDR_FLAG_POSTED);
335                 LOCAL_USER_REMOVE(u);
336                 return 0;
337         }
338
339 reorder:
340
341         ast_indicate(chan,AST_CONTROL_CONGESTION);
342         /* something is invalid, give em reorder for several seconds */
343         time(&rstart);
344         while(time(NULL) < rstart + 10)
345         {
346                 if (ast_waitfor(chan, -1) < 0)
347                         break;
348                 f = ast_read(chan);
349                 if (!f)
350                         break;
351                 ast_frfree(f);
352         }
353         ast_playtones_stop(chan);
354         LOCAL_USER_REMOVE(u);
355         return -1;
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(void)
382 {
383         return ASTERISK_GPL_KEY;
384 }