Asterisk data retrieval API.
[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 "asterisk/lock.h"
33 #include "asterisk/file.h"
34 #include "asterisk/channel.h"
35 #include "asterisk/pbx.h"
36 #include "asterisk/module.h"
37 #include "asterisk/app.h"
38 #include "asterisk/astdb.h"
39 #include "asterisk/utils.h"
40
41 enum {
42         OPT_ACCOUNT = (1 << 0),
43         OPT_DATABASE = (1 << 1),
44         OPT_MULTIPLE = (1 << 3),
45         OPT_REMOVE = (1 << 4),
46 };
47
48 AST_APP_OPTIONS(auth_app_options, {
49         AST_APP_OPTION('a', OPT_ACCOUNT),
50         AST_APP_OPTION('d', OPT_DATABASE),
51         AST_APP_OPTION('m', OPT_MULTIPLE),
52         AST_APP_OPTION('r', OPT_REMOVE),
53 });
54
55
56 static const char app[] = "Authenticate";
57 /*** DOCUMENTATION
58         <application name="Authenticate" language="en_US">
59                 <synopsis>
60                         Authenticate a user
61                 </synopsis>
62                 <syntax>
63                         <parameter name="password" required="true">
64                                 <para>Password the user should know</para>
65                         </parameter>
66                         <parameter name="options" required="false">
67                                 <optionlist>
68                                         <option name="a">
69                                                 <para>Set the channels' account code to the password that is entered</para>
70                                         </option>
71                                         <option name="d">
72                                                 <para>Interpret the given path as database key, not a literal file</para>
73                                         </option>
74                                         <option name="m">
75                                                 <para>Interpret the given path as a file which contains a list of account
76                                                 codes and password hashes delimited with <literal>:</literal>, listed one per line in
77                                                 the file. When one of the passwords is matched, the channel will have
78                                                 its account code set to the corresponding account code in the file.</para>
79                                         </option>
80                                         <option name="r">
81                                                 <para>Remove the database key upon successful entry (valid with <literal>d</literal> only)</para>
82                                         </option>
83                                 </optionlist>
84                         </parameter>
85                         <parameter name="maxdigits" required="false">
86                                 <para>maximum acceptable number of digits. Stops reading after
87                                 maxdigits have been entered (without requiring the user to press the <literal>#</literal> key).
88                                 Defaults to 0 - no limit - wait for the user press the <literal>#</literal> key.</para>
89                         </parameter>
90                         <parameter name="prompt" required="false">
91                                 <para>Override the agent-pass prompt file.</para>
92                         </parameter>
93                 </syntax>
94                 <description>
95                         <para>This application asks the caller to enter a given password in order to continue dialplan execution.</para>
96                         <para>If the password begins with the <literal>/</literal> character, 
97                         it is interpreted as a file which contains a list of valid passwords, listed 1 password per line in the file.</para>
98                         <para>When using a database key, the value associated with the key can be anything.</para>
99                         <para>Users have three attempts to authenticate before the channel is hung up.</para>
100                 </description>
101                 <see-also>
102                         <ref type="application">VMAuthenticate</ref>
103                         <ref type="application">DISA</ref>
104                 </see-also>
105         </application>
106  ***/
107
108 static int auth_exec(struct ast_channel *chan, const char *data)
109 {
110         int res = 0, retries, maxdigits;
111         char passwd[256], *prompt = "agent-pass", *argcopy = NULL;
112         struct ast_flags flags = {0};
113
114         AST_DECLARE_APP_ARGS(arglist,
115                 AST_APP_ARG(password);
116                 AST_APP_ARG(options);
117                 AST_APP_ARG(maxdigits);
118                 AST_APP_ARG(prompt);
119         );
120
121         if (ast_strlen_zero(data)) {
122                 ast_log(LOG_WARNING, "Authenticate requires an argument(password)\n");
123                 return -1;
124         }
125
126         if (chan->_state != AST_STATE_UP) {
127                 if ((res = ast_answer(chan)))
128                         return -1;
129         }
130
131         argcopy = ast_strdupa(data);
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         if (!ast_strlen_zero(arglist.maxdigits)) {
139                 maxdigits = atoi(arglist.maxdigits);
140                 if ((maxdigits<1) || (maxdigits>sizeof(passwd)-2))
141                         maxdigits = sizeof(passwd) - 2;
142         } else {
143                 maxdigits = sizeof(passwd) - 2;
144         }
145
146         if (!ast_strlen_zero(arglist.prompt)) {
147                 prompt = arglist.prompt;
148         } else {
149                 prompt = "agent-pass";
150         }
151    
152         /* Start asking for password */
153         for (retries = 0; retries < 3; retries++) {
154                 if ((res = ast_app_getdata(chan, prompt, passwd, maxdigits, 0)) < 0)
155                         break;
156
157                 res = 0;
158
159                 if (arglist.password[0] != '/') {
160                         /* Compare against a fixed password */
161                         if (!strcmp(passwd, arglist.password))
162                                 break;
163                 } else if (ast_test_flag(&flags,OPT_DATABASE)) {
164                         char tmp[256];
165                         /* Compare against a database key */
166                         if (!ast_db_get(arglist.password + 1, passwd, tmp, sizeof(tmp))) {
167                                 /* It's a good password */
168                                 if (ast_test_flag(&flags,OPT_REMOVE))
169                                         ast_db_del(arglist.password + 1, passwd);
170                                 break;
171                         }
172                 } else {
173                         /* Compare against a file */
174                         FILE *f;
175                         char buf[256] = "", md5passwd[33] = "", *md5secret = NULL;
176
177                         if (!(f = fopen(arglist.password, "r"))) {
178                                 ast_log(LOG_WARNING, "Unable to open file '%s' for authentication: %s\n", arglist.password, strerror(errno));
179                                 continue;
180                         }
181
182                         for (;;) {
183                                 size_t len;
184
185                                 if (feof(f))
186                                         break;
187
188                                 if (!fgets(buf, sizeof(buf), f)) {
189                                         continue;
190                                 }
191
192                                 if (ast_strlen_zero(buf))
193                                         continue;
194
195                                 len = strlen(buf) - 1;
196                                 if (buf[len] == '\n')
197                                         buf[len] = '\0';
198
199                                 if (ast_test_flag(&flags, OPT_MULTIPLE)) {
200                                         md5secret = buf;
201                                         strsep(&md5secret, ":");
202                                         if (!md5secret)
203                                                 continue;
204                                         ast_md5_hash(md5passwd, passwd);
205                                         if (!strcmp(md5passwd, md5secret)) {
206                                                 if (ast_test_flag(&flags,OPT_ACCOUNT))
207                                                         ast_cdr_setaccount(chan, buf);
208                                                 break;
209                                         }
210                                 } else {
211                                         if (!strcmp(passwd, buf)) {
212                                                 if (ast_test_flag(&flags, OPT_ACCOUNT))
213                                                         ast_cdr_setaccount(chan, buf);
214                                                 break;
215                                         }
216                                 }
217                         }
218
219                         fclose(f);
220
221                         if (!ast_strlen_zero(buf)) {
222                                 if (ast_test_flag(&flags, OPT_MULTIPLE)) {
223                                         if (md5secret && !strcmp(md5passwd, md5secret))
224                                                 break;
225                                 } else {
226                                         if (!strcmp(passwd, buf))
227                                                 break;
228                                 }
229                         }
230                 }
231                 prompt = "auth-incorrect";
232         }
233
234         if ((retries < 3) && !res) {
235                 if (ast_test_flag(&flags,OPT_ACCOUNT) && !ast_test_flag(&flags,OPT_MULTIPLE))
236                         ast_cdr_setaccount(chan, passwd);
237                 if (!(res = ast_streamfile(chan, "auth-thankyou", chan->language)))
238                         res = ast_waitstream(chan, "");
239         } else {
240                 if (!ast_streamfile(chan, "vm-goodbye", chan->language))
241                         res = ast_waitstream(chan, "");
242                 res = -1;
243         }
244
245         return res;
246 }
247
248 static int unload_module(void)
249 {
250         return ast_unregister_application(app);
251 }
252
253 static int load_module(void)
254 {
255         if (ast_register_application_xml(app, auth_exec))
256                 return AST_MODULE_LOAD_FAILURE;
257         return AST_MODULE_LOAD_SUCCESS;
258 }
259
260 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Authentication Application");