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