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