2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
7 * Made only slightly more sane by Mark Spencer <markster@digium.com>
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.
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.
22 * \brief DISA -- Direct Inward System Access Application
24 * \author Jim Dixon <jim@lambdatel.com>
26 * \ingroup applications
30 <use type="module">app_cdr</use>
31 <support_level>core</support_level>
36 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
41 #include "asterisk/lock.h"
42 #include "asterisk/file.h"
43 #include "asterisk/channel.h"
44 #include "asterisk/app.h"
45 #include "asterisk/indications.h"
46 #include "asterisk/pbx.h"
47 #include "asterisk/module.h"
48 #include "asterisk/translate.h"
49 #include "asterisk/ulaw.h"
50 #include "asterisk/callerid.h"
51 #include "asterisk/stringfields.h"
54 <application name="DISA" language="en_US">
56 Direct Inward System Access.
59 <parameter name="passcode|filename" required="true">
60 <para>If you need to present a DISA dialtone without entering a password,
61 simply set <replaceable>passcode</replaceable> to <literal>no-password</literal></para>
62 <para>You may specified a <replaceable>filename</replaceable> instead of a
63 <replaceable>passcode</replaceable>, this filename must contain individual passcodes</para>
65 <parameter name="context">
66 <para>Specifies the dialplan context in which the user-entered extension
67 will be matched. If no context is specified, the DISA application defaults
68 to the <literal>disa</literal> context. Presumably a normal system will have a special
69 context set up for DISA use with some or a lot of restrictions.</para>
71 <parameter name="cid">
72 <para>Specifies a new (different) callerid to be used for this call.</para>
74 <parameter name="mailbox" argsep="@">
75 <para>Will cause a stutter-dialtone (indication <emphasis>dialrecall</emphasis>)
76 to be used, if the specified mailbox contains any new messages.</para>
77 <argument name="mailbox" required="true" />
78 <argument name="context" required="false" />
80 <parameter name="options">
83 <para>The DISA application will not answer initially.</para>
86 <para>The extension entered will be considered complete when a <literal>#</literal>
93 <para>The DISA, Direct Inward System Access, application allows someone from
94 outside the telephone switch (PBX) to obtain an <emphasis>internal</emphasis> system
95 dialtone and to place calls from it as if they were placing a call from
97 DISA plays a dialtone. The user enters their numeric passcode, followed by
98 the pound sign <literal>#</literal>. If the passcode is correct, the user is then given
99 system dialtone within <replaceable>context</replaceable> on which a call may be placed.
100 If the user enters an invalid extension and extension <literal>i</literal> exists in the specified
101 <replaceable>context</replaceable>, it will be used.
103 <para>Be aware that using this may compromise the security of your PBX.</para>
104 <para>The arguments to this application (in <filename>extensions.conf</filename>) allow either
105 specification of a single global <replaceable>passcode</replaceable> (that everyone uses), or
106 individual passcodes contained in a file (<replaceable>filename</replaceable>).</para>
107 <para>The file that contains the passcodes (if used) allows a complete
108 specification of all of the same arguments available on the command
109 line, with the sole exception of the options. The file may contain blank
110 lines, or comments starting with <literal>#</literal> or <literal>;</literal>.</para>
113 <ref type="application">Authenticate</ref>
114 <ref type="application">VMAuthenticate</ref>
118 static const char app[] = "DISA";
121 NOANSWER_FLAG = (1 << 0),
122 POUND_TO_END_FLAG = (1 << 1),
125 AST_APP_OPTIONS(app_opts, {
126 AST_APP_OPTION('n', NOANSWER_FLAG),
127 AST_APP_OPTION('p', POUND_TO_END_FLAG),
130 static void play_dialtone(struct ast_channel *chan, char *mailbox)
132 struct ast_tone_zone_sound *ts = NULL;
134 if (ast_app_has_voicemail(mailbox, NULL)) {
135 ts = ast_get_indication_tone(ast_channel_zone(chan), "dialrecall");
137 ts = ast_get_indication_tone(ast_channel_zone(chan), "dial");
141 ast_playtones_start(chan, 0, ts->data, 0);
142 ts = ast_tone_zone_sound_unref(ts);
144 ast_tonepair_start(chan, 350, 440, 0, 0);
148 static int disa_exec(struct ast_channel *chan, const char *data)
150 int i = 0, j, k = 0, did_ignore = 0, special_noanswer = 0;
151 int firstdigittimeout = (ast_channel_pbx(chan) ? ast_channel_pbx(chan)->rtimeoutms : 20000);
152 int digittimeout = (ast_channel_pbx(chan) ? ast_channel_pbx(chan)->dtimeoutms : 10000);
153 struct ast_flags flags;
154 char *tmp, exten[AST_MAX_EXTENSION] = "", acctcode[20]="";
156 char ourcidname[256],ourcidnum[256];
158 struct timeval lastdigittime;
161 AST_DECLARE_APP_ARGS(args,
162 AST_APP_ARG(passcode);
163 AST_APP_ARG(context);
165 AST_APP_ARG(mailbox);
166 AST_APP_ARG(options);
169 if (ast_strlen_zero(data)) {
170 ast_log(LOG_WARNING, "DISA requires an argument (passcode/passcode file)\n");
174 ast_debug(1, "Digittimeout: %d\n", digittimeout);
175 ast_debug(1, "Responsetimeout: %d\n", firstdigittimeout);
177 tmp = ast_strdupa(data);
179 AST_STANDARD_APP_ARGS(args, tmp);
181 if (ast_strlen_zero(args.context))
182 args.context = "disa";
183 if (ast_strlen_zero(args.mailbox))
185 if (!ast_strlen_zero(args.options)) {
186 ast_app_parse_options(app_opts, &flags, NULL, args.options);
188 /* Coverity - This uninit_use should be ignored since this macro initializes the flags */
189 ast_clear_flag(&flags, AST_FLAGS_ALL);
193 ast_debug(1, "Mailbox: %s\n",args.mailbox);
195 if (!ast_test_flag(&flags, NOANSWER_FLAG)) {
196 if (ast_channel_state(chan) != AST_STATE_UP) {
200 } else special_noanswer = 1;
202 ast_debug(1, "Context: %s\n",args.context);
204 if (!strcasecmp(args.passcode, "no-password")) {
205 k |= 1; /* We have the password */
206 ast_debug(1, "DISA no-password login success\n");
209 lastdigittime = ast_tvnow();
211 play_dialtone(chan, args.mailbox);
213 ast_set_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
216 /* if outa time, give em reorder */
217 if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) > ((k&2) ? digittimeout : firstdigittimeout)) {
218 ast_debug(1,"DISA %s entry timeout on chan %s\n",
219 ((k&1) ? "extension" : "password"),ast_channel_name(chan));
223 if ((res = ast_waitfor(chan, -1)) < 0) {
224 ast_debug(1, "Waitfor returned %d\n", res);
228 if (!(f = ast_read(chan))) {
229 ast_clear_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
233 if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass.integer == AST_CONTROL_HANGUP)) {
235 ast_channel_hangupcause_set(chan, f->data.uint32);
237 ast_clear_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
241 /* If the frame coming in is not DTMF, just drop it and continue */
242 if (f->frametype != AST_FRAME_DTMF) {
247 j = f->subclass.integer; /* save digit */
251 k |= 2; /* We have the first digit */
252 ast_playtones_stop(chan);
255 lastdigittime = ast_tvnow();
257 /* got a DTMF tone */
258 if (i < AST_MAX_EXTENSION) { /* if still valid number of digits */
259 if (!(k&1)) { /* if in password state */
260 if (j == '#') { /* end of password */
261 /* see if this is an integer */
262 if (sscanf(args.passcode,"%30d",&j) < 1) { /* nope, it must be a filename */
263 fp = fopen(args.passcode,"r");
265 ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",args.passcode,ast_channel_name(chan));
266 ast_clear_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
270 while(fgets(pwline,sizeof(pwline) - 1,fp)) {
273 if (pwline[strlen(pwline) - 1] == '\n')
274 pwline[strlen(pwline) - 1] = 0;
278 if (pwline[0] == '#')
280 if (pwline[0] == ';')
283 AST_STANDARD_APP_ARGS(args, pwline);
285 ast_debug(1, "Mailbox: %s\n",args.mailbox);
287 /* password must be in valid format (numeric) */
288 if (sscanf(args.passcode,"%30d", &j) < 1)
291 if (!strcmp(exten,args.passcode)) {
292 if (ast_strlen_zero(args.context))
293 args.context = "disa";
294 if (ast_strlen_zero(args.mailbox))
301 /* compare the two */
302 if (strcmp(exten,args.passcode)) {
303 ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",ast_channel_name(chan),exten);
307 /* password good, set to dial state */
308 ast_debug(1,"DISA on chan %s password is good\n",ast_channel_name(chan));
309 play_dialtone(chan, args.mailbox);
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));
316 ast_debug(1,"Successful DISA log-in on chan %s\n", ast_channel_name(chan));
320 if (j == '#') { /* end of extension .. maybe */
322 && (ast_matchmore_extension(chan, args.context, "#", 1,
323 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))
324 || ast_exists_extension(chan, args.context, "#", 1,
325 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) ) {
326 /* Let the # be the part of, or the entire extension */
333 exten[i++] = j; /* save digit */
336 continue; /* if getting password, continue doing it */
339 /* user wants end of number, remove # */
340 if (ast_test_flag(&flags, POUND_TO_END_FLAG) && j == '#') {
345 if (ast_ignore_pattern(args.context, exten)) {
346 play_dialtone(chan, "");
350 ast_playtones_stop(chan);
354 /* if can do some more, do it */
355 if (!ast_matchmore_extension(chan, args.context, exten, 1,
356 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
362 ast_clear_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
366 struct ast_app *app_reset_cdr;
368 if (!ast_exists_extension(chan, args.context, exten, 1,
369 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
370 pbx_builtin_setvar_helper(chan, "INVALID_EXTEN", exten);
376 || ast_exists_extension(chan, args.context, exten, 1,
377 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
378 ast_playtones_stop(chan);
379 /* We're authenticated and have a target extension */
380 if (!ast_strlen_zero(args.cid)) {
381 ast_callerid_split(args.cid, ourcidname, sizeof(ourcidname), ourcidnum, sizeof(ourcidnum));
382 ast_set_callerid(chan, ourcidnum, ourcidname, ourcidnum);
385 if (!ast_strlen_zero(acctcode)) {
386 ast_channel_lock(chan);
387 ast_channel_accountcode_set(chan, acctcode);
388 ast_channel_unlock(chan);
391 app_reset_cdr = pbx_findapp("ResetCDR");
393 pbx_exec(chan, app_reset_cdr, special_noanswer ? "" : "e");
395 ast_log(AST_LOG_NOTICE, "ResetCDR application not found; CDR will not be reset\n");
397 ast_explicit_goto(chan, args.context, exten, 1);
402 /* Received invalid, but no "i" extension exists in the given context */
405 /* Play congestion for a bit */
406 ast_indicate(chan, AST_CONTROL_CONGESTION);
407 ast_safe_sleep(chan, 10*1000);
409 ast_playtones_stop(chan);
414 static int unload_module(void)
416 return ast_unregister_application(app);
419 static int load_module(void)
421 return ast_register_application_xml(app, disa_exec) ?
422 AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS;
425 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DISA (Direct Inward System Access) Application");