db87c106f42c46171416b629bc8c52aac13b9c9a
[mod_auth_gssapi.git] / src / mod_auth_gssapi.c
1 /*
2    MOD AUTH GSSAPI
3
4    Copyright (C) 2014 Simo Sorce <simo@redhat.com>
5
6    Permission is hereby granted, free of charge, to any person obtaining a
7    copy of this software and associated documentation files (the "Software"),
8    to deal in the Software without restriction, including without limitation
9    the rights to use, copy, modify, merge, publish, distribute, sublicense,
10    and/or sell copies of the Software, and to permit persons to whom the
11    Software is furnished to do so, subject to the following conditions:
12
13    The above copyright notice and this permission notice shall be included in
14    all copies or substantial portions of the Software.
15
16    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22    DEALINGS IN THE SOFTWARE.
23 */
24
25 #include "mod_auth_gssapi.h"
26
27 #define MOD_AUTH_GSSAPI_VERSION PACKAGE_NAME "/" PACKAGE_VERSION
28
29 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
30
31 APLOG_USE_MODULE(auth_gssapi);
32
33 APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
34
35 static char *mag_status(request_rec *req, int type, uint32_t err)
36 {
37     uint32_t maj_ret, min_ret;
38     gss_buffer_desc text;
39     uint32_t msg_ctx;
40     char *msg_ret;
41     int len;
42
43     msg_ret = NULL;
44     msg_ctx = 0;
45     do {
46         maj_ret = gss_display_status(&min_ret, err, type,
47                                      GSS_C_NO_OID, &msg_ctx, &text);
48         if (maj_ret != GSS_S_COMPLETE) {
49             return msg_ret;
50         }
51
52         len = text.length;
53         if (msg_ret) {
54             msg_ret = apr_psprintf(req->pool, "%s, %*s",
55                                    msg_ret, len, (char *)text.value);
56         } else {
57             msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
58         }
59         gss_release_buffer(&min_ret, &text);
60     } while (msg_ctx != 0);
61
62     return msg_ret;
63 }
64
65 static char *mag_error(request_rec *req, const char *msg,
66                        uint32_t maj, uint32_t min)
67 {
68     char *msg_maj;
69     char *msg_min;
70
71     msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
72     msg_min = mag_status(req, GSS_C_MECH_CODE, min);
73     return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
74 }
75
76 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
77
78 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
79                            apr_pool_t *temp, server_rec *s)
80 {
81     /* FIXME: create mutex to deal with connections and contexts ? */
82     mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
83     mag_post_config_session();
84     ap_add_version_component(cfgpool, MOD_AUTH_GSSAPI_VERSION);
85
86     return OK;
87 }
88
89 static int mag_pre_connection(conn_rec *c, void *csd)
90 {
91     struct mag_conn *mc;
92
93     mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
94     if (!mc) return DECLINED;
95
96     mc->parent = c->pool;
97     ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
98     return OK;
99 }
100
101 static apr_status_t mag_conn_destroy(void *ptr)
102 {
103     struct mag_conn *mc = (struct mag_conn *)ptr;
104     uint32_t min;
105
106     if (mc->ctx) {
107         (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
108         mc->established = false;
109     }
110     return APR_SUCCESS;
111 }
112
113 static bool mag_conn_is_https(conn_rec *c)
114 {
115     if (mag_is_https) {
116         if (mag_is_https(c)) return true;
117     }
118
119     return false;
120 }
121
122 static void mag_store_deleg_creds(request_rec *req,
123                                   char *dir, char *clientname,
124                                   gss_cred_id_t delegated_cred,
125                                   char **ccachefile)
126 {
127     gss_key_value_element_desc element;
128     gss_key_value_set_desc store;
129     char *value;
130     uint32_t maj, min;
131
132     value = apr_psprintf(req->pool, "FILE:%s/%s", dir, clientname);
133     if (!value) {
134         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, NULL,
135                      "OOM storing delegated credentials");
136         return;
137     }
138
139     element.key = "ccache";
140     element.value = value;
141     store.elements = &element;
142     store.count = 1;
143
144     maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
145                               GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
146     if (GSS_ERROR(maj)) {
147         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
148                       mag_error(req, "failed to store delegated creds",
149                                 maj, min));
150     }
151
152     *ccachefile = value;
153 }
154
155 static int mag_auth(request_rec *req)
156 {
157     const char *type;
158     const char *auth_type;
159     struct mag_config *cfg;
160     const char *auth_header;
161     char *auth_header_type;
162     char *auth_header_value;
163     int ret = HTTP_UNAUTHORIZED;
164     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
165     gss_ctx_id_t *pctx;
166     gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
167     gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
168     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
169     gss_name_t client = GSS_C_NO_NAME;
170     gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
171     gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
172     gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
173     gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
174     uint32_t flags;
175     uint32_t vtime;
176     uint32_t maj, min;
177     char *reply;
178     size_t replen;
179     char *clientname;
180     gss_OID mech_type = GSS_C_NO_OID;
181     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
182     struct mag_conn *mc = NULL;
183     bool is_basic = false;
184     gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
185     gss_name_t server = GSS_C_NO_NAME;
186 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
187     const char *user_ccache = NULL;
188     char *orig_ccache = NULL;
189 #endif
190
191     type = ap_auth_type(req);
192     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
193         return DECLINED;
194     }
195
196     /* ignore auth for subrequests */
197     if (!ap_is_initial_req(req)) {
198         return OK;
199     }
200
201     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
202
203     if (cfg->ssl_only) {
204         if (!mag_conn_is_https(req->connection)) {
205             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
206                           "Not a TLS connection, refusing to authenticate!");
207             goto done;
208         }
209     }
210
211     if (cfg->gss_conn_ctx) {
212         mc = (struct mag_conn *)ap_get_module_config(
213                                                 req->connection->conn_config,
214                                                 &auth_gssapi_module);
215         if (!mc) {
216             ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
217                           "Failed to retrieve connection context!");
218             goto done;
219         }
220     }
221
222     /* if available, session always supersedes connection bound data */
223     if (cfg->use_sessions) {
224         mag_check_session(req, cfg, &mc);
225     }
226
227     if (mc) {
228         /* register the context in the memory pool, so it can be freed
229          * when the connection/request is terminated */
230         apr_pool_userdata_set(mc, "mag_conn_ptr",
231                               mag_conn_destroy, mc->parent);
232
233         if (mc->established) {
234             ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
235                           "Already established context found!");
236             apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
237             req->ap_auth_type = apr_pstrdup(req->pool, mc->auth_type);
238             req->user = apr_pstrdup(req->pool, mc->user_name);
239             ret = OK;
240             goto done;
241         }
242         pctx = &mc->ctx;
243     } else {
244         pctx = &ctx;
245     }
246
247     auth_header = apr_table_get(req->headers_in, "Authorization");
248     if (!auth_header) goto done;
249
250     auth_header_type = ap_getword_white(req->pool, &auth_header);
251     if (!auth_header_type) goto done;
252
253     if (strcasecmp(auth_header_type, "Negotiate") == 0) {
254         auth_type = "Negotiate";
255
256         auth_header_value = ap_getword_white(req->pool, &auth_header);
257         if (!auth_header_value) goto done;
258         input.length = apr_base64_decode_len(auth_header_value) + 1;
259         input.value = apr_pcalloc(req->pool, input.length);
260         if (!input.value) goto done;
261         input.length = apr_base64_decode(input.value, auth_header_value);
262     } else if ((strcasecmp(auth_header_type, "Basic") == 0) &&
263                (cfg->use_basic_auth == true)) {
264         auth_type = "Basic";
265         is_basic = true;
266
267         gss_buffer_desc ba_user;
268         gss_buffer_desc ba_pwd;
269
270         ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
271         if (!ba_pwd.value) goto done;
272         ba_user.value = ap_getword_nulls_nc(req->pool,
273                                             (char **)&ba_pwd.value, ':');
274         if (!ba_user.value) goto done;
275         if (((char *)ba_user.value)[0] == '\0' ||
276             ((char *)ba_pwd.value)[0] == '\0') {
277             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
278                           "Invalid empty user or password for Basic Auth");
279             goto done;
280         }
281         ba_user.length = strlen(ba_user.value);
282         ba_pwd.length = strlen(ba_pwd.value);
283         maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &client);
284         if (GSS_ERROR(maj)) {
285             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
286                           "In Basic Auth, %s",
287                           mag_error(req, "gss_import_name() failed",
288                                     maj, min));
289             goto done;
290         }
291 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
292         /* Set a per-thread ccache in case we are using kerberos,
293          * it is not elegant but avoids interference between threads */
294         long long unsigned int rndname;
295         apr_status_t rs;
296         rs = apr_generate_random_bytes((unsigned char *)(&rndname),
297                                        sizeof(long long unsigned int));
298         if (rs != APR_SUCCESS) {
299             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
300                           "Failed to generate random ccache name");
301             goto done;
302         }
303         user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
304         maj = gss_krb5_ccache_name(&min, user_ccache,
305                                    (const char **)&orig_ccache);
306         if (GSS_ERROR(maj)) {
307             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
308                           "In Basic Auth, %s",
309                           mag_error(req, "gss_krb5_ccache_name() "
310                                     "failed", maj, min));
311             goto done;
312         }
313 #endif
314         maj = gss_acquire_cred_with_password(&min, client, &ba_pwd,
315                                              GSS_C_INDEFINITE,
316                                              GSS_C_NO_OID_SET,
317                                              GSS_C_INITIATE,
318                                              &user_cred, NULL, NULL);
319         if (GSS_ERROR(maj)) {
320             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
321                           "In Basic Auth, %s",
322                           mag_error(req, "gss_acquire_cred_with_password() "
323                                     "failed", maj, min));
324             goto done;
325         }
326         gss_release_name(&min, &client);
327     } else {
328         goto done;
329     }
330
331     req->ap_auth_type = apr_pstrdup(req->pool, auth_type);
332
333 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
334     if (cfg->use_s4u2proxy) {
335         cred_usage = GSS_C_BOTH;
336     }
337     if (cfg->cred_store) {
338         maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
339                                     GSS_C_NO_OID_SET, cred_usage,
340                                     cfg->cred_store, &acquired_cred,
341                                     NULL, NULL);
342         if (GSS_ERROR(maj)) {
343             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
344                           mag_error(req, "gss_acquire_cred_from() failed",
345                                     maj, min));
346             goto done;
347         }
348     }
349 #endif
350
351     if (is_basic) {
352         if (!acquired_cred) {
353             /* Try to acquire default creds */
354             maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
355                                    GSS_C_NO_OID_SET, cred_usage,
356                                    &acquired_cred, NULL, NULL);
357             if (GSS_ERROR(maj)) {
358                 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
359                               "%s", mag_error(req, "gss_acquire_cred_from()"
360                                               " failed", maj, min));
361                 goto done;
362             }
363         }
364         maj = gss_inquire_cred(&min, acquired_cred, &server,
365                                NULL, NULL, NULL);
366         if (GSS_ERROR(maj)) {
367             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
368                           "%s", mag_error(req, "gss_inquired_cred_() "
369                                           "failed", maj, min));
370             goto done;
371         }
372         /* output and input are inverted here, this is intentional */
373         maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
374                                    GSS_C_NO_OID, 0, 300,
375                                    GSS_C_NO_CHANNEL_BINDINGS, &output,
376                                    NULL, &input, NULL, NULL);
377         if (GSS_ERROR(maj)) {
378             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
379                           "%s", mag_error(req, "gss_init_sec_context() "
380                                           "failed", maj, min));
381             goto done;
382         }
383     }
384
385     maj = gss_accept_sec_context(&min, pctx, acquired_cred,
386                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
387                                  &client, &mech_type, &output, &flags, &vtime,
388                                  &delegated_cred);
389     if (GSS_ERROR(maj)) {
390         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
391                       mag_error(req, "gss_accept_sec_context() failed",
392                                 maj, min));
393         goto done;
394     }
395     if (is_basic) {
396         while (maj == GSS_S_CONTINUE_NEEDED) {
397             gss_release_buffer(&min, &input);
398             /* output and input are inverted here, this is intentional */
399             maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
400                                        GSS_C_NO_OID, 0, 300,
401                                        GSS_C_NO_CHANNEL_BINDINGS, &output,
402                                        NULL, &input, NULL, NULL);
403             if (GSS_ERROR(maj)) {
404                 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
405                               "%s", mag_error(req, "gss_init_sec_context() "
406                                               "failed", maj, min));
407                 goto done;
408             }
409             gss_release_buffer(&min, &output);
410             maj = gss_accept_sec_context(&min, pctx, acquired_cred,
411                                          &input, GSS_C_NO_CHANNEL_BINDINGS,
412                                          &client, &mech_type, &output, &flags,
413                                          &vtime, &delegated_cred);
414             if (GSS_ERROR(maj)) {
415                 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
416                               "%s", mag_error(req, "gss_accept_sec_context()"
417                                               " failed", maj, min));
418                 goto done;
419             }
420         }
421     } else if (maj == GSS_S_CONTINUE_NEEDED) {
422         if (!mc) {
423             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
424                           "Mechanism needs continuation but neither "
425                           "GssapiConnectionBound nor "
426                           "GssapiUseSessions are available");
427             gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
428             gss_release_buffer(&min, &output);
429             output.length = 0;
430         }
431         /* auth not complete send token and wait next packet */
432         goto done;
433     }
434
435     /* Always set the GSS name in an env var */
436     maj = gss_display_name(&min, client, &name, NULL);
437     if (GSS_ERROR(maj)) {
438         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
439                       mag_error(req, "gss_display_name() failed",
440                                 maj, min));
441         goto done;
442     }
443     clientname = apr_pstrndup(req->pool, name.value, name.length);
444     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
445
446 #ifdef HAVE_GSS_STORE_CRED_INTO
447     if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
448         char *ccachefile = NULL;
449
450         mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
451                               delegated_cred, &ccachefile);
452
453         if (ccachefile) {
454             apr_table_set(req->subprocess_env, "KRB5CCNAME", ccachefile);
455         }
456     }
457 #endif
458
459     if (cfg->map_to_local) {
460         maj = gss_localname(&min, client, mech_type, &lname);
461         if (maj != GSS_S_COMPLETE) {
462             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
463                           mag_error(req, "gss_localname() failed", maj, min));
464             goto done;
465         }
466         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
467     } else {
468         req->user = clientname;
469     }
470
471     if (mc) {
472         mc->user_name = apr_pstrdup(mc->parent, req->user);
473         mc->gss_name = apr_pstrdup(mc->parent, clientname);
474         mc->established = true;
475         if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
476             vtime = MIN_SESS_EXP_TIME;
477         }
478         mc->expiration = time(NULL) + vtime;
479         if (cfg->use_sessions) {
480             mag_attempt_session(req, cfg, mc);
481         }
482         mc->auth_type = auth_type;
483     }
484
485     ret = OK;
486
487 done:
488     if (ret == HTTP_UNAUTHORIZED) {
489         if (output.length != 0) {
490             replen = apr_base64_encode_len(output.length) + 1;
491             reply = apr_pcalloc(req->pool, 10 + replen);
492             if (reply) {
493                 memcpy(reply, "Negotiate ", 10);
494                 apr_base64_encode(&reply[10], output.value, output.length);
495                 apr_table_add(req->err_headers_out,
496                               "WWW-Authenticate", reply);
497             }
498         } else {
499             apr_table_add(req->err_headers_out,
500                           "WWW-Authenticate", "Negotiate");
501             if (cfg->use_basic_auth) {
502                 apr_table_add(req->err_headers_out,
503                               "WWW-Authenticate",
504                               apr_psprintf(req->pool, "Basic realm=\"%s\"",
505                                            ap_auth_name(req)));
506             }
507         }
508     }
509 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
510     if (user_ccache != NULL) {
511         maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
512         if (maj != GSS_S_COMPLETE) {
513             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
514                           "Failed to restore per-thread ccache, %s",
515                           mag_error(req, "gss_krb5_ccache_name() "
516                                     "failed", maj, min));
517         }
518     }
519     free(orig_ccache);
520     orig_ccache = NULL;
521 #endif
522     gss_delete_sec_context(&min, &user_ctx, &output);
523     gss_release_cred(&min, &user_cred);
524     gss_release_cred(&min, &acquired_cred);
525     gss_release_cred(&min, &delegated_cred);
526     gss_release_buffer(&min, &output);
527     gss_release_name(&min, &client);
528     gss_release_name(&min, &server);
529     gss_release_buffer(&min, &name);
530     gss_release_buffer(&min, &lname);
531     return ret;
532 }
533
534
535 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
536 {
537     struct mag_config *cfg;
538
539     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
540     if (!cfg) return NULL;
541     cfg->pool = p;
542
543     return cfg;
544 }
545
546 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
547 {
548     struct mag_config *cfg = (struct mag_config *)mconfig;
549     cfg->ssl_only = on ? true : false;
550     return NULL;
551 }
552
553 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
554 {
555     struct mag_config *cfg = (struct mag_config *)mconfig;
556     cfg->map_to_local = on ? true : false;
557     return NULL;
558 }
559
560 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
561 {
562     struct mag_config *cfg = (struct mag_config *)mconfig;
563     cfg->gss_conn_ctx = on ? true : false;
564     return NULL;
565 }
566
567 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
568 {
569     struct mag_config *cfg = (struct mag_config *)mconfig;
570     cfg->use_sessions = on ? true : false;
571     return NULL;
572 }
573
574 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
575 {
576     struct mag_config *cfg = (struct mag_config *)mconfig;
577     cfg->use_s4u2proxy = on ? true : false;
578
579     if (cfg->deleg_ccache_dir == NULL) {
580         cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
581         if (!cfg->deleg_ccache_dir) {
582             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0,
583                          parms->server, "%s", "OOM setting deleg_ccache_dir.");
584         }
585     }
586     return NULL;
587 }
588
589 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
590 {
591     struct mag_config *cfg = (struct mag_config *)mconfig;
592     struct databuf keys;
593     unsigned char *val;
594     apr_status_t rc;
595     const char *k;
596     int l;
597
598     if (strncmp(w, "key:", 4) != 0) {
599         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
600                      "Invalid key format, expected prefix 'key:'");
601         return NULL;
602     }
603     k = w + 4;
604
605     l = apr_base64_decode_len(k);
606     val = apr_palloc(parms->temp_pool, l);
607     if (!val) {
608         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
609                      "Failed to get memory to decode key");
610         return NULL;
611     }
612
613     keys.length = (int)apr_base64_decode_binary(val, k);
614     keys.value = (unsigned char *)val;
615
616     if (keys.length != 32) {
617         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
618                      "Invalid key lenght, expected 32 got %d", keys.length);
619         return NULL;
620     }
621
622     rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
623     if (rc != OK) {
624         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
625                      "Failed to import sealing key!");
626     }
627     return NULL;
628 }
629
630 #define MAX_CRED_OPTIONS 10
631
632 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
633                                   const char *w)
634 {
635     struct mag_config *cfg = (struct mag_config *)mconfig;
636     gss_key_value_element_desc *elements;
637     uint32_t count;
638     size_t size;
639     const char *p;
640     char *value;
641     char *key;
642
643     p = strchr(w, ':');
644     if (!p) {
645         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
646                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
647         return NULL;
648     }
649
650     key = apr_pstrndup(parms->pool, w, (p-w));
651     value = apr_pstrdup(parms->pool, p + 1);
652     if (!key || !value) {
653         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
654                      "%s", "OOM handling GssapiCredStore option");
655         return NULL;
656     }
657
658     if (!cfg->cred_store) {
659         cfg->cred_store = apr_pcalloc(parms->pool,
660                                       sizeof(gss_key_value_set_desc));
661         if (!cfg->cred_store) {
662             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
663                          "%s", "OOM handling GssapiCredStore option");
664             return NULL;
665         }
666         size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
667         cfg->cred_store->elements = apr_palloc(parms->pool, size);
668         if (!cfg->cred_store->elements) {
669             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
670                          "%s", "OOM handling GssapiCredStore option");
671         }
672     }
673
674     elements = cfg->cred_store->elements;
675     count = cfg->cred_store->count;
676
677     if (count >= MAX_CRED_OPTIONS) {
678         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
679                      "Too many GssapiCredStore options (MAX: %d)",
680                      MAX_CRED_OPTIONS);
681         return NULL;
682     }
683     cfg->cred_store->count++;
684
685     elements[count].key = key;
686     elements[count].value = value;
687
688     return NULL;
689 }
690
691 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
692                                         const char *value)
693 {
694     struct mag_config *cfg = (struct mag_config *)mconfig;
695
696     cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
697     if (!cfg->deleg_ccache_dir) {
698         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
699                      "%s", "OOM handling GssapiDelegCcacheDir option");
700     }
701
702     return NULL;
703 }
704
705 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
706 {
707     struct mag_config *cfg = (struct mag_config *)mconfig;
708
709     cfg->use_basic_auth = on ? true : false;
710     return NULL;
711 }
712
713 static const command_rec mag_commands[] = {
714     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
715                   "Work only if connection is SSL Secured"),
716     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
717                   "Translate principals to local names"),
718     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
719                   "Authentication is bound to the TCP connection"),
720     AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
721                   "Authentication uses mod_sessions to hold status"),
722     AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
723                      "Key Used to seal session data."),
724 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
725     AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
726                   "Initializes credentials for s4u2proxy usage"),
727 #endif
728 #ifdef HAVE_GSS_STORE_CRED_INTO
729     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
730                     "Credential Store"),
731     AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
732                      OR_AUTHCFG, "Directory to store delegated credentials"),
733 #endif
734 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
735     AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
736                      "Allows use of Basic Auth for authentication"),
737 #endif
738     { NULL }
739 };
740
741 static void
742 mag_register_hooks(apr_pool_t *p)
743 {
744     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
745     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
746     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
747 }
748
749 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
750 {
751     STANDARD20_MODULE_STUFF,
752     mag_create_dir_config,
753     NULL,
754     NULL,
755     NULL,
756     mag_commands,
757     mag_register_hooks
758 };