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