8473f1c437bab2ef4aa60efcab4d9baf46a9f0d4
[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>[|<cid>[|mailbox[|options]]]]) or\n"
59 "DISA(<filename>[||||options])\n"
60 "The DISA, Direct Inward System Access, application allows someone from \n"
61 "outside the telephone switch (PBX) to obtain an \"internal\" system \n"
62 "dialtone and to place calls from it as if they were placing a call from \n"
63 "within the switch.\n"
64 "DISA plays a dialtone. The user enters their numeric passcode, followed by\n"
65 "the pound sign (#). If the passcode is correct, the user is then given\n"
66 "system dialtone within <context> on which a call may be placed. If the user\n"
67 "enters an invalid extension and extension \"i\" exists in the specified\n"
68 "context, it will be used.\n"
69 "\n"
70 "If you need to present a DISA dialtone without entering a password, simply\n"
71 "set <passcode> to \"no-password\".\n"
72 "\n"
73 "Be aware that using this may compromise the security of your PBX.\n"
74 "\n"
75 "The arguments to this application (in extensions.conf) allow either\n"
76 "specification of a single global passcode (that everyone uses), or\n"
77 "individual passcodes contained in a file.\n"
78 "\n"
79 "The file that contains the passcodes (if used) allows a complete\n"
80 "specification of all of the same arguments available on the command\n"
81 "line, with the sole exception of the options. The file may contain blank\n"
82 "lines, or comments starting with \"#\" or \";\".\n"
83 "\n"
84 "<context> specifies the dialplan context in which the user-entered extension\n"
85 "will be matched. If no context is specified, the DISA application defaults\n"
86 "the context to \"disa\". Presumably a normal system will have a special\n"
87 "context set up for DISA use with some or a lot of restrictions.\n"
88 "\n"
89 "<cid> specifies a new (different) callerid to be used for this call.\n"
90 "\n"
91 "<mailbox[@context]> will cause a stutter-dialtone (indication \"dialrecall\")\n"
92 "to be used, if the specified mailbox contains any new messages.\n"
93 "\n"
94 "The following options are available:\n"
95 "  n - the DISA application will not answer initially.\n"
96 "  p - the extension entered will be considered complete when a '#' is entered.\n";
97
98 enum {
99         NOANSWER_FLAG = (1 << 0),
100         POUND_TO_END_FLAG = (1 << 1),
101 } option_flags;
102
103 AST_APP_OPTIONS(app_opts, {
104         AST_APP_OPTION('n', NOANSWER_FLAG),
105         AST_APP_OPTION('p', POUND_TO_END_FLAG),
106 });
107
108 static void play_dialtone(struct ast_channel *chan, char *mailbox)
109 {
110         const struct ind_tone_zone_sound *ts = NULL;
111         if(ast_app_has_voicemail(mailbox, NULL))
112                 ts = ast_get_indication_tone(chan->zone, "dialrecall");
113         else
114                 ts = ast_get_indication_tone(chan->zone, "dial");
115         if (ts)
116                 ast_playtones_start(chan, 0, ts->data, 0);
117         else
118                 ast_tonepair_start(chan, 350, 440, 0, 0);
119 }
120
121 static int disa_exec(struct ast_channel *chan, void *data)
122 {
123         int i,j,k,x,did_ignore,special_noanswer;
124         int firstdigittimeout = 20000;
125         int digittimeout = 10000;
126         struct ast_module_user *u;
127         struct ast_flags flags;
128         char *tmp, exten[AST_MAX_EXTENSION],acctcode[20]="";
129         char pwline[256];
130         char ourcidname[256],ourcidnum[256];
131         struct ast_frame *f;
132         struct timeval lastdigittime;
133         int res;
134         time_t rstart;
135         FILE *fp;
136         AST_DECLARE_APP_ARGS(args,
137                 AST_APP_ARG(passcode);
138                 AST_APP_ARG(context);
139                 AST_APP_ARG(cid);
140                 AST_APP_ARG(mailbox);
141                 AST_APP_ARG(options);
142         );
143
144         if (ast_strlen_zero(data)) {
145                 ast_log(LOG_WARNING, "DISA requires an argument (passcode/passcode file)\n");
146                 return -1;
147         }
148
149         u = ast_module_user_add(chan);
150         
151         if (chan->pbx) {
152                 firstdigittimeout = chan->pbx->rtimeout*1000;
153                 digittimeout = chan->pbx->dtimeout*1000;
154         }
155         
156         if (ast_set_write_format(chan,AST_FORMAT_ULAW)) {
157                 ast_log(LOG_WARNING, "Unable to set write format to Mu-law on %s\n", chan->name);
158                 ast_module_user_remove(u);
159                 return -1;
160         }
161         if (ast_set_read_format(chan,AST_FORMAT_ULAW)) {
162                 ast_log(LOG_WARNING, "Unable to set read format to Mu-law on %s\n", chan->name);
163                 ast_module_user_remove(u);
164                 return -1;
165         }
166         
167         if (option_debug) {
168                 ast_log(LOG_DEBUG, "Digittimeout: %d\n", digittimeout);
169                 ast_log(LOG_DEBUG, "Responsetimeout: %d\n", firstdigittimeout);
170         }
171
172         tmp = ast_strdupa(data);
173
174         AST_STANDARD_APP_ARGS(args, tmp);
175
176         if (ast_strlen_zero(args.context)) 
177                 args.context = "disa";  
178         if (ast_strlen_zero(args.mailbox))
179                 args.mailbox = "";
180         if (!ast_strlen_zero(args.options))
181                 ast_app_parse_options(app_opts, &flags, NULL, args.options);
182
183         if (option_debug)
184                 ast_log(LOG_DEBUG, "Mailbox: %s\n",args.mailbox);
185
186         special_noanswer = 0;
187         if (ast_test_flag(&flags, NOANSWER_FLAG)) {
188                 if (chan->_state != AST_STATE_UP) {
189                         /* answer */
190                         ast_answer(chan);
191                 }
192         } else special_noanswer = 1;
193         i = k = x = 0; /* k is 0 for pswd entry, 1 for ext entry */
194         did_ignore = 0;
195         exten[0] = 0;
196         acctcode[0] = 0;
197         /* can we access DISA without password? */ 
198
199         if (option_debug)
200                 ast_log(LOG_DEBUG, "Context: %s\n",args.context);
201
202         if (!strcasecmp(args.passcode, "no-password")) {
203                 k |= 1; /* We have the password */
204                 if (option_debug)
205                         ast_log(LOG_DEBUG, "DISA no-password login success\n");
206         }
207         lastdigittime = ast_tvnow();
208
209         play_dialtone(chan, args.mailbox);
210
211         for (;;) {
212                   /* if outa time, give em reorder */
213                 if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) > 
214                     ((k&2) ? digittimeout : firstdigittimeout)) {
215                         if (option_debug)
216                                 ast_log(LOG_DEBUG,"DISA %s entry timeout on chan %s\n",
217                                         ((k&1) ? "extension" : "password"),chan->name);
218                         break;
219                 }
220                 if ((res = ast_waitfor(chan, -1) < 0)) {
221                         if (option_debug)
222                                 ast_log(LOG_DEBUG, "Waitfor returned %d\n", res);
223                         continue;
224                 }
225                         
226                 f = ast_read(chan);
227                 if (f == NULL) {
228                         ast_module_user_remove(u);
229                         return -1;
230                 }
231                 if ((f->frametype == AST_FRAME_CONTROL) &&
232                     (f->subclass == AST_CONTROL_HANGUP)) {
233                         ast_frfree(f);
234                         ast_module_user_remove(u);
235                         return -1;
236                 }
237                 if (f->frametype == AST_FRAME_VOICE) {
238                         ast_frfree(f);
239                         continue;
240                 }
241
242                 /* if not DTMF, just do it again */
243                 if (f->frametype != AST_FRAME_DTMF) {
244                         ast_frfree(f);
245                         continue;
246                 }
247
248                 j = f->subclass;  /* save digit */
249                 ast_frfree(f);
250                 if (i == 0) {
251                         k|=2; /* We have the first digit */ 
252                         ast_playtones_stop(chan);
253                 }
254                 lastdigittime = ast_tvnow();
255                   /* got a DTMF tone */
256                 if (i < AST_MAX_EXTENSION) { /* if still valid number of digits */
257                         if (!(k&1)) { /* if in password state */
258                                 if (j == '#') { /* end of password */
259                                           /* see if this is an integer */
260                                         if (sscanf(args.passcode,"%d",&j) < 1) { /* nope, it must be a filename */
261                                                 fp = fopen(args.passcode,"r");
262                                                 if (!fp) {
263                                                         ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",args.passcode,chan->name);
264                                                         ast_module_user_remove(u);
265                                                         return -1;
266                                                 }
267                                                 pwline[0] = 0;
268                                                 while(fgets(pwline,sizeof(pwline) - 1,fp)) {
269                                                         if (!pwline[0])
270                                                                 continue;
271                                                         if (pwline[strlen(pwline) - 1] == '\n') 
272                                                                 pwline[strlen(pwline) - 1] = 0;
273                                                         if (!pwline[0])
274                                                                 continue;
275                                                          /* skip comments */
276                                                         if (pwline[0] == '#')
277                                                                 continue;
278                                                         if (pwline[0] == ';')
279                                                                 continue;
280
281                                                         AST_STANDARD_APP_ARGS(args, pwline);
282                         
283                                                         if (option_debug)
284                                                                 ast_log(LOG_DEBUG, "Mailbox: %s\n",args.mailbox);
285
286                                                         /* password must be in valid format (numeric) */
287                                                         if (sscanf(args.passcode,"%d", &j) < 1)
288                                                                 continue;
289                                                          /* if we got it */
290                                                         if (!strcmp(exten,args.passcode)) {
291                                                                 if (ast_strlen_zero(args.context))
292                                                                         args.context = "disa";
293                                                                 if (ast_strlen_zero(args.mailbox))
294                                                                         args.mailbox = "";
295                                                                 break;
296                                                         }
297                                                 }
298                                                 fclose(fp);
299                                         }
300                                         /* compare the two */
301                                         if (strcmp(exten,args.passcode)) {
302                                                 ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",chan->name,exten);
303                                                 goto reorder;
304
305                                         }
306                                          /* password good, set to dial state */
307                                         if (option_debug)
308                                                 ast_log(LOG_DEBUG,"DISA on chan %s password is good\n",chan->name);
309                                         play_dialtone(chan, args.mailbox);
310
311                                         k|=1; /* In number mode */
312                                         i = 0;  /* re-set buffer pointer */
313                                         exten[sizeof(acctcode)] = 0;
314                                         ast_copy_string(acctcode, exten, sizeof(acctcode));
315                                         exten[0] = 0;
316                                         if (option_debug)
317                                                 ast_log(LOG_DEBUG,"Successful DISA log-in on chan %s\n", chan->name);
318                                         continue;
319                                 }
320                         }
321
322                         exten[i++] = j;  /* save digit */
323                         exten[i] = 0;
324                         if (!(k&1))
325                                 continue; /* if getting password, continue doing it */
326                         /* if this exists */
327
328                         /* user wants end of number, remove # */
329                         if (ast_test_flag(&flags, POUND_TO_END_FLAG) && j == '#') {
330                                 exten[--i] = 0;
331                                 break;
332                         }
333
334                         if (ast_ignore_pattern(args.context, exten)) {
335                                 play_dialtone(chan, "");
336                                 did_ignore = 1;
337                         } else
338                                 if (did_ignore) {
339                                         ast_playtones_stop(chan);
340                                         did_ignore = 0;
341                                 }
342
343                         /* if can do some more, do it */
344                         if (!ast_matchmore_extension(chan,args.context,exten,1, chan->cid.cid_num)) {
345                                 break;
346                         }
347                 }
348         }
349
350         if (k == 3) {
351                 int recheck = 0;
352                 struct ast_flags flags = { AST_CDR_FLAG_POSTED };
353
354                 if (!ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) {
355                         pbx_builtin_setvar_helper(chan, "INVALID_EXTEN", exten);
356                         exten[0] = 'i';
357                         exten[1] = '\0';
358                         recheck = 1;
359                 }
360                 if (!recheck || ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) {
361                         ast_playtones_stop(chan);
362                         /* We're authenticated and have a target extension */
363                         if (!ast_strlen_zero(args.cid)) {
364                                 ast_callerid_split(args.cid, ourcidname, sizeof(ourcidname), ourcidnum, sizeof(ourcidnum));
365                                 ast_set_callerid(chan, ourcidnum, ourcidname, ourcidnum);
366                         }
367
368                         if (!ast_strlen_zero(acctcode))
369                                 ast_string_field_set(chan, accountcode, acctcode);
370
371                         if (special_noanswer) flags.flags = 0;
372                         ast_cdr_reset(chan->cdr, &flags);
373                         ast_explicit_goto(chan, args.context, exten, 1);
374                         ast_module_user_remove(u);
375                         return 0;
376                 }
377         }
378
379         /* Received invalid, but no "i" extension exists in the given context */
380
381 reorder:
382
383         ast_indicate(chan,AST_CONTROL_CONGESTION);
384         /* something is invalid, give em reorder for several seconds */
385         time(&rstart);
386         while(time(NULL) < rstart + 10) {
387                 if (ast_waitfor(chan, -1) < 0)
388                         break;
389                 f = ast_read(chan);
390                 if (!f)
391                         break;
392                 ast_frfree(f);
393         }
394         ast_playtones_stop(chan);
395         ast_module_user_remove(u);
396         return -1;
397 }
398
399 static int unload_module(void)
400 {
401         int res;
402
403         res = ast_unregister_application(app);
404
405         ast_module_user_hangup_all();
406
407         return res;
408 }
409
410 static int load_module(void)
411 {
412         return ast_register_application(app, disa_exec, synopsis, descrip);
413 }
414
415 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DISA (Direct Inward System Access) Application");