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