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