1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "ap_provider.h"
18#include "httpd.h"
19#include "http_config.h"
20#include "http_log.h"
21#include "http_request.h"
22#include "apr_lib.h"
23#include "apr_dbd.h"
24#include "mod_dbd.h"
25#include "apr_strings.h"
26#include "mod_auth.h"
27#include "apr_md5.h"
28#include "apu_version.h"
29
30module AP_MODULE_DECLARE_DATA authn_dbd_module;
31
32typedef struct {
33    const char *user;
34    const char *realm;
35} authn_dbd_conf;
36typedef struct {
37    const char *label;
38    const char *query;
39} authn_dbd_rec;
40
41/* optional function - look it up once in post_config */
42static ap_dbd_t *(*authn_dbd_acquire_fn)(request_rec*) = NULL;
43static void (*authn_dbd_prepare_fn)(server_rec*, const char*, const char*) = NULL;
44static APR_OPTIONAL_FN_TYPE(ap_authn_cache_store) *authn_cache_store = NULL;
45#define AUTHN_CACHE_STORE(r,user,realm,data) \
46    if (authn_cache_store != NULL) \
47        authn_cache_store((r), "dbd", (user), (realm), (data))
48
49static void *authn_dbd_cr_conf(apr_pool_t *pool, char *dummy)
50{
51    authn_dbd_conf *ret = apr_pcalloc(pool, sizeof(authn_dbd_conf));
52    return ret;
53}
54static void *authn_dbd_merge_conf(apr_pool_t *pool, void *BASE, void *ADD)
55{
56    authn_dbd_conf *add = ADD;
57    authn_dbd_conf *base = BASE;
58    authn_dbd_conf *ret = apr_palloc(pool, sizeof(authn_dbd_conf));
59    ret->user = (add->user == NULL) ? base->user : add->user;
60    ret->realm = (add->realm == NULL) ? base->realm : add->realm;
61    return ret;
62}
63static const char *authn_dbd_prepare(cmd_parms *cmd, void *cfg, const char *query)
64{
65    static unsigned int label_num = 0;
66    char *label;
67    const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
68    if (err)
69        return err;
70
71    if (authn_dbd_prepare_fn == NULL) {
72        authn_dbd_prepare_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_prepare);
73        if (authn_dbd_prepare_fn == NULL) {
74            return "You must load mod_dbd to enable AuthDBD functions";
75        }
76        authn_dbd_acquire_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire);
77    }
78    label = apr_psprintf(cmd->pool, "authn_dbd_%d", ++label_num);
79
80    authn_dbd_prepare_fn(cmd->server, query, label);
81
82    /* save the label here for our own use */
83    return ap_set_string_slot(cmd, cfg, label);
84}
85static const command_rec authn_dbd_cmds[] =
86{
87    AP_INIT_TAKE1("AuthDBDUserPWQuery", authn_dbd_prepare,
88                  (void *)APR_OFFSETOF(authn_dbd_conf, user), ACCESS_CONF,
89                  "Query used to fetch password for user"),
90    AP_INIT_TAKE1("AuthDBDUserRealmQuery", authn_dbd_prepare,
91                  (void *)APR_OFFSETOF(authn_dbd_conf, realm), ACCESS_CONF,
92                  "Query used to fetch password for user+realm"),
93    {NULL}
94};
95static authn_status authn_dbd_password(request_rec *r, const char *user,
96                                       const char *password)
97{
98    apr_status_t rv;
99    const char *dbd_password = NULL;
100    apr_dbd_prepared_t *statement;
101    apr_dbd_results_t *res = NULL;
102    apr_dbd_row_t *row = NULL;
103    int ret;
104
105    authn_dbd_conf *conf = ap_get_module_config(r->per_dir_config,
106                                                &authn_dbd_module);
107    ap_dbd_t *dbd = authn_dbd_acquire_fn(r);
108    if (dbd == NULL) {
109        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01653)
110                      "Failed to acquire database connection to look up "
111                      "user '%s'", user);
112        return AUTH_GENERAL_ERROR;
113    }
114
115    if (conf->user == NULL) {
116        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01654)
117                      "No AuthDBDUserPWQuery has been specified");
118        return AUTH_GENERAL_ERROR;
119    }
120
121    statement = apr_hash_get(dbd->prepared, conf->user, APR_HASH_KEY_STRING);
122    if (statement == NULL) {
123        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01655)
124                      "A prepared statement could not be found for "
125                      "AuthDBDUserPWQuery with the key '%s'", conf->user);
126        return AUTH_GENERAL_ERROR;
127    }
128    if ((ret = apr_dbd_pvselect(dbd->driver, r->pool, dbd->handle, &res,
129                                statement, 0, user, NULL) != 0)) {
130        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01656)
131                      "Query execution error looking up '%s' "
132                      "in database [%s]",
133                      user, apr_dbd_error(dbd->driver, dbd->handle, ret));
134        return AUTH_GENERAL_ERROR;
135    }
136    for (rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1);
137         rv != -1;
138         rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1)) {
139        if (rv != 0) {
140            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01657)
141                          "Error retrieving results while looking up '%s' "
142                          "in database", user);
143            return AUTH_GENERAL_ERROR;
144        }
145        if (dbd_password == NULL) {
146#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 3)
147            /* add the rest of the columns to the environment */
148            int i = 1;
149            const char *name;
150            for (name = apr_dbd_get_name(dbd->driver, res, i);
151                 name != NULL;
152                 name = apr_dbd_get_name(dbd->driver, res, i)) {
153
154                char *str = apr_pstrcat(r->pool, AUTHN_PREFIX,
155                                        name,
156                                        NULL);
157                int j = sizeof(AUTHN_PREFIX)-1; /* string length of "AUTHENTICATE_", excluding the trailing NIL */
158                while (str[j]) {
159                    if (!apr_isalnum(str[j])) {
160                        str[j] = '_';
161                    }
162                    else {
163                        str[j] = apr_toupper(str[j]);
164                    }
165                    j++;
166                }
167                apr_table_set(r->subprocess_env, str,
168                              apr_dbd_get_entry(dbd->driver, row, i));
169                i++;
170            }
171#endif
172            dbd_password = apr_dbd_get_entry(dbd->driver, row, 0);
173        }
174        /* we can't break out here or row won't get cleaned up */
175    }
176
177    if (!dbd_password) {
178        return AUTH_USER_NOT_FOUND;
179    }
180    AUTHN_CACHE_STORE(r, user, NULL, dbd_password);
181
182    rv = apr_password_validate(password, dbd_password);
183
184    if (rv != APR_SUCCESS) {
185        return AUTH_DENIED;
186    }
187
188    return AUTH_GRANTED;
189}
190static authn_status authn_dbd_realm(request_rec *r, const char *user,
191                                    const char *realm, char **rethash)
192{
193    apr_status_t rv;
194    const char *dbd_hash = NULL;
195    apr_dbd_prepared_t *statement;
196    apr_dbd_results_t *res = NULL;
197    apr_dbd_row_t *row = NULL;
198    int ret;
199
200    authn_dbd_conf *conf = ap_get_module_config(r->per_dir_config,
201                                                &authn_dbd_module);
202    ap_dbd_t *dbd = authn_dbd_acquire_fn(r);
203    if (dbd == NULL) {
204        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01658)
205                      "Failed to acquire database connection to look up "
206                      "user '%s:%s'", user, realm);
207        return AUTH_GENERAL_ERROR;
208    }
209    if (conf->realm == NULL) {
210        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01659)
211                      "No AuthDBDUserRealmQuery has been specified");
212        return AUTH_GENERAL_ERROR;
213    }
214    statement = apr_hash_get(dbd->prepared, conf->realm, APR_HASH_KEY_STRING);
215    if (statement == NULL) {
216        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01660)
217                      "A prepared statement could not be found for "
218                      "AuthDBDUserRealmQuery with the key '%s'", conf->realm);
219        return AUTH_GENERAL_ERROR;
220    }
221    if ((ret = apr_dbd_pvselect(dbd->driver, r->pool, dbd->handle, &res,
222                                statement, 0, user, realm, NULL) != 0)) {
223        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01661)
224                      "Query execution error looking up '%s:%s' "
225                      "in database [%s]",
226                      user, realm,
227                      apr_dbd_error(dbd->driver, dbd->handle, ret));
228        return AUTH_GENERAL_ERROR;
229    }
230    for (rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1);
231         rv != -1;
232         rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1)) {
233        if (rv != 0) {
234            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01662)
235                          "Error retrieving results while looking up '%s:%s' "
236                          "in database", user, realm);
237            return AUTH_GENERAL_ERROR;
238        }
239        if (dbd_hash == NULL) {
240#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 3)
241            /* add the rest of the columns to the environment */
242            int i = 1;
243            const char *name;
244            for (name = apr_dbd_get_name(dbd->driver, res, i);
245                 name != NULL;
246                 name = apr_dbd_get_name(dbd->driver, res, i)) {
247
248                char *str = apr_pstrcat(r->pool, AUTHN_PREFIX,
249                                        name,
250                                        NULL);
251                int j = sizeof(AUTHN_PREFIX)-1; /* string length of "AUTHENTICATE_", excluding the trailing NIL */
252                while (str[j]) {
253                    if (!apr_isalnum(str[j])) {
254                        str[j] = '_';
255                    }
256                    else {
257                        str[j] = apr_toupper(str[j]);
258                    }
259                    j++;
260                }
261                apr_table_set(r->subprocess_env, str,
262                              apr_dbd_get_entry(dbd->driver, row, i));
263                i++;
264            }
265#endif
266            dbd_hash = apr_dbd_get_entry(dbd->driver, row, 0);
267        }
268        /* we can't break out here or row won't get cleaned up */
269    }
270
271    if (!dbd_hash) {
272        return AUTH_USER_NOT_FOUND;
273    }
274    AUTHN_CACHE_STORE(r, user, realm, dbd_hash);
275    *rethash = apr_pstrdup(r->pool, dbd_hash);
276    return AUTH_USER_FOUND;
277}
278static void opt_retr(void)
279{
280    authn_cache_store = APR_RETRIEVE_OPTIONAL_FN(ap_authn_cache_store);
281}
282static void authn_dbd_hooks(apr_pool_t *p)
283{
284    static const authn_provider authn_dbd_provider = {
285        &authn_dbd_password,
286        &authn_dbd_realm
287    };
288
289    ap_register_auth_provider(p, AUTHN_PROVIDER_GROUP, "dbd",
290                              AUTHN_PROVIDER_VERSION,
291                              &authn_dbd_provider, AP_AUTH_INTERNAL_PER_CONF);
292    ap_hook_optional_fn_retrieve(opt_retr, NULL, NULL, APR_HOOK_MIDDLE);
293}
294AP_DECLARE_MODULE(authn_dbd) =
295{
296    STANDARD20_MODULE_STUFF,
297    authn_dbd_cr_conf,
298    authn_dbd_merge_conf,
299    NULL,
300    NULL,
301    authn_dbd_cmds,
302    authn_dbd_hooks
303};
304