normalize code preparing for loader changes
[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 <stdlib.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <errno.h>
32 #include <stdio.h>
33
34 #include "asterisk.h"
35
36 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
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 LOCAL_USER_DECL;
95
96 static int auth_exec(struct ast_channel *chan, void *data)
97 {
98         int res=0;
99         int retries;
100         struct localuser *u;
101         char passwd[256];
102         char *prompt;
103         int maxdigits;
104         char *argcopy =NULL;
105         struct ast_flags flags = {0};
106
107         AST_DECLARE_APP_ARGS(arglist,
108                 AST_APP_ARG(password);
109                 AST_APP_ARG(options);
110                 AST_APP_ARG(maxdigits);
111         );
112         
113         if (ast_strlen_zero(data)) {
114                 ast_log(LOG_WARNING, "Authenticate requires an argument(password)\n");
115                 return -1;
116         }
117         
118         LOCAL_USER_ADD(u);
119
120         if (chan->_state != AST_STATE_UP) {
121                 res = ast_answer(chan);
122                 if (res) {
123                         LOCAL_USER_REMOVE(u);
124                         return -1;
125                 }
126         }
127         
128         if (!(argcopy = ast_strdupa(data))) {
129                 LOCAL_USER_REMOVE(u);
130                 return -1;
131         }
132
133         AST_STANDARD_APP_ARGS(arglist,argcopy);
134         
135         if (!ast_strlen_zero(arglist.options)) {
136                 ast_app_parse_options(auth_app_options, &flags, NULL, arglist.options);
137         }
138
139         if (!ast_strlen_zero(arglist.maxdigits)) {
140                 maxdigits = atoi(arglist.maxdigits);
141                 if ((maxdigits<1) || (maxdigits>sizeof(passwd)-2))
142                         maxdigits = sizeof(passwd) - 2;
143         } else {
144                 maxdigits = sizeof(passwd) - 2;
145         }
146
147         /* Start asking for password */
148         prompt = "agent-pass";
149         for (retries = 0; retries < 3; retries++) {
150                 res = ast_app_getdata(chan, prompt, passwd, maxdigits, 0);
151                 if (res < 0)
152                         break;
153                 res = 0;
154                 if (arglist.password[0] == '/') {
155                         if (ast_test_flag(&flags,OPT_DATABASE)) {
156                                 char tmp[256];
157                                 /* Compare against a database key */
158                                 if (!ast_db_get(arglist.password + 1, passwd, tmp, sizeof(tmp))) {
159                                         /* It's a good password */
160                                         if (ast_test_flag(&flags,OPT_REMOVE)) {
161                                                 ast_db_del(arglist.password + 1, passwd);
162                                         }
163                                         break;
164                                 }
165                         } else {
166                                 /* Compare against a file */
167                                 FILE *f;
168                                 f = fopen(arglist.password, "r");
169                                 if (f) {
170                                         char buf[256] = "";
171                                         char md5passwd[33] = "";
172                                         char *md5secret = NULL;
173
174                                         while (!feof(f)) {
175                                                 fgets(buf, sizeof(buf), f);
176                                                 if (!feof(f) && !ast_strlen_zero(buf)) {
177                                                         buf[strlen(buf) - 1] = '\0';
178                                                         if (ast_test_flag(&flags,OPT_MULTIPLE)) {
179                                                                 md5secret = strchr(buf, ':');
180                                                                 if (md5secret == NULL)
181                                                                         continue;
182                                                                 *md5secret = '\0';
183                                                                 md5secret++;
184                                                                 ast_md5_hash(md5passwd, passwd);
185                                                                 if (!strcmp(md5passwd, md5secret)) {
186                                                                         if (ast_test_flag(&flags,OPT_ACCOUNT))
187                                                                                 ast_cdr_setaccount(chan, buf);
188                                                                         break;
189                                                                 }
190                                                         } else {
191                                                                 if (!strcmp(passwd, buf)) {
192                                                                         if (ast_test_flag(&flags,OPT_ACCOUNT))
193                                                                                 ast_cdr_setaccount(chan, buf);
194                                                                         break;
195                                                                 }
196                                                         }
197                                                 }
198                                         }
199                                         fclose(f);
200                                         if (!ast_strlen_zero(buf)) {
201                                                 if (ast_test_flag(&flags,OPT_MULTIPLE)) {
202                                                         if (md5secret && !strcmp(md5passwd, md5secret))
203                                                                 break;
204                                                 } else {
205                                                         if (!strcmp(passwd, buf))
206                                                                 break;
207                                                 }
208                                         }
209                                 } else 
210                                         ast_log(LOG_WARNING, "Unable to open file '%s' for authentication: %s\n", arglist.password, strerror(errno));
211                         }
212                 } else {
213                         /* Compare against a fixed password */
214                         if (!strcmp(passwd, arglist.password)) 
215                                 break;
216                 }
217                 prompt="auth-incorrect";
218         }
219         if ((retries < 3) && !res) {
220                 if (ast_test_flag(&flags,OPT_ACCOUNT) && !ast_test_flag(&flags,OPT_MULTIPLE)) 
221                         ast_cdr_setaccount(chan, passwd);
222                 res = ast_streamfile(chan, "auth-thankyou", chan->language);
223                 if (!res)
224                         res = ast_waitstream(chan, "");
225         } else {
226                 if (ast_test_flag(&flags,OPT_JUMP) && ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) {
227                         res = 0;
228                 } else {
229                         if (!ast_streamfile(chan, "vm-goodbye", chan->language))
230                                 res = ast_waitstream(chan, "");
231                         res = -1;
232                 }
233         }
234         LOCAL_USER_REMOVE(u);
235         return res;
236 }
237
238 int unload_module(void)
239 {
240         int res;
241
242         res = ast_unregister_application(app);
243
244         STANDARD_HANGUP_LOCALUSERS;
245         
246         return res;
247 }
248
249 int load_module(void)
250 {
251         return ast_register_application(app, auth_exec, synopsis, descrip);
252 }
253
254 const char *description(void)
255 {
256         return "Authentication Application";
257 }
258
259 int usecount(void)
260 {
261         int res;
262         STANDARD_USECOUNT(res);
263         return res;
264 }
265
266 const char *key(void)
267 {
268         return ASTERISK_GPL_KEY;
269 }