- Add more <see-also> based on TFOT.
[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 char *app = "DISA";
114
115 enum {
116         NOANSWER_FLAG = (1 << 0),
117         POUND_TO_END_FLAG = (1 << 1),
118 } option_flags;
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         const struct ind_tone_zone_sound *ts = NULL;
128         if(ast_app_has_voicemail(mailbox, NULL))
129                 ts = ast_get_indication_tone(chan->zone, "dialrecall");
130         else
131                 ts = ast_get_indication_tone(chan->zone, "dial");
132         if (ts)
133                 ast_playtones_start(chan, 0, ts->data, 0);
134         else
135                 ast_tonepair_start(chan, 350, 440, 0, 0);
136 }
137
138 static int disa_exec(struct ast_channel *chan, void *data)
139 {
140         int i = 0, j, k = 0, did_ignore = 0, special_noanswer = 0;
141         int firstdigittimeout = (chan->pbx ? chan->pbx->rtimeoutms : 20000);
142         int digittimeout = (chan->pbx ? chan->pbx->dtimeoutms : 10000);
143         struct ast_flags flags;
144         char *tmp, exten[AST_MAX_EXTENSION] = "", acctcode[20]="";
145         char pwline[256];
146         char ourcidname[256],ourcidnum[256];
147         struct ast_frame *f;
148         struct timeval lastdigittime;
149         int res;
150         FILE *fp;
151         AST_DECLARE_APP_ARGS(args,
152                 AST_APP_ARG(passcode);
153                 AST_APP_ARG(context);
154                 AST_APP_ARG(cid);
155                 AST_APP_ARG(mailbox);
156                 AST_APP_ARG(options);
157         );
158
159         if (ast_strlen_zero(data)) {
160                 ast_log(LOG_WARNING, "DISA requires an argument (passcode/passcode file)\n");
161                 return -1;
162         }
163
164         ast_debug(1, "Digittimeout: %d\n", digittimeout);
165         ast_debug(1, "Responsetimeout: %d\n", firstdigittimeout);
166
167         tmp = ast_strdupa(data);
168
169         AST_STANDARD_APP_ARGS(args, tmp);
170
171         if (ast_strlen_zero(args.context))
172                 args.context = "disa";
173         if (ast_strlen_zero(args.mailbox))
174                 args.mailbox = "";
175         if (!ast_strlen_zero(args.options))
176                 ast_app_parse_options(app_opts, &flags, NULL, args.options);
177
178         ast_debug(1, "Mailbox: %s\n",args.mailbox);
179
180         if (!ast_test_flag(&flags, NOANSWER_FLAG)) {
181                 if (chan->_state != AST_STATE_UP) {
182                         /* answer */
183                         ast_answer(chan);
184                 }
185         } else
186                 special_noanswer = 1;
187
188         ast_debug(1, "Context: %s\n",args.context);
189
190         if (!strcasecmp(args.passcode, "no-password")) {
191                 k |= 1; /* We have the password */
192                 ast_debug(1, "DISA no-password login success\n");
193         }
194
195         lastdigittime = ast_tvnow();
196
197         play_dialtone(chan, args.mailbox);
198
199         ast_set_flag(chan, AST_FLAG_END_DTMF_ONLY);
200
201         for (;;) {
202                   /* if outa time, give em reorder */
203                 if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) > ((k&2) ? digittimeout : firstdigittimeout)) {
204                         ast_debug(1,"DISA %s entry timeout on chan %s\n",
205                                 ((k&1) ? "extension" : "password"),chan->name);
206                         break;
207                 }
208
209                 if ((res = ast_waitfor(chan, -1) < 0)) {
210                         ast_debug(1, "Waitfor returned %d\n", res);
211                         continue;
212                 }
213
214                 if (!(f = ast_read(chan))) {
215                         ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
216                         return -1;
217                 }
218
219                 if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
220                         if (f->data.uint32)
221                                 chan->hangupcause = f->data.uint32;
222                         ast_frfree(f);
223                         ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
224                         return -1;
225                 }
226
227                 /* If the frame coming in is not DTMF, just drop it and continue */
228                 if (f->frametype != AST_FRAME_DTMF) {
229                         ast_frfree(f);
230                         continue;
231                 }
232
233                 j = f->subclass;  /* save digit */
234                 ast_frfree(f);
235
236                 if (!i) {
237                         k |= 2; /* We have the first digit */
238                         ast_playtones_stop(chan);
239                 }
240
241                 lastdigittime = ast_tvnow();
242
243                 /* got a DTMF tone */
244                 if (i < AST_MAX_EXTENSION) { /* if still valid number of digits */
245                         if (!(k&1)) { /* if in password state */
246                                 if (j == '#') { /* end of password */
247                                           /* see if this is an integer */
248                                         if (sscanf(args.passcode,"%d",&j) < 1) { /* nope, it must be a filename */
249                                                 fp = fopen(args.passcode,"r");
250                                                 if (!fp) {
251                                                         ast_log(LOG_WARNING,"DISA password file %s not found on chan %s\n",args.passcode,chan->name);
252                                                         ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
253                                                         return -1;
254                                                 }
255                                                 pwline[0] = 0;
256                                                 while(fgets(pwline,sizeof(pwline) - 1,fp)) {
257                                                         if (!pwline[0])
258                                                                 continue;
259                                                         if (pwline[strlen(pwline) - 1] == '\n')
260                                                                 pwline[strlen(pwline) - 1] = 0;
261                                                         if (!pwline[0])
262                                                                 continue;
263                                                          /* skip comments */
264                                                         if (pwline[0] == '#')
265                                                                 continue;
266                                                         if (pwline[0] == ';')
267                                                                 continue;
268
269                                                         AST_STANDARD_APP_ARGS(args, pwline);
270
271                                                         ast_debug(1, "Mailbox: %s\n",args.mailbox);
272
273                                                         /* password must be in valid format (numeric) */
274                                                         if (sscanf(args.passcode,"%d", &j) < 1)
275                                                                 continue;
276                                                          /* if we got it */
277                                                         if (!strcmp(exten,args.passcode)) {
278                                                                 if (ast_strlen_zero(args.context))
279                                                                         args.context = "disa";
280                                                                 if (ast_strlen_zero(args.mailbox))
281                                                                         args.mailbox = "";
282                                                                 break;
283                                                         }
284                                                 }
285                                                 fclose(fp);
286                                         }
287                                         /* compare the two */
288                                         if (strcmp(exten,args.passcode)) {
289                                                 ast_log(LOG_WARNING,"DISA on chan %s got bad password %s\n",chan->name,exten);
290                                                 goto reorder;
291
292                                         }
293                                          /* password good, set to dial state */
294                                         ast_debug(1,"DISA on chan %s password is good\n",chan->name);
295                                         play_dialtone(chan, args.mailbox);
296
297                                         k|=1; /* In number mode */
298                                         i = 0;  /* re-set buffer pointer */
299                                         exten[sizeof(acctcode)] = 0;
300                                         ast_copy_string(acctcode, exten, sizeof(acctcode));
301                                         exten[0] = 0;
302                                         ast_debug(1,"Successful DISA log-in on chan %s\n", chan->name);
303                                         continue;
304                                 }
305                         } else {
306                                 if (j == '#') { /* end of extension */
307                                         break;
308                                 }
309                         }
310
311                         exten[i++] = j;  /* save digit */
312                         exten[i] = 0;
313                         if (!(k&1))
314                                 continue; /* if getting password, continue doing it */
315                         /* if this exists */
316
317                         /* user wants end of number, remove # */
318                         if (ast_test_flag(&flags, POUND_TO_END_FLAG) && j == '#') {
319                                 exten[--i] = 0;
320                                 break;
321                         }
322
323                         if (ast_ignore_pattern(args.context, exten)) {
324                                 play_dialtone(chan, "");
325                                 did_ignore = 1;
326                         } else
327                                 if (did_ignore) {
328                                         ast_playtones_stop(chan);
329                                         did_ignore = 0;
330                                 }
331
332                         /* if can do some more, do it */
333                         if (!ast_matchmore_extension(chan,args.context,exten,1, chan->cid.cid_num)) {
334                                 break;
335                         }
336                 }
337         }
338
339         ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
340
341         if (k == 3) {
342                 int recheck = 0;
343                 struct ast_flags cdr_flags = { AST_CDR_FLAG_POSTED };
344
345                 if (!ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) {
346                         pbx_builtin_setvar_helper(chan, "INVALID_EXTEN", exten);
347                         exten[0] = 'i';
348                         exten[1] = '\0';
349                         recheck = 1;
350                 }
351                 if (!recheck || ast_exists_extension(chan, args.context, exten, 1, chan->cid.cid_num)) {
352                         ast_playtones_stop(chan);
353                         /* We're authenticated and have a target extension */
354                         if (!ast_strlen_zero(args.cid)) {
355                                 ast_callerid_split(args.cid, ourcidname, sizeof(ourcidname), ourcidnum, sizeof(ourcidnum));
356                                 ast_set_callerid(chan, ourcidnum, ourcidname, ourcidnum);
357                         }
358
359                         if (!ast_strlen_zero(acctcode))
360                                 ast_string_field_set(chan, accountcode, acctcode);
361
362                         if (special_noanswer) cdr_flags.flags = 0;
363                         ast_cdr_reset(chan->cdr, &cdr_flags);
364                         ast_explicit_goto(chan, args.context, exten, 1);
365                         return 0;
366                 }
367         }
368
369         /* Received invalid, but no "i" extension exists in the given context */
370
371 reorder:
372         /* Play congestion for a bit */
373         ast_indicate(chan, AST_CONTROL_CONGESTION);
374         ast_safe_sleep(chan, 10*1000);
375
376         ast_playtones_stop(chan);
377
378         return -1;
379 }
380
381 static int unload_module(void)
382 {
383         return ast_unregister_application(app);
384 }
385
386 static int load_module(void)
387 {
388         return ast_register_application_xml(app, disa_exec) ?
389                 AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS;
390 }
391
392 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DISA (Direct Inward System Access) Application");