Fix app_disa to set the proper variable before goign to invalid (bug #5439)
[asterisk/asterisk.git] / apps / app_disa.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Jim Dixon <jim@lambdatel.com>
7  *
8  * Made only slightly more sane by Mark Spencer <markster@digium.com>
9  *
10  * See http://www.asterisk.org for more information about
11  * the Asterisk project. Please do not directly contact
12  * any of the maintainers of this project for assistance;
13  * the project provides a web site, mailing lists and IRC
14  * channels for your use.
15  *
16  * This program is free software, distributed under the terms of
17  * the GNU General Public License Version 2. See the LICENSE file
18  * at the top of the source tree.
19  */
20
21 /*
22  *
23  * DISA -- Direct Inward System Access Application
24  * 
25  */
26  
27 #include <string.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <math.h>
31 #include <sys/time.h>
32
33 #include "asterisk.h"
34
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36
37 #include "asterisk/lock.h"
38 #include "asterisk/file.h"
39 #include "asterisk/logger.h"
40 #include "asterisk/channel.h"
41 #include "asterisk/app.h"
42 #include "asterisk/indications.h"
43 #include "asterisk/pbx.h"
44 #include "asterisk/module.h"
45 #include "asterisk/translate.h"
46 #include "asterisk/ulaw.h"
47 #include "asterisk/callerid.h"
48
49 static char *tdesc = "DISA (Direct Inward System Access) Application";
50
51 static char *app = "DISA";
52
53 static char *synopsis = "DISA (Direct Inward System Access)";
54
55 static char *descrip = 
56         "DISA(<numeric passcode>[|<context>]) or disa(<filename>)\n"
57         "The DISA, Direct Inward System Access, application allows someone from \n"
58         "outside the telephone switch (PBX) to obtain an \"internal\" system \n"
59         "dialtone and to place calls from it as if they were placing a call from \n"
60         "within the switch.\n"
61         "DISA plays a dialtone. The user enters their numeric passcode, followed by\n"
62         "the pound sign (#). If the passcode is correct, the user is then given\n"
63         "system dialtone on which a call may be placed. Obviously, this type\n"
64         "of access has SERIOUS security implications, and GREAT care must be\n"
65         "taken NOT to compromise your security.\n\n"
66         "There is a possibility of accessing DISA without password. Simply\n"
67         "exchange your password with \"no-password\".\n\n"
68         "    Example: exten => s,1,DISA(no-password|local)\n\n"
69         "Be aware that using this compromises the security of your PBX.\n\n"
70         "The arguments to this application (in extensions.conf) allow either\n"
71         "specification of a single global passcode (that everyone uses), or\n"
72         "individual passcodes contained in a file. It also allow specification\n"
73         "of the context on which the user will be dialing. If no context is\n"
74         "specified, the DISA application defaults the context to \"disa\".\n"
75         "Presumably a normal system will have a special context set up\n"
76         "for DISA use with some or a lot of restrictions. \n\n"
77         "The file that contains the passcodes (if used) allows specification\n"
78         "of either just a passcode (defaulting to the \"disa\" context, or\n"
79         "passcode|context on each line of the file. The file may contain blank\n"
80         "lines, or comments starting with \"#\" or \";\". In addition, the\n"
81         "above arguments may have |new-callerid-string appended to them, to\n"
82         "specify a new (different) callerid to be used for this call, for\n"
83         "example: numeric-passcode|context|\"My Phone\" <(234) 123-4567> or \n"
84         "full-pathname-of-passcode-file|\"My Phone\" <(234) 123-4567>.  Last\n"
85         "but not least, |mailbox[@context] may be appended, which will cause\n"
86         "a stutter-dialtone (indication \"dialrecall\") to be used, if the\n"
87         "specified mailbox contains any new messages, for example:\n"
88         "numeric-passcode|context||1234 (w/a changing callerid).  Note that\n"
89         "in the case of specifying the numeric-passcode, the context must be\n"
90         "specified if the callerid is specified also.\n\n"
91         "If login is successful, the application looks up the dialed number in\n"
92         "the specified (or default) context, and executes it if found.\n"
93         "If the user enters an invalid extension and extension \"i\" (invalid) \n"
94         "exists in the context, it will be used.\n";
95
96
97 STANDARD_LOCAL_USER;
98
99 LOCAL_USER_DECL;
100
101 static void play_dialtone(struct ast_channel *chan, char *mailbox)
102 {
103         const struct tone_zone_sound *ts = NULL;
104         if(ast_app_has_voicemail(mailbox, NULL))
105                 ts = ast_get_indication_tone(chan->zone, "dialrecall");
106         else
107                 ts = ast_get_indication_tone(chan->zone, "dial");
108         if (ts)
109                 ast_playtones_start(chan, 0, ts->data, 0);
110         else
111                 ast_tonepair_start(chan, 350, 440, 0, 0);
112 }
113
114 static int disa_exec(struct ast_channel *chan, void *data)
115 {
116         int i,j,k,x,did_ignore;
117         int firstdigittimeout = 20000;
118         int digittimeout = 10000;
119         struct localuser *u;
120         char *tmp, arg2[256]="",exten[AST_MAX_EXTENSION],acctcode[20]="";
121         char *ourcontext,*ourcallerid,ourcidname[256],ourcidnum[256],*mailbox;
122         struct ast_frame *f;
123         struct timeval lastdigittime;
124         int res;
125         time_t rstart;
126         FILE *fp;
127         char *stringp=NULL;
128
129         if (!data || ast_strlen_zero(data)) {
130                 ast_log(LOG_WARNING, "disa requires an argument (passcode/passcode file)\n");
131                 return -1;
132         }
133
134         LOCAL_USER_ADD(u);
135         
136         if (chan->pbx) {
137                 firstdigittimeout = chan->pbx->rtimeout*1000;
138                 digittimeout = chan->pbx->dtimeout*1000;
139         }
140         
141         if (ast_set_write_format(chan,AST_FORMAT_ULAW)) {
142                 ast_log(LOG_WARNING, "Unable to set write format to Mu-law on %s\n",chan->name);
143                 LOCAL_USER_REMOVE(u);
144                 return -1;
145         }
146         if (ast_set_read_format(chan,AST_FORMAT_ULAW)) {
147                 ast_log(LOG_WARNING, "Unable to set read format to Mu-law on %s\n",chan->name);
148                 LOCAL_USER_REMOVE(u);
149                 return -1;
150         }
151         
152         ast_log(LOG_DEBUG, "Digittimeout: %d\n", digittimeout);
153         ast_log(LOG_DEBUG, "Responsetimeout: %d\n", firstdigittimeout);
154
155         tmp = ast_strdupa(data);
156         if (!tmp) {
157                 ast_log(LOG_ERROR, "Out of memory\n");
158                 LOCAL_USER_REMOVE(u);
159                 return -1;
160         }       
161
162         stringp=tmp;
163         strsep(&stringp, "|");
164         ourcontext = strsep(&stringp, "|");
165         /* if context specified, save 2nd arg and parse third */
166         if (ourcontext) {
167                 strncpy(arg2,ourcontext, sizeof(arg2) - 1);
168                 ourcallerid = strsep(&stringp,"|");
169         }
170           /* if context not specified, use "disa" */
171         else {
172                 arg2[0] = 0;
173                 ourcallerid = NULL;
174                 ourcontext = "disa";
175         }
176         mailbox = strsep(&stringp, "|");
177         if (!mailbox)
178                 mailbox = "";
179         ast_log(LOG_DEBUG, "Mailbox: %s\n",mailbox);
180         
181         if (chan->_state != AST_STATE_UP) {
182                 /* answer */
183                 ast_answer(chan);
184         }
185         i = k = x = 0; /* k is 0 for pswd entry, 1 for ext entry */
186         did_ignore = 0;
187         exten[0] = 0;
188         acctcode[0] = 0;
189         /* can we access DISA without password? */ 
190
191         ast_log(LOG_DEBUG, "Context: %s\n",ourcontext);
192
193         if (!strcasecmp(tmp, "no-password")) {
194                 k |= 1; /* We have the password */
195                 ast_log(LOG_DEBUG, "DISA no-password login success\n");
196         }
197         lastdigittime = ast_tvnow();
198
199         play_dialtone(chan, mailbox);
200
201         for (;;) {
202                   /* if outa time, give em reorder */
203                 if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) > 
204                     ((k&2) ? digittimeout : firstdigittimeout))
205                 {
206                         ast_log(LOG_DEBUG,"DISA %s entry timeout on chan %s\n",
207                                 ((k&1) ? "extension" : "password"),chan->name);
208                         break;
209                 }
210                 if ((res = ast_waitfor(chan, -1) < 0)) {
211                         ast_log(LOG_DEBUG, "Waitfor returned %d\n", res);
212                         continue;
213                 }
214                         
215                 f = ast_read(chan);
216                 if (f == NULL) 
217                 {
218                         LOCAL_USER_REMOVE(u);
219                         return -1;
220                 }
221                 if ((f->frametype == AST_FRAME_CONTROL) &&
222                     (f->subclass == AST_CONTROL_HANGUP))
223                 {
224                         ast_frfree(f);
225                         LOCAL_USER_REMOVE(u);
226                         return -1;
227                 }
228                 if (f->frametype == AST_FRAME_VOICE) {
229                         ast_frfree(f);
230                         continue;
231                 }
232                   /* if not DTMF, just do it again */
233                 if (f->frametype != AST_FRAME_DTMF) 
234                 {
235                         ast_frfree(f);
236                         continue;
237                 }
238
239                 j = f->subclass;  /* save digit */
240                 ast_frfree(f);
241                 if (i == 0) 
242                 {
243                         k|=2; /* We have the first digit */ 
244                         ast_playtones_stop(chan);
245                 }
246                 lastdigittime = ast_tvnow();
247                   /* got a DTMF tone */
248                 if (i < AST_MAX_EXTENSION) /* if still valid number of digits */
249                 {
250                         if (!(k&1)) /* if in password state */
251                         {
252                                 if (j == '#') /* end of password */
253                                 {
254                                           /* see if this is an integer */
255                                         if (sscanf(tmp,"%d",&j) < 1)
256                                            { /* nope, it must be a filename */
257                                                 fp = fopen(tmp,"r");
258                                                 if (!fp)
259                                                    {
260                                                         ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",tmp,chan->name);
261                                                         LOCAL_USER_REMOVE(u);
262                                                         return -1;
263                                                    }
264                                                 tmp[0] = 0;
265                                                 while(fgets(tmp,sizeof(tmp) - 1,fp))
266                                                    {
267                                                         char *stringp=NULL,*stringp2;
268                                                         if (!tmp[0]) continue;
269                                                         if (tmp[strlen(tmp) - 1] == '\n') 
270                                                                 tmp[strlen(tmp) - 1] = 0;
271                                                         if (!tmp[0]) continue;
272                                                           /* skip comments */
273                                                         if (tmp[0] == '#') continue;
274                                                         if (tmp[0] == ';') continue;
275                                                         stringp=tmp;
276                                                         strsep(&stringp, "|");
277                                                         stringp2=strsep(&stringp, "|");
278                                                         if (stringp2) {
279                                                                 ourcontext=stringp2;
280                                                                 stringp2=strsep(&stringp, "|");
281                                                                 if (stringp2) ourcallerid=stringp2;
282                                                         }
283                                                         mailbox = strsep(&stringp, "|");
284                                                         if (!mailbox)
285                                                                 mailbox = "";
286                                                         ast_log(LOG_DEBUG, "Mailbox: %s\n",mailbox);
287
288                                                           /* password must be in valid format (numeric) */
289                                                         if (sscanf(tmp,"%d",&j) < 1) continue;
290                                                           /* if we got it */
291                                                         if (!strcmp(exten,tmp)) break;
292                                                    }
293                                                 fclose(fp);
294                                            }
295                                           /* compare the two */
296                                         if (strcmp(exten,tmp))
297                                         {
298                                                 ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",chan->name,exten);
299                                                 goto reorder;
300
301                                         }
302                                          /* password good, set to dial state */
303                                         ast_log(LOG_DEBUG,"DISA on chan %s password is good\n",chan->name);
304                                         play_dialtone(chan, mailbox);
305
306                                         k|=1; /* In number mode */
307                                         i = 0;  /* re-set buffer pointer */
308                                         exten[sizeof(acctcode)] = 0;
309                                         strncpy(acctcode,exten, sizeof(acctcode) - 1);
310                                         exten[0] = 0;
311                                         ast_log(LOG_DEBUG,"Successful DISA log-in on chan %s\n",chan->name);
312                                         continue;
313                                 }
314                         }
315
316                         exten[i++] = j;  /* save digit */
317                         exten[i] = 0;
318                         if (!(k&1)) continue; /* if getting password, continue doing it */
319                           /* if this exists */
320
321                         if (ast_ignore_pattern(ourcontext, exten)) {
322                                 play_dialtone(chan, "");
323                                 did_ignore = 1;
324                         } else
325                                 if (did_ignore) {
326                                         ast_playtones_stop(chan);
327                                         did_ignore = 0;
328                                 }
329
330                           /* if can do some more, do it */
331                         if (!ast_matchmore_extension(chan,ourcontext,exten,1, chan->cid.cid_num)) {
332                                 break;
333                         }
334                 }
335         }
336
337         if (k == 3) {
338                 int recheck = 0;
339
340                 if (!ast_exists_extension(chan, ourcontext, exten, 1, chan->cid.cid_num)) {
341                         pbx_builtin_setvar_helper(chan, "INVALID_EXTEN", exten);
342                         exten[0] = 'i';
343                         exten[1] = '\0';
344                         recheck = 1;
345                 }
346                 if (!recheck || ast_exists_extension(chan, ourcontext, exten, 1, chan->cid.cid_num)) {
347                         ast_playtones_stop(chan);
348                         /* We're authenticated and have a target extension */
349                         if (ourcallerid && *ourcallerid)
350                         {
351                                 ast_callerid_split(ourcallerid, ourcidname, sizeof(ourcidname), ourcidnum, sizeof(ourcidnum));
352                                 ast_set_callerid(chan, ourcidnum, ourcidname, ourcidnum);
353                         }
354
355                         if (!ast_strlen_zero(acctcode))
356                                 strncpy(chan->accountcode, acctcode, sizeof(chan->accountcode) - 1);
357
358                         ast_cdr_reset(chan->cdr, AST_CDR_FLAG_POSTED);
359                         ast_explicit_goto(chan, ourcontext, exten, 1);
360                         LOCAL_USER_REMOVE(u);
361                         return 0;
362                 }
363         }
364
365         /* Received invalid, but no "i" extension exists in the given context */
366
367 reorder:
368
369         ast_indicate(chan,AST_CONTROL_CONGESTION);
370         /* something is invalid, give em reorder for several seconds */
371         time(&rstart);
372         while(time(NULL) < rstart + 10)
373         {
374                 if (ast_waitfor(chan, -1) < 0)
375                         break;
376                 f = ast_read(chan);
377                 if (!f)
378                         break;
379                 ast_frfree(f);
380         }
381         ast_playtones_stop(chan);
382         LOCAL_USER_REMOVE(u);
383         return -1;
384 }
385
386 int unload_module(void)
387 {
388         int res;
389
390         res = ast_unregister_application(app);
391
392         STANDARD_HANGUP_LOCALUSERS;
393
394         return res;
395 }
396
397 int load_module(void)
398 {
399         return ast_register_application(app, disa_exec, synopsis, descrip);
400 }
401
402 char *description(void)
403 {
404         return tdesc;
405 }
406
407 int usecount(void)
408 {
409         int res;
410         STANDARD_USECOUNT(res);
411         return res;
412 }
413
414 char *key(void)
415 {
416         return ASTERISK_GPL_KEY;
417 }