Fixup skinny registration following network issues.
[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(ast_channel_zone(chan), "dialrecall");
135         } else {
136                 ts = ast_get_indication_tone(ast_channel_zone(chan), "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 = (ast_channel_pbx(chan) ? ast_channel_pbx(chan)->rtimeoutms : 20000);
151         int digittimeout = (ast_channel_pbx(chan) ? ast_channel_pbx(chan)->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         } else {
187                 /* Coverity - This uninit_use should be ignored since this macro initializes the flags */
188                 ast_clear_flag(&flags, AST_FLAGS_ALL);
189         }
190
191
192         ast_debug(1, "Mailbox: %s\n",args.mailbox);
193
194         if (!ast_test_flag(&flags, NOANSWER_FLAG)) {
195                 if (ast_channel_state(chan) != AST_STATE_UP) {
196                         /* answer */
197                         ast_answer(chan);
198                 }
199         } else special_noanswer = 1;
200
201         ast_debug(1, "Context: %s\n",args.context);
202
203         if (!strcasecmp(args.passcode, "no-password")) {
204                 k |= 1; /* We have the password */
205                 ast_debug(1, "DISA no-password login success\n");
206         }
207
208         lastdigittime = ast_tvnow();
209
210         play_dialtone(chan, args.mailbox);
211
212         ast_set_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
213
214         for (;;) {
215                   /* if outa time, give em reorder */
216                 if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) > ((k&2) ? digittimeout : firstdigittimeout)) {
217                         ast_debug(1,"DISA %s entry timeout on chan %s\n",
218                                 ((k&1) ? "extension" : "password"),ast_channel_name(chan));
219                         break;
220                 }
221
222                 if ((res = ast_waitfor(chan, -1)) < 0) {
223                         ast_debug(1, "Waitfor returned %d\n", res);
224                         continue;
225                 }
226
227                 if (!(f = ast_read(chan))) {
228                         ast_clear_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
229                         return -1;
230                 }
231
232                 if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass.integer == AST_CONTROL_HANGUP)) {
233                         if (f->data.uint32)
234                                 ast_channel_hangupcause_set(chan, f->data.uint32);
235                         ast_frfree(f);
236                         ast_clear_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
237                         return -1;
238                 }
239
240                 /* If the frame coming in is not DTMF, just drop it and continue */
241                 if (f->frametype != AST_FRAME_DTMF) {
242                         ast_frfree(f);
243                         continue;
244                 }
245
246                 j = f->subclass.integer;  /* save digit */
247                 ast_frfree(f);
248
249                 if (!i) {
250                         k |= 2; /* We have the first digit */
251                         ast_playtones_stop(chan);
252                 }
253
254                 lastdigittime = ast_tvnow();
255
256                 /* got a DTMF tone */
257                 if (i < AST_MAX_EXTENSION) { /* if still valid number of digits */
258                         if (!(k&1)) { /* if in password state */
259                                 if (j == '#') { /* end of password */
260                                           /* see if this is an integer */
261                                         if (sscanf(args.passcode,"%30d",&j) < 1) { /* nope, it must be a filename */
262                                                 fp = fopen(args.passcode,"r");
263                                                 if (!fp) {
264                                                         ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",args.passcode,ast_channel_name(chan));
265                                                         ast_clear_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
266                                                         return -1;
267                                                 }
268                                                 pwline[0] = 0;
269                                                 while(fgets(pwline,sizeof(pwline) - 1,fp)) {
270                                                         if (!pwline[0])
271                                                                 continue;
272                                                         if (pwline[strlen(pwline) - 1] == '\n')
273                                                                 pwline[strlen(pwline) - 1] = 0;
274                                                         if (!pwline[0])
275                                                                 continue;
276                                                          /* skip comments */
277                                                         if (pwline[0] == '#')
278                                                                 continue;
279                                                         if (pwline[0] == ';')
280                                                                 continue;
281
282                                                         AST_STANDARD_APP_ARGS(args, pwline);
283
284                                                         ast_debug(1, "Mailbox: %s\n",args.mailbox);
285
286                                                         /* password must be in valid format (numeric) */
287                                                         if (sscanf(args.passcode,"%30d", &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",ast_channel_name(chan),exten);
303                                                 goto reorder;
304
305                                         }
306                                          /* password good, set to dial state */
307                                         ast_debug(1,"DISA on chan %s password is good\n",ast_channel_name(chan));
308                                         play_dialtone(chan, args.mailbox);
309
310                                         k|=1; /* In number mode */
311                                         i = 0;  /* re-set buffer pointer */
312                                         exten[sizeof(acctcode)] = 0;
313                                         ast_copy_string(acctcode, exten, sizeof(acctcode));
314                                         exten[0] = 0;
315                                         ast_debug(1,"Successful DISA log-in on chan %s\n", ast_channel_name(chan));
316                                         continue;
317                                 }
318                         } else {
319                                 if (j == '#') { /* end of extension .. maybe */
320                                         if (i == 0
321                                                 && (ast_matchmore_extension(chan, args.context, "#", 1,
322                                                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))
323                                                         || ast_exists_extension(chan, args.context, "#", 1,
324                                                                 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) ) {
325                                                 /* Let the # be the part of, or the entire extension */
326                                         } else {
327                                                 break;
328                                         }
329                                 }
330                         }
331
332                         exten[i++] = j;  /* save digit */
333                         exten[i] = 0;
334                         if (!(k&1))
335                                 continue; /* if getting password, continue doing it */
336                         /* if this exists */
337
338                         /* user wants end of number, remove # */
339                         if (ast_test_flag(&flags, POUND_TO_END_FLAG) && j == '#') {
340                                 exten[--i] = 0;
341                                 break;
342                         }
343
344                         if (ast_ignore_pattern(args.context, exten)) {
345                                 play_dialtone(chan, "");
346                                 did_ignore = 1;
347                         } else
348                                 if (did_ignore) {
349                                         ast_playtones_stop(chan);
350                                         did_ignore = 0;
351                                 }
352
353                         /* if can do some more, do it */
354                         if (!ast_matchmore_extension(chan, args.context, exten, 1,
355                                 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
356                                 break;
357                         }
358                 }
359         }
360
361         ast_clear_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
362
363         if (k == 3) {
364                 int recheck = 0;
365                 struct ast_flags cdr_flags = { AST_CDR_FLAG_DISABLE, };
366
367                 if (!ast_exists_extension(chan, args.context, exten, 1,
368                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
369                         pbx_builtin_setvar_helper(chan, "INVALID_EXTEN", exten);
370                         exten[0] = 'i';
371                         exten[1] = '\0';
372                         recheck = 1;
373                 }
374                 if (!recheck
375                         || ast_exists_extension(chan, args.context, exten, 1,
376                                 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
377                         ast_playtones_stop(chan);
378                         /* We're authenticated and have a target extension */
379                         if (!ast_strlen_zero(args.cid)) {
380                                 ast_callerid_split(args.cid, ourcidname, sizeof(ourcidname), ourcidnum, sizeof(ourcidnum));
381                                 ast_set_callerid(chan, ourcidnum, ourcidname, ourcidnum);
382                         }
383
384                         if (!ast_strlen_zero(acctcode)) {
385                                 ast_channel_lock(chan);
386                                 ast_channel_accountcode_set(chan, acctcode);
387                                 ast_channel_unlock(chan);
388                         }
389
390                         if (special_noanswer) {
391                                 ast_clear_flag(&cdr_flags, AST_CDR_FLAG_DISABLE);
392                         }
393                         ast_cdr_reset(ast_channel_name(chan), &cdr_flags);
394                         ast_explicit_goto(chan, args.context, exten, 1);
395                         return 0;
396                 }
397         }
398
399         /* Received invalid, but no "i" extension exists in the given context */
400
401 reorder:
402         /* Play congestion for a bit */
403         ast_indicate(chan, AST_CONTROL_CONGESTION);
404         ast_safe_sleep(chan, 10*1000);
405
406         ast_playtones_stop(chan);
407
408         return -1;
409 }
410
411 static int unload_module(void)
412 {
413         return ast_unregister_application(app);
414 }
415
416 static int load_module(void)
417 {
418         return ast_register_application_xml(app, disa_exec) ?
419                 AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS;
420 }
421
422 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DISA (Direct Inward System Access) Application");