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