Escape principal name to remove the path separator
[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 char *escape(apr_pool_t *pool, const char *name,
123                     char find, const char *replace)
124 {
125     char *escaped = NULL;
126     char *namecopy;
127     char *n;
128     char *p;
129
130     namecopy = apr_pstrdup(pool, name);
131     if (!namecopy) goto done;
132
133     p = strchr(namecopy, find);
134     if (!p) return namecopy;
135
136     /* first segment */
137     n = namecopy;
138     while (p) {
139         /* terminate previous segment */
140         *p = '\0';
141         if (escaped) {
142             escaped = apr_pstrcat(pool, escaped, n, replace, NULL);
143         } else {
144             escaped = apr_pstrcat(pool, n, replace, NULL);
145         }
146         if (!escaped) goto done;
147         /* move to next segment */
148         n = p + 1;
149         p = strchr(n, find);
150     }
151     /* append last segment if any */
152     if (*n) {
153         escaped = apr_pstrcat(pool, escaped, n, NULL);
154     }
155
156 done:
157     if (!escaped) {
158         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, NULL,
159                      "OOM escaping name");
160     }
161     return escaped;
162 }
163
164 static void mag_store_deleg_creds(request_rec *req,
165                                   char *dir, char *clientname,
166                                   gss_cred_id_t delegated_cred,
167                                   char **ccachefile)
168 {
169     gss_key_value_element_desc element;
170     gss_key_value_set_desc store;
171     char *value;
172     uint32_t maj, min;
173     char *escaped;
174
175     /* We need to escape away '/', we can't have path separators in
176      * a ccache file name */
177     /* first double escape the esacping char (~) if any */
178     escaped = escape(req->pool, clientname, '~', "~~");
179     if (!escaped) return;
180     /* then escape away the separator (/) if any */
181     escaped = escape(req->pool, escaped, '/', "~");
182     if (!escaped) return;
183
184     value = apr_psprintf(req->pool, "FILE:%s/%s", dir, escaped);
185     if (!value) {
186         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, NULL,
187                      "OOM storing delegated credentials");
188         return;
189     }
190
191     element.key = "ccache";
192     element.value = value;
193     store.elements = &element;
194     store.count = 1;
195
196     maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
197                               GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
198     if (GSS_ERROR(maj)) {
199         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
200                       mag_error(req, "failed to store delegated creds",
201                                 maj, min));
202     }
203
204     *ccachefile = value;
205 }
206
207 static int mag_auth(request_rec *req)
208 {
209     const char *type;
210     const char *auth_type;
211     struct mag_config *cfg;
212     const char *auth_header;
213     char *auth_header_type;
214     char *auth_header_value;
215     int ret = HTTP_UNAUTHORIZED;
216     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
217     gss_ctx_id_t *pctx;
218     gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
219     gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
220     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
221     gss_name_t client = GSS_C_NO_NAME;
222     gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
223     gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
224     gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
225     gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
226     uint32_t flags;
227     uint32_t vtime;
228     uint32_t maj, min;
229     char *reply;
230     size_t replen;
231     char *clientname;
232     gss_OID mech_type = GSS_C_NO_OID;
233     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
234     struct mag_conn *mc = NULL;
235     bool is_basic = false;
236     gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
237     gss_name_t server = GSS_C_NO_NAME;
238 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
239     const char *user_ccache = NULL;
240     const char *orig_ccache = NULL;
241 #endif
242
243     type = ap_auth_type(req);
244     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
245         return DECLINED;
246     }
247
248     /* ignore auth for subrequests */
249     if (!ap_is_initial_req(req)) {
250         return OK;
251     }
252
253     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
254
255     if (cfg->ssl_only) {
256         if (!mag_conn_is_https(req->connection)) {
257             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
258                           "Not a TLS connection, refusing to authenticate!");
259             goto done;
260         }
261     }
262
263     if (cfg->gss_conn_ctx) {
264         mc = (struct mag_conn *)ap_get_module_config(
265                                                 req->connection->conn_config,
266                                                 &auth_gssapi_module);
267         if (!mc) {
268             ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
269                           "Failed to retrieve connection context!");
270             goto done;
271         }
272     }
273
274     /* if available, session always supersedes connection bound data */
275     if (cfg->use_sessions) {
276         mag_check_session(req, cfg, &mc);
277     }
278
279     if (mc) {
280         /* register the context in the memory pool, so it can be freed
281          * when the connection/request is terminated */
282         apr_pool_userdata_set(mc, "mag_conn_ptr",
283                               mag_conn_destroy, mc->parent);
284
285         if (mc->established) {
286             ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
287                           "Already established context found!");
288             apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
289             req->ap_auth_type = apr_pstrdup(req->pool, mc->auth_type);
290             req->user = apr_pstrdup(req->pool, mc->user_name);
291             ret = OK;
292             goto done;
293         }
294         pctx = &mc->ctx;
295     } else {
296         pctx = &ctx;
297     }
298
299     auth_header = apr_table_get(req->headers_in, "Authorization");
300     if (!auth_header) goto done;
301
302     auth_header_type = ap_getword_white(req->pool, &auth_header);
303     if (!auth_header_type) goto done;
304
305     if (strcasecmp(auth_header_type, "Negotiate") == 0) {
306         auth_type = "Negotiate";
307
308         auth_header_value = ap_getword_white(req->pool, &auth_header);
309         if (!auth_header_value) goto done;
310         input.length = apr_base64_decode_len(auth_header_value) + 1;
311         input.value = apr_pcalloc(req->pool, input.length);
312         if (!input.value) goto done;
313         input.length = apr_base64_decode(input.value, auth_header_value);
314     } else if ((strcasecmp(auth_header_type, "Basic") == 0) &&
315                (cfg->use_basic_auth == true)) {
316         auth_type = "Basic";
317         is_basic = true;
318
319         gss_buffer_desc ba_user;
320         gss_buffer_desc ba_pwd;
321
322         ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
323         if (!ba_pwd.value) goto done;
324         ba_user.value = ap_getword_nulls_nc(req->pool,
325                                             (char **)&ba_pwd.value, ':');
326         if (!ba_user.value) goto done;
327         if (((char *)ba_user.value)[0] == '\0' ||
328             ((char *)ba_pwd.value)[0] == '\0') {
329             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
330                           "Invalid empty user or password for Basic Auth");
331             goto done;
332         }
333         ba_user.length = strlen(ba_user.value);
334         ba_pwd.length = strlen(ba_pwd.value);
335         maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &client);
336         if (GSS_ERROR(maj)) {
337             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
338                           "In Basic Auth, %s",
339                           mag_error(req, "gss_import_name() failed",
340                                     maj, min));
341             goto done;
342         }
343 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
344         /* Set a per-thread ccache in case we are using kerberos,
345          * it is not elegant but avoids interference between threads */
346         long long unsigned int rndname;
347         apr_status_t rs;
348         rs = apr_generate_random_bytes((unsigned char *)(&rndname),
349                                        sizeof(long long unsigned int));
350         if (rs != APR_SUCCESS) {
351             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
352                           "Failed to generate random ccache name");
353             goto done;
354         }
355         user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
356         maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
357         if (GSS_ERROR(maj)) {
358             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
359                           "In Basic Auth, %s",
360                           mag_error(req, "gss_krb5_ccache_name() "
361                                     "failed", maj, min));
362             goto done;
363         }
364 #endif
365         maj = gss_acquire_cred_with_password(&min, client, &ba_pwd,
366                                              GSS_C_INDEFINITE,
367                                              GSS_C_NO_OID_SET,
368                                              GSS_C_INITIATE,
369                                              &user_cred, NULL, NULL);
370         if (GSS_ERROR(maj)) {
371             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
372                           "In Basic Auth, %s",
373                           mag_error(req, "gss_acquire_cred_with_password() "
374                                     "failed", maj, min));
375             goto done;
376         }
377         gss_release_name(&min, &client);
378     } else {
379         goto done;
380     }
381
382     req->ap_auth_type = apr_pstrdup(req->pool, auth_type);
383
384 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
385     if (cfg->use_s4u2proxy) {
386         cred_usage = GSS_C_BOTH;
387     }
388     if (cfg->cred_store) {
389         maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
390                                     GSS_C_NO_OID_SET, cred_usage,
391                                     cfg->cred_store, &acquired_cred,
392                                     NULL, NULL);
393         if (GSS_ERROR(maj)) {
394             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
395                           mag_error(req, "gss_acquire_cred_from() failed",
396                                     maj, min));
397             goto done;
398         }
399     }
400 #endif
401
402     if (is_basic) {
403         if (!acquired_cred) {
404             /* Try to acquire default creds */
405             maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
406                                    GSS_C_NO_OID_SET, cred_usage,
407                                    &acquired_cred, NULL, NULL);
408             if (GSS_ERROR(maj)) {
409                 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
410                               "%s", mag_error(req, "gss_acquire_cred_from()"
411                                               " failed", maj, min));
412                 goto done;
413             }
414         }
415         maj = gss_inquire_cred(&min, acquired_cred, &server,
416                                NULL, NULL, NULL);
417         if (GSS_ERROR(maj)) {
418             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
419                           "%s", mag_error(req, "gss_inquired_cred_() "
420                                           "failed", maj, min));
421             goto done;
422         }
423         /* output and input are inverted here, this is intentional */
424         maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
425                                    GSS_C_NO_OID, 0, 300,
426                                    GSS_C_NO_CHANNEL_BINDINGS, &output,
427                                    NULL, &input, NULL, NULL);
428         if (GSS_ERROR(maj)) {
429             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
430                           "%s", mag_error(req, "gss_init_sec_context() "
431                                           "failed", maj, min));
432             goto done;
433         }
434     }
435
436     maj = gss_accept_sec_context(&min, pctx, acquired_cred,
437                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
438                                  &client, &mech_type, &output, &flags, &vtime,
439                                  &delegated_cred);
440     if (GSS_ERROR(maj)) {
441         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
442                       mag_error(req, "gss_accept_sec_context() failed",
443                                 maj, min));
444         goto done;
445     }
446     if (is_basic) {
447         while (maj == GSS_S_CONTINUE_NEEDED) {
448             gss_release_buffer(&min, &input);
449             /* output and input are inverted here, this is intentional */
450             maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
451                                        GSS_C_NO_OID, 0, 300,
452                                        GSS_C_NO_CHANNEL_BINDINGS, &output,
453                                        NULL, &input, NULL, NULL);
454             if (GSS_ERROR(maj)) {
455                 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
456                               "%s", mag_error(req, "gss_init_sec_context() "
457                                               "failed", maj, min));
458                 goto done;
459             }
460             gss_release_buffer(&min, &output);
461             maj = gss_accept_sec_context(&min, pctx, acquired_cred,
462                                          &input, GSS_C_NO_CHANNEL_BINDINGS,
463                                          &client, &mech_type, &output, &flags,
464                                          &vtime, &delegated_cred);
465             if (GSS_ERROR(maj)) {
466                 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
467                               "%s", mag_error(req, "gss_accept_sec_context()"
468                                               " failed", maj, min));
469                 goto done;
470             }
471         }
472     } else if (maj == GSS_S_CONTINUE_NEEDED) {
473         if (!mc) {
474             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
475                           "Mechanism needs continuation but neither "
476                           "GssapiConnectionBound nor "
477                           "GssapiUseSessions are available");
478             gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
479             gss_release_buffer(&min, &output);
480             output.length = 0;
481         }
482         /* auth not complete send token and wait next packet */
483         goto done;
484     }
485
486     /* Always set the GSS name in an env var */
487     maj = gss_display_name(&min, client, &name, NULL);
488     if (GSS_ERROR(maj)) {
489         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
490                       mag_error(req, "gss_display_name() failed",
491                                 maj, min));
492         goto done;
493     }
494     clientname = apr_pstrndup(req->pool, name.value, name.length);
495     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
496
497 #ifdef HAVE_GSS_STORE_CRED_INTO
498     if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
499         char *ccachefile = NULL;
500
501         mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
502                               delegated_cred, &ccachefile);
503
504         if (ccachefile) {
505             apr_table_set(req->subprocess_env, "KRB5CCNAME", ccachefile);
506         }
507     }
508 #endif
509
510     if (cfg->map_to_local) {
511         maj = gss_localname(&min, client, mech_type, &lname);
512         if (maj != GSS_S_COMPLETE) {
513             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
514                           mag_error(req, "gss_localname() failed", maj, min));
515             goto done;
516         }
517         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
518     } else {
519         req->user = clientname;
520     }
521
522     if (mc) {
523         mc->user_name = apr_pstrdup(mc->parent, req->user);
524         mc->gss_name = apr_pstrdup(mc->parent, clientname);
525         mc->established = true;
526         if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
527             vtime = MIN_SESS_EXP_TIME;
528         }
529         mc->expiration = time(NULL) + vtime;
530         if (cfg->use_sessions) {
531             mag_attempt_session(req, cfg, mc);
532         }
533         mc->auth_type = auth_type;
534     }
535
536     ret = OK;
537
538 done:
539     if (ret == HTTP_UNAUTHORIZED) {
540         if (output.length != 0) {
541             replen = apr_base64_encode_len(output.length) + 1;
542             reply = apr_pcalloc(req->pool, 10 + replen);
543             if (reply) {
544                 memcpy(reply, "Negotiate ", 10);
545                 apr_base64_encode(&reply[10], output.value, output.length);
546                 apr_table_add(req->err_headers_out,
547                               "WWW-Authenticate", reply);
548             }
549         } else {
550             apr_table_add(req->err_headers_out,
551                           "WWW-Authenticate", "Negotiate");
552             if (cfg->use_basic_auth) {
553                 apr_table_add(req->err_headers_out,
554                               "WWW-Authenticate",
555                               apr_psprintf(req->pool, "Basic realm=\"%s\"",
556                                            ap_auth_name(req)));
557             }
558         }
559     }
560 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
561     if (user_ccache != NULL) {
562         maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
563         if (maj != GSS_S_COMPLETE) {
564             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
565                           "Failed to restore per-thread ccache, %s",
566                           mag_error(req, "gss_krb5_ccache_name() "
567                                     "failed", maj, min));
568         }
569     }
570 #endif
571     gss_delete_sec_context(&min, &user_ctx, &output);
572     gss_release_cred(&min, &user_cred);
573     gss_release_cred(&min, &acquired_cred);
574     gss_release_cred(&min, &delegated_cred);
575     gss_release_buffer(&min, &output);
576     gss_release_name(&min, &client);
577     gss_release_name(&min, &server);
578     gss_release_buffer(&min, &name);
579     gss_release_buffer(&min, &lname);
580     return ret;
581 }
582
583
584 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
585 {
586     struct mag_config *cfg;
587
588     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
589     if (!cfg) return NULL;
590     cfg->pool = p;
591
592     return cfg;
593 }
594
595 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
596 {
597     struct mag_config *cfg = (struct mag_config *)mconfig;
598     cfg->ssl_only = on ? true : false;
599     return NULL;
600 }
601
602 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
603 {
604     struct mag_config *cfg = (struct mag_config *)mconfig;
605     cfg->map_to_local = on ? true : false;
606     return NULL;
607 }
608
609 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
610 {
611     struct mag_config *cfg = (struct mag_config *)mconfig;
612     cfg->gss_conn_ctx = on ? true : false;
613     return NULL;
614 }
615
616 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
617 {
618     struct mag_config *cfg = (struct mag_config *)mconfig;
619     cfg->use_sessions = on ? true : false;
620     return NULL;
621 }
622
623 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
624 {
625     struct mag_config *cfg = (struct mag_config *)mconfig;
626     cfg->use_s4u2proxy = on ? true : false;
627
628     if (cfg->deleg_ccache_dir == NULL) {
629         cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
630         if (!cfg->deleg_ccache_dir) {
631             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0,
632                          parms->server, "%s", "OOM setting deleg_ccache_dir.");
633         }
634     }
635     return NULL;
636 }
637
638 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
639 {
640     struct mag_config *cfg = (struct mag_config *)mconfig;
641     struct databuf key;
642     unsigned char *val;
643     apr_status_t rc;
644     const char *k;
645     int l;
646
647     if (strncmp(w, "key:", 4) != 0) {
648         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
649                      "Invalid key format, expected prefix 'key:'");
650         return NULL;
651     }
652     k = w + 4;
653
654     l = apr_base64_decode_len(k);
655     val = apr_palloc(parms->temp_pool, l);
656     if (!val) {
657         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
658                      "Failed to get memory to decode key");
659         return NULL;
660     }
661
662     key.length = (int)apr_base64_decode_binary(val, k);
663     key.value = (unsigned char *)val;
664
665     if (key.length < 32) {
666         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
667                      "Invalid key length, expected >=32 got %d", key.length);
668         return NULL;
669     }
670
671     rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &key);
672     if (rc != OK) {
673         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
674                      "Failed to import sealing key!");
675     }
676     return NULL;
677 }
678
679 #define MAX_CRED_OPTIONS 10
680
681 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
682                                   const char *w)
683 {
684     struct mag_config *cfg = (struct mag_config *)mconfig;
685     gss_key_value_element_desc *elements;
686     uint32_t count;
687     size_t size;
688     const char *p;
689     char *value;
690     char *key;
691
692     p = strchr(w, ':');
693     if (!p) {
694         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
695                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
696         return NULL;
697     }
698
699     key = apr_pstrndup(parms->pool, w, (p-w));
700     value = apr_pstrdup(parms->pool, p + 1);
701     if (!key || !value) {
702         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
703                      "%s", "OOM handling GssapiCredStore option");
704         return NULL;
705     }
706
707     if (!cfg->cred_store) {
708         cfg->cred_store = apr_pcalloc(parms->pool,
709                                       sizeof(gss_key_value_set_desc));
710         if (!cfg->cred_store) {
711             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
712                          "%s", "OOM handling GssapiCredStore option");
713             return NULL;
714         }
715         size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
716         cfg->cred_store->elements = apr_palloc(parms->pool, size);
717         if (!cfg->cred_store->elements) {
718             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
719                          "%s", "OOM handling GssapiCredStore option");
720         }
721     }
722
723     elements = cfg->cred_store->elements;
724     count = cfg->cred_store->count;
725
726     if (count >= MAX_CRED_OPTIONS) {
727         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
728                      "Too many GssapiCredStore options (MAX: %d)",
729                      MAX_CRED_OPTIONS);
730         return NULL;
731     }
732     cfg->cred_store->count++;
733
734     elements[count].key = key;
735     elements[count].value = value;
736
737     return NULL;
738 }
739
740 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
741                                         const char *value)
742 {
743     struct mag_config *cfg = (struct mag_config *)mconfig;
744
745     cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
746     if (!cfg->deleg_ccache_dir) {
747         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
748                      "%s", "OOM handling GssapiDelegCcacheDir option");
749     }
750
751     return NULL;
752 }
753
754 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
755 {
756     struct mag_config *cfg = (struct mag_config *)mconfig;
757
758     cfg->use_basic_auth = on ? true : false;
759     return NULL;
760 }
761
762 static const command_rec mag_commands[] = {
763     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
764                   "Work only if connection is SSL Secured"),
765     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
766                   "Translate principals to local names"),
767     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
768                   "Authentication is bound to the TCP connection"),
769     AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
770                   "Authentication uses mod_sessions to hold status"),
771     AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
772                      "Key Used to seal session data."),
773 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
774     AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
775                   "Initializes credentials for s4u2proxy usage"),
776 #endif
777 #ifdef HAVE_GSS_STORE_CRED_INTO
778     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
779                     "Credential Store"),
780     AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
781                      OR_AUTHCFG, "Directory to store delegated credentials"),
782 #endif
783 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
784     AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
785                      "Allows use of Basic Auth for authentication"),
786 #endif
787     { NULL }
788 };
789
790 static void
791 mag_register_hooks(apr_pool_t *p)
792 {
793     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
794     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
795     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
796 }
797
798 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
799 {
800     STANDARD20_MODULE_STUFF,
801     mag_create_dir_config,
802     NULL,
803     NULL,
804     NULL,
805     mag_commands,
806     mag_register_hooks
807 };