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