4 Copyright (C) 2014 Simo Sorce <simo@redhat.com>
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:
13 The above copyright notice and this permission notice shall be included in
14 all copies or substantial portions of the Software.
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.
25 #include "mod_auth_gssapi.h"
27 #define MOD_AUTH_GSSAPI_VERSION PACKAGE_NAME "/" PACKAGE_VERSION
29 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
31 APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
33 static char *mag_status(request_rec *req, int type, uint32_t err)
35 uint32_t maj_ret, min_ret;
44 maj_ret = gss_display_status(&min_ret, err, type,
45 GSS_C_NO_OID, &msg_ctx, &text);
46 if (maj_ret != GSS_S_COMPLETE) {
52 msg_ret = apr_psprintf(req->pool, "%s, %*s",
53 msg_ret, len, (char *)text.value);
55 msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
57 gss_release_buffer(&min_ret, &text);
58 } while (msg_ctx != 0);
63 static char *mag_error(request_rec *req, const char *msg,
64 uint32_t maj, uint32_t min)
69 msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
70 msg_min = mag_status(req, GSS_C_MECH_CODE, min);
71 return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
74 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
76 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
77 apr_pool_t *temp, server_rec *s)
79 /* FIXME: create mutex to deal with connections and contexts ? */
80 mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
81 mag_post_config_session();
82 ap_add_version_component(cfgpool, MOD_AUTH_GSSAPI_VERSION);
87 static int mag_pre_connection(conn_rec *c, void *csd)
91 mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
92 if (!mc) return DECLINED;
95 ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
99 static apr_status_t mag_conn_destroy(void *ptr)
101 struct mag_conn *mc = (struct mag_conn *)ptr;
105 (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
106 mc->established = false;
111 static bool mag_conn_is_https(conn_rec *c)
114 if (mag_is_https(c)) return true;
120 static void mag_store_deleg_creds(request_rec *req,
121 char *dir, char *clientname,
122 gss_cred_id_t delegated_cred,
125 gss_key_value_element_desc element;
126 gss_key_value_set_desc store;
130 value = apr_psprintf(req->pool, "FILE:%s/%s", dir, clientname);
132 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, NULL,
133 "OOM storing delegated credentials");
137 element.key = "ccache";
138 element.value = value;
139 store.elements = &element;
142 maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
143 GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
144 if (GSS_ERROR(maj)) {
145 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
146 mag_error(req, "failed to store delegated creds",
153 static int mag_auth(request_rec *req)
156 struct mag_config *cfg;
157 const char *auth_header;
158 char *auth_header_type;
159 char *auth_header_value;
160 int ret = HTTP_UNAUTHORIZED;
161 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
163 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
164 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
165 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
166 gss_name_t client = GSS_C_NO_NAME;
167 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
168 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
175 gss_OID mech_type = GSS_C_NO_OID;
176 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
177 struct mag_conn *mc = NULL;
179 type = ap_auth_type(req);
180 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
184 /* ignore auth for subrequests */
185 if (!ap_is_initial_req(req)) {
189 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
192 if (!mag_conn_is_https(req->connection)) {
193 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
194 "Not a TLS connection, refusing to authenticate!");
199 if (cfg->gss_conn_ctx) {
200 mc = (struct mag_conn *)ap_get_module_config(
201 req->connection->conn_config,
202 &auth_gssapi_module);
204 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
205 "Failed to retrieve connection context!");
210 /* if available, session always supersedes connection bound data */
211 mag_check_session(req, cfg, &mc);
214 /* register the context in the memory pool, so it can be freed
215 * when the connection/request is terminated */
216 apr_pool_userdata_set(mc, "mag_conn_ptr",
217 mag_conn_destroy, mc->parent);
219 if (mc->established) {
220 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
221 "Already established context found!");
222 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
223 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
224 req->user = apr_pstrdup(req->pool, mc->user_name);
233 auth_header = apr_table_get(req->headers_in, "Authorization");
234 if (!auth_header) goto done;
236 auth_header_type = ap_getword_white(req->pool, &auth_header);
237 if (!auth_header_type) goto done;
239 if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
241 auth_header_value = ap_getword_white(req->pool, &auth_header);
242 if (!auth_header_value) goto done;
243 input.length = apr_base64_decode_len(auth_header_value) + 1;
244 input.value = apr_pcalloc(req->pool, input.length);
245 if (!input.value) goto done;
246 input.length = apr_base64_decode(input.value, auth_header_value);
248 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
249 if (cfg->use_s4u2proxy) {
250 maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, 0,
251 GSS_C_NO_OID_SET, GSS_C_BOTH,
252 cfg->cred_store, &acquired_cred,
254 if (GSS_ERROR(maj)) {
255 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
256 mag_error(req, "gss_acquire_cred_from() failed",
263 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
264 &input, GSS_C_NO_CHANNEL_BINDINGS,
265 &client, &mech_type, &output, &flags, &vtime,
267 if (GSS_ERROR(maj)) {
268 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
269 mag_error(req, "gss_accept_sec_context() failed",
274 if (maj == GSS_S_CONTINUE_NEEDED) {
276 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
277 "Mechanism needs continuation but neither "
278 "GssapiConnectionBound nor "
279 "GssapiUseSessions are available");
280 gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
281 gss_release_buffer(&min, &output);
284 /* auth not complete send token and wait next packet */
288 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
290 /* Always set the GSS name in an env var */
291 maj = gss_display_name(&min, client, &name, NULL);
292 if (GSS_ERROR(maj)) {
293 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
294 mag_error(req, "gss_accept_sec_context() failed",
298 clientname = apr_pstrndup(req->pool, name.value, name.length);
299 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
301 #ifdef HAVE_GSS_STORE_CRED_INTO
302 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
303 char *ccachefile = NULL;
305 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
306 delegated_cred, &ccachefile);
309 apr_table_set(req->subprocess_env, "KRB5CCNAME", ccachefile);
314 if (cfg->map_to_local) {
315 maj = gss_localname(&min, client, mech_type, &lname);
316 if (maj != GSS_S_COMPLETE) {
317 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
318 mag_error(req, "gss_localname() failed", maj, min));
321 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
323 req->user = clientname;
327 mc->user_name = apr_pstrdup(mc->parent, req->user);
328 mc->gss_name = apr_pstrdup(mc->parent, clientname);
329 mc->established = true;
330 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
331 vtime = MIN_SESS_EXP_TIME;
333 mc->expiration = time(NULL) + vtime;
334 mag_attempt_session(req, cfg, mc);
340 if (ret == HTTP_UNAUTHORIZED) {
341 if (output.length != 0) {
342 replen = apr_base64_encode_len(output.length) + 1;
343 reply = apr_pcalloc(req->pool, 10 + replen);
345 memcpy(reply, "Negotiate ", 10);
346 apr_base64_encode(&reply[10], output.value, output.length);
347 apr_table_add(req->err_headers_out,
348 "WWW-Authenticate", reply);
351 apr_table_add(req->err_headers_out,
352 "WWW-Authenticate", "Negotiate");
355 gss_release_cred(&min, &delegated_cred);
356 gss_release_buffer(&min, &output);
357 gss_release_name(&min, &client);
358 gss_release_buffer(&min, &name);
359 gss_release_buffer(&min, &lname);
364 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
366 struct mag_config *cfg;
368 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
369 if (!cfg) return NULL;
375 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
377 struct mag_config *cfg = (struct mag_config *)mconfig;
378 cfg->ssl_only = on ? true : false;
382 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
384 struct mag_config *cfg = (struct mag_config *)mconfig;
385 cfg->map_to_local = on ? true : false;
389 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
391 struct mag_config *cfg = (struct mag_config *)mconfig;
392 cfg->gss_conn_ctx = on ? true : false;
396 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
398 struct mag_config *cfg = (struct mag_config *)mconfig;
399 cfg->use_sessions = on ? true : false;
403 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
405 struct mag_config *cfg = (struct mag_config *)mconfig;
406 cfg->use_s4u2proxy = on ? true : false;
408 if (cfg->deleg_ccache_dir == NULL) {
409 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
410 if (!cfg->deleg_ccache_dir) {
411 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0,
412 parms->server, "%s", "OOM setting deleg_ccache_dir.");
418 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
420 struct mag_config *cfg = (struct mag_config *)mconfig;
427 if (strncmp(w, "key:", 4) != 0) {
428 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
429 "Invalid key format, expected prefix 'key:'");
434 l = apr_base64_decode_len(k);
435 val = apr_palloc(parms->temp_pool, l);
437 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
438 "Failed to get memory to decode key");
442 keys.length = (int)apr_base64_decode_binary(val, k);
443 keys.value = (unsigned char *)val;
445 if (keys.length != 32) {
446 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
447 "Invalid key lenght, expected 32 got %d", keys.length);
451 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
453 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
454 "Failed to import sealing key!");
459 #define MAX_CRED_OPTIONS 10
461 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
464 struct mag_config *cfg = (struct mag_config *)mconfig;
465 gss_key_value_element_desc *elements;
474 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
475 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
479 key = apr_pstrndup(parms->pool, w, (p-w));
480 value = apr_pstrdup(parms->pool, p + 1);
481 if (!key || !value) {
482 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
483 "%s", "OOM handling GssapiCredStore option");
487 if (!cfg->cred_store) {
488 cfg->cred_store = apr_pcalloc(parms->pool,
489 sizeof(gss_key_value_set_desc));
490 if (!cfg->cred_store) {
491 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
492 "%s", "OOM handling GssapiCredStore option");
495 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
496 cfg->cred_store->elements = apr_palloc(parms->pool, size);
497 if (!cfg->cred_store->elements) {
498 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
499 "%s", "OOM handling GssapiCredStore option");
503 elements = cfg->cred_store->elements;
504 count = cfg->cred_store->count;
506 if (count >= MAX_CRED_OPTIONS) {
507 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
508 "Too many GssapiCredStore options (MAX: %d)",
512 cfg->cred_store->count++;
514 elements[count].key = key;
515 elements[count].value = value;
520 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
523 struct mag_config *cfg = (struct mag_config *)mconfig;
525 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
526 if (!cfg->deleg_ccache_dir) {
527 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
528 "%s", "OOM handling GssapiDelegCcacheDir option");
534 static const command_rec mag_commands[] = {
535 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
536 "Work only if connection is SSL Secured"),
537 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
538 "Translate principals to local names"),
539 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
540 "Authentication is bound to the TCP connection"),
541 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
542 "Authentication uses mod_sessions to hold status"),
543 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
544 "Key Used to seal session data."),
545 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
546 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
547 "Initializes credentials for s4u2proxy usage"),
549 #ifdef HAVE_GSS_STORE_CRED_INTO
550 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
552 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
553 OR_AUTHCFG, "Directory to store delegated credentials"),
559 mag_register_hooks(apr_pool_t *p)
561 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
562 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
563 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
566 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
568 STANDARD20_MODULE_STUFF,
569 mag_create_dir_config,