3ff11005380acaa2e1e9dc59042611d751e89021
[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 <math.h>
34 #include <sys/time.h>
35
36 #include "asterisk/lock.h"
37 #include "asterisk/file.h"
38 #include "asterisk/channel.h"
39 #include "asterisk/app.h"
40 #include "asterisk/indications.h"
41 #include "asterisk/pbx.h"
42 #include "asterisk/module.h"
43 #include "asterisk/translate.h"
44 #include "asterisk/ulaw.h"
45 #include "asterisk/callerid.h"
46 #include "asterisk/stringfields.h"
47
48 static char *app = "DISA";
49
50 static char *synopsis = "DISA (Direct Inward System Access)";
51
52 static char *descrip =
53 "DISA(<numeric passcode>[,<context>[,<cid>[,mailbox[,options]]]]) or\n"
54 "DISA(<filename>[,,,,options])\n"
55 "The DISA, Direct Inward System Access, application allows someone from \n"
56 "outside the telephone switch (PBX) to obtain an \"internal\" system \n"
57 "dialtone and to place calls from it as if they were placing a call from \n"
58 "within the switch.\n"
59 "DISA plays a dialtone. The user enters their numeric passcode, followed by\n"
60 "the pound sign (#). If the passcode is correct, the user is then given\n"
61 "system dialtone within <context> on which a call may be placed. If the user\n"
62 "enters an invalid extension and extension \"i\" exists in the specified\n"
63 "context, it will be used.\n"
64 "\n"
65 "If you need to present a DISA dialtone without entering a password, simply\n"
66 "set <passcode> to \"no-password\".\n"
67 "\n"
68 "Be aware that using this may compromise the security of your PBX.\n"
69 "\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.\n"
73 "\n"
74 "The file that contains the passcodes (if used) allows a complete\n"
75 "specification of all of the same arguments available on the command\n"
76 "line, with the sole exception of the options. The file may contain blank\n"
77 "lines, or comments starting with \"#\" or \";\".\n"
78 "\n"
79 "<context> specifies the dialplan context in which the user-entered extension\n"
80 "will be matched. If no context is specified, the DISA application defaults\n"
81 "the context to \"disa\". Presumably a normal system will have a special\n"
82 "context set up for DISA use with some or a lot of restrictions.\n"
83 "\n"
84 "<cid> specifies a new (different) callerid to be used for this call.\n"
85 "\n"
86 "<mailbox[@context]> will cause a stutter-dialtone (indication \"dialrecall\")\n"
87 "to be used, if the specified mailbox contains any new messages.\n"
88 "\n"
89 "The following options are available:\n"
90 "  n - the DISA application will not answer initially.\n"
91 "  p - the extension entered will be considered complete when a '#' is entered.\n";
92
93 enum {
94         NOANSWER_FLAG = (1 << 0),
95         POUND_TO_END_FLAG = (1 << 1),
96 } option_flags;
97
98 AST_APP_OPTIONS(app_opts, {
99         AST_APP_OPTION('n', NOANSWER_FLAG),
100         AST_APP_OPTION('p', POUND_TO_END_FLAG),
101 });
102
103 static void play_dialtone(struct ast_channel *chan, char *mailbox)
104 {
105         const struct ind_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 = 0, j, k = 0, did_ignore = 0, special_noanswer = 0;
119         int firstdigittimeout = (chan->pbx ? chan->pbx->rtimeoutms : 20000);
120         int digittimeout = (chan->pbx ? chan->pbx->dtimeoutms : 10000);
121         struct ast_flags flags;
122         char *tmp, exten[AST_MAX_EXTENSION] = "", acctcode[20]="";
123         char pwline[256];
124         char ourcidname[256],ourcidnum[256];
125         struct ast_frame *f;
126         struct timeval lastdigittime;
127         int res;
128         FILE *fp;
129         AST_DECLARE_APP_ARGS(args,
130                 AST_APP_ARG(passcode);
131                 AST_APP_ARG(context);
132                 AST_APP_ARG(cid);
133                 AST_APP_ARG(mailbox);
134                 AST_APP_ARG(options);
135         );
136
137         if (ast_strlen_zero(data)) {
138                 ast_log(LOG_WARNING, "DISA requires an argument (passcode/passcode file)\n");
139                 return -1;
140         }
141
142         ast_debug(1, "Digittimeout: %d\n", digittimeout);
143         ast_debug(1, "Responsetimeout: %d\n", firstdigittimeout);
144
145         tmp = ast_strdupa(data);
146
147         AST_STANDARD_APP_ARGS(args, tmp);
148
149         if (ast_strlen_zero(args.context))
150                 args.context = "disa";
151         if (ast_strlen_zero(args.mailbox))
152                 args.mailbox = "";
153         if (!ast_strlen_zero(args.options))
154                 ast_app_parse_options(app_opts, &flags, NULL, args.options);
155
156         ast_debug(1, "Mailbox: %s\n",args.mailbox);
157
158         if (!ast_test_flag(&flags, NOANSWER_FLAG)) {
159                 if (chan->_state != AST_STATE_UP) {
160                         /* answer */
161                         ast_answer(chan);
162                 }
163         } else
164                 special_noanswer = 1;
165
166         ast_debug(1, "Context: %s\n",args.context);
167
168         if (!strcasecmp(args.passcode, "no-password")) {
169                 k |= 1; /* We have the password */
170                 ast_debug(1, "DISA no-password login success\n");
171         }
172
173         lastdigittime = ast_tvnow();
174
175         play_dialtone(chan, args.mailbox);
176
177         ast_set_flag(chan, AST_FLAG_END_DTMF_ONLY);
178
179         for (;;) {
180                   /* if outa time, give em reorder */
181                 if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) > ((k&2) ? digittimeout : firstdigittimeout)) {
182                         ast_debug(1,"DISA %s entry timeout on chan %s\n",
183                                 ((k&1) ? "extension" : "password"),chan->name);
184                         break;
185                 }
186
187                 if ((res = ast_waitfor(chan, -1) < 0)) {
188                         ast_debug(1, "Waitfor returned %d\n", res);
189                         continue;
190                 }
191
192                 if (!(f = ast_read(chan))) {
193                         ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
194                         return -1;
195                 }
196
197                 if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
198                         if (f->data.uint32)
199                                 chan->hangupcause = f->data.uint32;
200                         ast_frfree(f);
201                         ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
202                         return -1;
203                 }
204
205                 /* If the frame coming in is not DTMF, just drop it and continue */
206                 if (f->frametype != AST_FRAME_DTMF) {
207                         ast_frfree(f);
208                         continue;
209                 }
210
211                 j = f->subclass;  /* save digit */
212                 ast_frfree(f);
213
214                 if (!i) {
215                         k |= 2; /* We have the first digit */
216                         ast_playtones_stop(chan);
217                 }
218
219                 lastdigittime = ast_tvnow();
220
221                 /* got a DTMF tone */
222                 if (i < AST_MAX_EXTENSION) { /* if still valid number of digits */
223                         if (!(k&1)) { /* if in password state */
224                                 if (j == '#') { /* end of password */
225                                           /* see if this is an integer */
226                                         if (sscanf(args.passcode,"%d",&j) < 1) { /* nope, it must be a filename */
227                                                 fp = fopen(args.passcode,"r");
228                                                 if (!fp) {
229                                                         ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",args.passcode,chan->name);
230                                                         ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
231                                                         return -1;
232                                                 }
233                                                 pwline[0] = 0;
234                                                 while(fgets(pwline,sizeof(pwline) - 1,fp)) {
235                                                         if (!pwline[0])
236                                                                 continue;
237                                                         if (pwline[strlen(pwline) - 1] == '\n')
238                                                                 pwline[strlen(pwline) - 1] = 0;
239                                                         if (!pwline[0])
240                                                                 continue;
241                                                          /* skip comments */
242                                                         if (pwline[0] == '#')
243                                                                 continue;
244                                                         if (pwline[0] == ';')
245                                                                 continue;
246
247                                                         AST_STANDARD_APP_ARGS(args, pwline);
248
249                                                         ast_debug(1, "Mailbox: %s\n",args.mailbox);
250
251                                                         /* password must be in valid format (numeric) */
252                                                         if (sscanf(args.passcode,"%d", &j) < 1)
253                                                                 continue;
254                                                          /* if we got it */
255                                                         if (!strcmp(exten,args.passcode)) {
256                                                                 if (ast_strlen_zero(args.context))
257                                                                         args.context = "disa";
258                                                                 if (ast_strlen_zero(args.mailbox))
259                                                                         args.mailbox = "";
260                                                                 break;
261                                                         }
262                                                 }
263                                                 fclose(fp);
264                                         }
265                                         /* compare the two */
266                                         if (strcmp(exten,args.passcode)) {
267                                                 ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",chan->name,exten);
268                                                 goto reorder;
269
270                                         }
271                                          /* password good, set to dial state */
272                                         ast_debug(1,"DISA on chan %s password is good\n",chan->name);
273                                         play_dialtone(chan, args.mailbox);
274
275                                         k|=1; /* In number mode */
276                                         i = 0;  /* re-set buffer pointer */
277                                         exten[sizeof(acctcode)] = 0;
278                                         ast_copy_string(acctcode, exten, sizeof(acctcode));
279                                         exten[0] = 0;
280                                         ast_debug(1,"Successful DISA log-in on chan %s\n", chan->name);
281                                         continue;
282                                 }
283                         } else {
284                                 if (j == '#') { /* end of extension */
285                                         break;
286                                 }
287                         }
288
289                         exten[i++] = j;  /* save digit */
290                         exten[i] = 0;
291                         if (!(k&1))
292                                 continue; /* if getting password, continue doing it */
293                         /* if this exists */
294
295                         /* user wants end of number, remove # */
296                         if (ast_test_flag(&flags, POUND_TO_END_FLAG) && j == '#') {
297                                 exten[--i] = 0;
298                                 break;
299                         }
300
301                         if (ast_ignore_pattern(args.context, exten)) {
302                                 play_dialtone(chan, "");
303                                 did_ignore = 1;
304                         } else
305                                 if (did_ignore) {
306                                         ast_playtones_stop(chan);
307                                         did_ignore = 0;
308                                 }
309
310                         /* if can do some more, do it */
311                         if (!ast_matchmore_extension(chan,args.context,exten,1, chan->cid.cid_num)) {
312                                 break;
313                         }
314                 }
315         }
316
317         ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
318
319         if (k == 3) {
320                 int recheck = 0;
321                 struct ast_flags flags = { AST_CDR_FLAG_POSTED };
322
323                 if (!ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) {
324                         pbx_builtin_setvar_helper(chan, "INVALID_EXTEN", exten);
325                         exten[0] = 'i';
326                         exten[1] = '\0';
327                         recheck = 1;
328                 }
329                 if (!recheck || ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) {
330                         ast_playtones_stop(chan);
331                         /* We're authenticated and have a target extension */
332                         if (!ast_strlen_zero(args.cid)) {
333                                 ast_callerid_split(args.cid, ourcidname, sizeof(ourcidname), ourcidnum, sizeof(ourcidnum));
334                                 ast_set_callerid(chan, ourcidnum, ourcidname, ourcidnum);
335                         }
336
337                         if (!ast_strlen_zero(acctcode))
338                                 ast_string_field_set(chan, accountcode, acctcode);
339
340                         if (special_noanswer) flags.flags = 0;
341                         ast_cdr_reset(chan->cdr, &flags);
342                         ast_explicit_goto(chan, args.context, exten, 1);
343                         return 0;
344                 }
345         }
346
347         /* Received invalid, but no "i" extension exists in the given context */
348
349 reorder:
350         /* Play congestion for a bit */
351         ast_indicate(chan, AST_CONTROL_CONGESTION);
352         ast_safe_sleep(chan, 10*1000);
353
354         ast_playtones_stop(chan);
355
356         return -1;
357 }
358
359 static int unload_module(void)
360 {
361         return ast_unregister_application(app);
362 }
363
364 static int load_module(void)
365 {
366         return ast_register_application(app, disa_exec, synopsis, descrip) ?
367                 AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS;
368 }
369
370 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DISA (Direct Inward System Access) Application");