Allow a Skinny device to monitor a dialplan hint (w00t!).
[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 "asterisk.h"
30
31 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
32
33 #include <string.h>
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <math.h>
37 #include <sys/time.h>
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 #include "asterisk/stringfields.h"
51 #include "asterisk/options.h"
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 allows 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. Also, if you set the 5th argument\n"
97         "to 'NOANSWER', the DISA application will not answer initially.\n";
98
99
100 static void play_dialtone(struct ast_channel *chan, char *mailbox)
101 {
102         const struct ind_tone_zone_sound *ts = NULL;
103         if(ast_app_has_voicemail(mailbox, NULL))
104                 ts = ast_get_indication_tone(chan->zone, "dialrecall");
105         else
106                 ts = ast_get_indication_tone(chan->zone, "dial");
107         if (ts)
108                 ast_playtones_start(chan, 0, ts->data, 0);
109         else
110                 ast_tonepair_start(chan, 350, 440, 0, 0);
111 }
112
113 static int disa_exec(struct ast_channel *chan, void *data)
114 {
115         int i,j,k,x,did_ignore,special_noanswer;
116         int firstdigittimeout = 20000;
117         int digittimeout = 10000;
118         struct ast_module_user *u;
119         char *tmp, exten[AST_MAX_EXTENSION],acctcode[20]="";
120         char pwline[256];
121         char ourcidname[256],ourcidnum[256];
122         struct ast_frame *f;
123         struct timeval lastdigittime;
124         int res;
125         time_t rstart;
126         FILE *fp;
127         AST_DECLARE_APP_ARGS(args,
128                 AST_APP_ARG(passcode);
129                 AST_APP_ARG(context);
130                 AST_APP_ARG(cid);
131                 AST_APP_ARG(mailbox);
132                 AST_APP_ARG(noanswer);
133         );
134
135         if (ast_strlen_zero(data)) {
136                 ast_log(LOG_WARNING, "DISA requires an argument (passcode/passcode file)\n");
137                 return -1;
138         }
139
140         u = ast_module_user_add(chan);
141         
142         if (chan->pbx) {
143                 firstdigittimeout = chan->pbx->rtimeout*1000;
144                 digittimeout = chan->pbx->dtimeout*1000;
145         }
146         
147         if (ast_set_write_format(chan,AST_FORMAT_ULAW)) {
148                 ast_log(LOG_WARNING, "Unable to set write format to Mu-law on %s\n", chan->name);
149                 ast_module_user_remove(u);
150                 return -1;
151         }
152         if (ast_set_read_format(chan,AST_FORMAT_ULAW)) {
153                 ast_log(LOG_WARNING, "Unable to set read format to Mu-law on %s\n", chan->name);
154                 ast_module_user_remove(u);
155                 return -1;
156         }
157         
158         if (option_debug) {
159                 ast_log(LOG_DEBUG, "Digittimeout: %d\n", digittimeout);
160                 ast_log(LOG_DEBUG, "Responsetimeout: %d\n", firstdigittimeout);
161         }
162
163         tmp = ast_strdupa(data);
164
165         AST_STANDARD_APP_ARGS(args, tmp);
166
167         if (ast_strlen_zero(args.context)) 
168                 args.context = "disa";  
169         if (ast_strlen_zero(args.mailbox))
170                 args.mailbox = "";
171
172         if (option_debug)
173                 ast_log(LOG_DEBUG, "Mailbox: %s\n",args.mailbox);
174
175         special_noanswer = 0;
176         if ((!args.noanswer) || strcmp(args.noanswer,"NOANSWER"))
177         {
178                 if (chan->_state != AST_STATE_UP) {
179                         /* answer */
180                         ast_answer(chan);
181                 }
182         } else special_noanswer = 1;
183         i = k = x = 0; /* k is 0 for pswd entry, 1 for ext entry */
184         did_ignore = 0;
185         exten[0] = 0;
186         acctcode[0] = 0;
187         /* can we access DISA without password? */ 
188
189         if (option_debug)
190                 ast_log(LOG_DEBUG, "Context: %s\n",args.context);
191
192         if (!strcasecmp(args.passcode, "no-password")) {
193                 k |= 1; /* We have the password */
194                 if (option_debug)
195                         ast_log(LOG_DEBUG, "DISA no-password login success\n");
196         }
197         lastdigittime = ast_tvnow();
198
199         play_dialtone(chan, args.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                         if (option_debug)
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                         if (option_debug)
212                                 ast_log(LOG_DEBUG, "Waitfor returned %d\n", res);
213                         continue;
214                 }
215                         
216                 f = ast_read(chan);
217                 if (f == NULL) {
218                         ast_module_user_remove(u);
219                         return -1;
220                 }
221                 if ((f->frametype == AST_FRAME_CONTROL) &&
222                     (f->subclass == AST_CONTROL_HANGUP)) {
223                         ast_frfree(f);
224                         ast_module_user_remove(u);
225                         return -1;
226                 }
227                 if (f->frametype == AST_FRAME_VOICE) {
228                         ast_frfree(f);
229                         continue;
230                 }
231
232                 /* if not DTMF, just do it again */
233                 if (f->frametype != AST_FRAME_DTMF) {
234                         ast_frfree(f);
235                         continue;
236                 }
237
238                 j = f->subclass;  /* save digit */
239                 ast_frfree(f);
240                 if (i == 0) {
241                         k|=2; /* We have the first digit */ 
242                         ast_playtones_stop(chan);
243                 }
244                 lastdigittime = ast_tvnow();
245                   /* got a DTMF tone */
246                 if (i < AST_MAX_EXTENSION) { /* if still valid number of digits */
247                         if (!(k&1)) { /* if in password state */
248                                 if (j == '#') { /* end of password */
249                                           /* see if this is an integer */
250                                         if (sscanf(args.passcode,"%d",&j) < 1) { /* nope, it must be a filename */
251                                                 fp = fopen(args.passcode,"r");
252                                                 if (!fp) {
253                                                         ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",args.passcode,chan->name);
254                                                         ast_module_user_remove(u);
255                                                         return -1;
256                                                 }
257                                                 pwline[0] = 0;
258                                                 while(fgets(pwline,sizeof(pwline) - 1,fp)) {
259                                                         if (!pwline[0])
260                                                                 continue;
261                                                         if (pwline[strlen(pwline) - 1] == '\n') 
262                                                                 pwline[strlen(pwline) - 1] = 0;
263                                                         if (!pwline[0])
264                                                                 continue;
265                                                          /* skip comments */
266                                                         if (pwline[0] == '#')
267                                                                 continue;
268                                                         if (pwline[0] == ';')
269                                                                 continue;
270
271                                                         AST_STANDARD_APP_ARGS(args, pwline);
272                         
273                                                         if (option_debug)
274                                                                 ast_log(LOG_DEBUG, "Mailbox: %s\n",args.mailbox);
275
276                                                         /* password must be in valid format (numeric) */
277                                                         if (sscanf(args.passcode,"%d", &j) < 1)
278                                                                 continue;
279                                                          /* if we got it */
280                                                         if (!strcmp(exten,args.passcode)) {
281                                                                 if (ast_strlen_zero(args.context))
282                                                                         args.context = "disa";
283                                                                 if (ast_strlen_zero(args.mailbox))
284                                                                         args.mailbox = "";
285                                                                 break;
286                                                         }
287                                                 }
288                                                 fclose(fp);
289                                         }
290                                         /* compare the two */
291                                         if (strcmp(exten,args.passcode)) {
292                                                 ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",chan->name,exten);
293                                                 goto reorder;
294
295                                         }
296                                          /* password good, set to dial state */
297                                         if (option_debug)
298                                                 ast_log(LOG_DEBUG,"DISA on chan %s password is good\n",chan->name);
299                                         play_dialtone(chan, args.mailbox);
300
301                                         k|=1; /* In number mode */
302                                         i = 0;  /* re-set buffer pointer */
303                                         exten[sizeof(acctcode)] = 0;
304                                         ast_copy_string(acctcode, exten, sizeof(acctcode));
305                                         exten[0] = 0;
306                                         if (option_debug)
307                                                 ast_log(LOG_DEBUG,"Successful DISA log-in on chan %s\n", chan->name);
308                                         continue;
309                                 }
310                         }
311
312                         exten[i++] = j;  /* save digit */
313                         exten[i] = 0;
314                         if (!(k&1))
315                                 continue; /* if getting password, continue doing it */
316                         /* if this exists */
317
318                         if (ast_ignore_pattern(args.context, exten)) {
319                                 play_dialtone(chan, "");
320                                 did_ignore = 1;
321                         } else
322                                 if (did_ignore) {
323                                         ast_playtones_stop(chan);
324                                         did_ignore = 0;
325                                 }
326
327                         /* if can do some more, do it */
328                         if (!ast_matchmore_extension(chan,args.context,exten,1, chan->cid.cid_num)) {
329                                 break;
330                         }
331                 }
332         }
333
334         if (k == 3) {
335                 int recheck = 0;
336                 struct ast_flags flags = { AST_CDR_FLAG_POSTED };
337
338                 if (!ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) {
339                         pbx_builtin_setvar_helper(chan, "INVALID_EXTEN", exten);
340                         exten[0] = 'i';
341                         exten[1] = '\0';
342                         recheck = 1;
343                 }
344                 if (!recheck || ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) {
345                         ast_playtones_stop(chan);
346                         /* We're authenticated and have a target extension */
347                         if (!ast_strlen_zero(args.cid)) {
348                                 ast_callerid_split(args.cid, ourcidname, sizeof(ourcidname), ourcidnum, sizeof(ourcidnum));
349                                 ast_set_callerid(chan, ourcidnum, ourcidname, ourcidnum);
350                         }
351
352                         if (!ast_strlen_zero(acctcode))
353                                 ast_string_field_set(chan, accountcode, acctcode);
354
355                         if (special_noanswer) flags.flags = 0;
356                         ast_cdr_reset(chan->cdr, &flags);
357                         ast_explicit_goto(chan, args.context, exten, 1);
358                         ast_module_user_remove(u);
359                         return 0;
360                 }
361         }
362
363         /* Received invalid, but no "i" extension exists in the given context */
364
365 reorder:
366
367         ast_indicate(chan,AST_CONTROL_CONGESTION);
368         /* something is invalid, give em reorder for several seconds */
369         time(&rstart);
370         while(time(NULL) < rstart + 10) {
371                 if (ast_waitfor(chan, -1) < 0)
372                         break;
373                 f = ast_read(chan);
374                 if (!f)
375                         break;
376                 ast_frfree(f);
377         }
378         ast_playtones_stop(chan);
379         ast_module_user_remove(u);
380         return -1;
381 }
382
383 static int unload_module(void)
384 {
385         int res;
386
387         res = ast_unregister_application(app);
388
389         ast_module_user_hangup_all();
390
391         return res;
392 }
393
394 static int load_module(void)
395 {
396         return ast_register_application(app, disa_exec, synopsis, descrip);
397 }
398
399 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DISA (Direct Inward System Access) Application");