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