Merged revisions 68922 via svnmerge from
[asterisk/asterisk.git] / apps / app_authenticate.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Execute arbitrary authenticate commands
22  *
23  * \author Mark Spencer <markster@digium.com>
24  * 
25  * \ingroup applications
26  */
27
28 #include "asterisk.h"
29
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
31
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <string.h>
35 #include <errno.h>
36 #include <stdio.h>
37
38 #include "asterisk/lock.h"
39 #include "asterisk/file.h"
40 #include "asterisk/logger.h"
41 #include "asterisk/channel.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/module.h"
44 #include "asterisk/app.h"
45 #include "asterisk/astdb.h"
46 #include "asterisk/utils.h"
47 #include "asterisk/options.h"
48
49 enum {
50         OPT_ACCOUNT = (1 << 0),
51         OPT_DATABASE = (1 << 1),
52         OPT_JUMP = (1 << 2),
53         OPT_MULTIPLE = (1 << 3),
54         OPT_REMOVE = (1 << 4),
55 } auth_option_flags;
56
57 AST_APP_OPTIONS(auth_app_options, {
58         AST_APP_OPTION('a', OPT_ACCOUNT),
59         AST_APP_OPTION('d', OPT_DATABASE),
60         AST_APP_OPTION('j', OPT_JUMP),
61         AST_APP_OPTION('m', OPT_MULTIPLE),
62         AST_APP_OPTION('r', OPT_REMOVE),
63 });
64
65
66 static char *app = "Authenticate";
67
68 static char *synopsis = "Authenticate a user";
69
70 static char *descrip =
71 "  Authenticate(password[|options[|maxdigits]]): This application asks the caller\n"
72 "to enter a given password in order to continue dialplan execution. If the password\n"
73 "begins with the '/' character, it is interpreted as a file which contains a list of\n"
74 "valid passwords, listed 1 password per line in the file.\n"
75 "  When using a database key, the value associated with the key can be anything.\n"
76 "Users have three attempts to authenticate before the channel is hung up. If the\n"
77 "passsword is invalid, the 'j' option is specified, and priority n+101 exists,\n"
78 "dialplan execution will continnue at this location.\n"
79 "  Options:\n"
80 "     a - Set the channels' account code to the password that is entered\n"
81 "     d - Interpret the given path as database key, not a literal file\n"
82 "     j - Support jumping to n+101 if authentication fails\n"
83 "     m - Interpret the given path as a file which contains a list of account\n"
84 "         codes and password hashes delimited with ':', listed one per line in\n"
85 "         the file. When one of the passwords is matched, the channel will have\n"
86 "         its account code set to the corresponding account code in the file.\n"
87 "     r - Remove the database key upon successful entry (valid with 'd' only)\n"
88 "     maxdigits  - maximum acceptable number of digits. Stops reading after\n"
89 "         maxdigits have been entered (without requiring the user to\n"
90 "         press the '#' key).\n"
91 "         Defaults to 0 - no limit - wait for the user press the '#' key.\n"
92 ;
93
94 static int auth_exec(struct ast_channel *chan, void *data)
95 {
96         int res=0;
97         int retries;
98         struct ast_module_user *u;
99         char passwd[256];
100         char *prompt;
101         int maxdigits;
102         char *argcopy =NULL;
103         struct ast_flags flags = {0};
104
105         AST_DECLARE_APP_ARGS(arglist,
106                 AST_APP_ARG(password);
107                 AST_APP_ARG(options);
108                 AST_APP_ARG(maxdigits);
109         );
110         
111         if (ast_strlen_zero(data)) {
112                 ast_log(LOG_WARNING, "Authenticate requires an argument(password)\n");
113                 return -1;
114         }
115         
116         u = ast_module_user_add(chan);
117
118         if (chan->_state != AST_STATE_UP) {
119                 res = ast_answer(chan);
120                 if (res) {
121                         ast_module_user_remove(u);
122                         return -1;
123                 }
124         }
125         
126         argcopy = ast_strdupa(data);
127
128         AST_STANDARD_APP_ARGS(arglist,argcopy);
129         
130         if (!ast_strlen_zero(arglist.options)) {
131                 ast_app_parse_options(auth_app_options, &flags, NULL, arglist.options);
132         }
133
134         if (!ast_strlen_zero(arglist.maxdigits)) {
135                 maxdigits = atoi(arglist.maxdigits);
136                 if ((maxdigits<1) || (maxdigits>sizeof(passwd)-2))
137                         maxdigits = sizeof(passwd) - 2;
138         } else {
139                 maxdigits = sizeof(passwd) - 2;
140         }
141
142         /* Start asking for password */
143         prompt = "agent-pass";
144         for (retries = 0; retries < 3; retries++) {
145                 res = ast_app_getdata(chan, prompt, passwd, maxdigits, 0);
146                 if (res < 0)
147                         break;
148                 res = 0;
149                 if (arglist.password[0] == '/') {
150                         if (ast_test_flag(&flags,OPT_DATABASE)) {
151                                 char tmp[256];
152                                 /* Compare against a database key */
153                                 if (!ast_db_get(arglist.password + 1, passwd, tmp, sizeof(tmp))) {
154                                         /* It's a good password */
155                                         if (ast_test_flag(&flags,OPT_REMOVE)) {
156                                                 ast_db_del(arglist.password + 1, passwd);
157                                         }
158                                         break;
159                                 }
160                         } else {
161                                 /* Compare against a file */
162                                 FILE *f;
163                                 f = fopen(arglist.password, "r");
164                                 if (f) {
165                                         char buf[256] = "";
166                                         char md5passwd[33] = "";
167                                         char *md5secret = NULL;
168
169                                         while (!feof(f)) {
170                                                 fgets(buf, sizeof(buf), f);
171                                                 if (!feof(f) && !ast_strlen_zero(buf)) {
172                                                         buf[strlen(buf) - 1] = '\0';
173                                                         if (ast_test_flag(&flags,OPT_MULTIPLE)) {
174                                                                 md5secret = strchr(buf, ':');
175                                                                 if (md5secret == NULL)
176                                                                         continue;
177                                                                 *md5secret = '\0';
178                                                                 md5secret++;
179                                                                 ast_md5_hash(md5passwd, passwd);
180                                                                 if (!strcmp(md5passwd, md5secret)) {
181                                                                         if (ast_test_flag(&flags,OPT_ACCOUNT))
182                                                                                 ast_cdr_setaccount(chan, buf);
183                                                                         break;
184                                                                 }
185                                                         } else {
186                                                                 if (!strcmp(passwd, buf)) {
187                                                                         if (ast_test_flag(&flags,OPT_ACCOUNT))
188                                                                                 ast_cdr_setaccount(chan, buf);
189                                                                         break;
190                                                                 }
191                                                         }
192                                                 }
193                                         }
194                                         fclose(f);
195                                         if (!ast_strlen_zero(buf)) {
196                                                 if (ast_test_flag(&flags,OPT_MULTIPLE)) {
197                                                         if (md5secret && !strcmp(md5passwd, md5secret))
198                                                                 break;
199                                                 } else {
200                                                         if (!strcmp(passwd, buf))
201                                                                 break;
202                                                 }
203                                         }
204                                 } else 
205                                         ast_log(LOG_WARNING, "Unable to open file '%s' for authentication: %s\n", arglist.password, strerror(errno));
206                         }
207                 } else {
208                         /* Compare against a fixed password */
209                         if (!strcmp(passwd, arglist.password)) 
210                                 break;
211                 }
212                 prompt="auth-incorrect";
213         }
214         if ((retries < 3) && !res) {
215                 if (ast_test_flag(&flags,OPT_ACCOUNT) && !ast_test_flag(&flags,OPT_MULTIPLE)) 
216                         ast_cdr_setaccount(chan, passwd);
217                 res = ast_streamfile(chan, "auth-thankyou", chan->language);
218                 if (!res)
219                         res = ast_waitstream(chan, "");
220         } else {
221                 if (ast_test_flag(&flags,OPT_JUMP) && ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101) == 0) {
222                         res = 0;
223                 } else {
224                         if (!ast_streamfile(chan, "vm-goodbye", chan->language))
225                                 res = ast_waitstream(chan, "");
226                         res = -1;
227                 }
228         }
229         ast_module_user_remove(u);
230         return res;
231 }
232
233 static int unload_module(void)
234 {
235         int res;
236
237         ast_module_user_hangup_all();
238
239         res = ast_unregister_application(app);
240
241         
242         return res;
243 }
244
245 static int load_module(void)
246 {
247         return ast_register_application(app, auth_exec, synopsis, descrip);
248 }
249
250 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Authentication Application");