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;
169 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
176 gss_OID mech_type = GSS_C_NO_OID;
177 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
178 struct mag_conn *mc = NULL;
180 type = ap_auth_type(req);
181 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
185 /* ignore auth for subrequests */
186 if (!ap_is_initial_req(req)) {
190 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
193 if (!mag_conn_is_https(req->connection)) {
194 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
195 "Not a TLS connection, refusing to authenticate!");
200 if (cfg->gss_conn_ctx) {
201 mc = (struct mag_conn *)ap_get_module_config(
202 req->connection->conn_config,
203 &auth_gssapi_module);
205 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
206 "Failed to retrieve connection context!");
211 /* if available, session always supersedes connection bound data */
212 if (cfg->use_sessions) {
213 mag_check_session(req, cfg, &mc);
217 /* register the context in the memory pool, so it can be freed
218 * when the connection/request is terminated */
219 apr_pool_userdata_set(mc, "mag_conn_ptr",
220 mag_conn_destroy, mc->parent);
222 if (mc->established) {
223 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
224 "Already established context found!");
225 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
226 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
227 req->user = apr_pstrdup(req->pool, mc->user_name);
236 auth_header = apr_table_get(req->headers_in, "Authorization");
237 if (!auth_header) goto done;
239 auth_header_type = ap_getword_white(req->pool, &auth_header);
240 if (!auth_header_type) goto done;
242 if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
244 auth_header_value = ap_getword_white(req->pool, &auth_header);
245 if (!auth_header_value) goto done;
246 input.length = apr_base64_decode_len(auth_header_value) + 1;
247 input.value = apr_pcalloc(req->pool, input.length);
248 if (!input.value) goto done;
249 input.length = apr_base64_decode(input.value, auth_header_value);
251 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
252 if (cfg->use_s4u2proxy) {
253 cred_usage = GSS_C_BOTH;
255 if (cfg->cred_store) {
256 maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, 0,
257 GSS_C_NO_OID_SET, cred_usage,
258 cfg->cred_store, &acquired_cred,
260 if (GSS_ERROR(maj)) {
261 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
262 mag_error(req, "gss_acquire_cred_from() failed",
269 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
270 &input, GSS_C_NO_CHANNEL_BINDINGS,
271 &client, &mech_type, &output, &flags, &vtime,
273 if (GSS_ERROR(maj)) {
274 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
275 mag_error(req, "gss_accept_sec_context() failed",
280 if (maj == GSS_S_CONTINUE_NEEDED) {
282 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
283 "Mechanism needs continuation but neither "
284 "GssapiConnectionBound nor "
285 "GssapiUseSessions are available");
286 gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
287 gss_release_buffer(&min, &output);
290 /* auth not complete send token and wait next packet */
294 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
296 /* Always set the GSS name in an env var */
297 maj = gss_display_name(&min, client, &name, NULL);
298 if (GSS_ERROR(maj)) {
299 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
300 mag_error(req, "gss_accept_sec_context() failed",
304 clientname = apr_pstrndup(req->pool, name.value, name.length);
305 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
307 #ifdef HAVE_GSS_STORE_CRED_INTO
308 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
309 char *ccachefile = NULL;
311 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
312 delegated_cred, &ccachefile);
315 apr_table_set(req->subprocess_env, "KRB5CCNAME", ccachefile);
320 if (cfg->map_to_local) {
321 maj = gss_localname(&min, client, mech_type, &lname);
322 if (maj != GSS_S_COMPLETE) {
323 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
324 mag_error(req, "gss_localname() failed", maj, min));
327 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
329 req->user = clientname;
333 mc->user_name = apr_pstrdup(mc->parent, req->user);
334 mc->gss_name = apr_pstrdup(mc->parent, clientname);
335 mc->established = true;
336 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
337 vtime = MIN_SESS_EXP_TIME;
339 mc->expiration = time(NULL) + vtime;
340 if (cfg->use_sessions) {
341 mag_attempt_session(req, cfg, mc);
348 if (ret == HTTP_UNAUTHORIZED) {
349 if (output.length != 0) {
350 replen = apr_base64_encode_len(output.length) + 1;
351 reply = apr_pcalloc(req->pool, 10 + replen);
353 memcpy(reply, "Negotiate ", 10);
354 apr_base64_encode(&reply[10], output.value, output.length);
355 apr_table_add(req->err_headers_out,
356 "WWW-Authenticate", reply);
359 apr_table_add(req->err_headers_out,
360 "WWW-Authenticate", "Negotiate");
363 gss_release_cred(&min, &delegated_cred);
364 gss_release_buffer(&min, &output);
365 gss_release_name(&min, &client);
366 gss_release_buffer(&min, &name);
367 gss_release_buffer(&min, &lname);
372 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
374 struct mag_config *cfg;
376 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
377 if (!cfg) return NULL;
383 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
385 struct mag_config *cfg = (struct mag_config *)mconfig;
386 cfg->ssl_only = on ? true : false;
390 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
392 struct mag_config *cfg = (struct mag_config *)mconfig;
393 cfg->map_to_local = on ? true : false;
397 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
399 struct mag_config *cfg = (struct mag_config *)mconfig;
400 cfg->gss_conn_ctx = on ? true : false;
404 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
406 struct mag_config *cfg = (struct mag_config *)mconfig;
407 cfg->use_sessions = on ? true : false;
411 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
413 struct mag_config *cfg = (struct mag_config *)mconfig;
414 cfg->use_s4u2proxy = on ? true : false;
416 if (cfg->deleg_ccache_dir == NULL) {
417 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
418 if (!cfg->deleg_ccache_dir) {
419 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0,
420 parms->server, "%s", "OOM setting deleg_ccache_dir.");
426 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
428 struct mag_config *cfg = (struct mag_config *)mconfig;
435 if (strncmp(w, "key:", 4) != 0) {
436 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
437 "Invalid key format, expected prefix 'key:'");
442 l = apr_base64_decode_len(k);
443 val = apr_palloc(parms->temp_pool, l);
445 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
446 "Failed to get memory to decode key");
450 keys.length = (int)apr_base64_decode_binary(val, k);
451 keys.value = (unsigned char *)val;
453 if (keys.length != 32) {
454 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
455 "Invalid key lenght, expected 32 got %d", keys.length);
459 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
461 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
462 "Failed to import sealing key!");
467 #define MAX_CRED_OPTIONS 10
469 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
472 struct mag_config *cfg = (struct mag_config *)mconfig;
473 gss_key_value_element_desc *elements;
482 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
483 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
487 key = apr_pstrndup(parms->pool, w, (p-w));
488 value = apr_pstrdup(parms->pool, p + 1);
489 if (!key || !value) {
490 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
491 "%s", "OOM handling GssapiCredStore option");
495 if (!cfg->cred_store) {
496 cfg->cred_store = apr_pcalloc(parms->pool,
497 sizeof(gss_key_value_set_desc));
498 if (!cfg->cred_store) {
499 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
500 "%s", "OOM handling GssapiCredStore option");
503 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
504 cfg->cred_store->elements = apr_palloc(parms->pool, size);
505 if (!cfg->cred_store->elements) {
506 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
507 "%s", "OOM handling GssapiCredStore option");
511 elements = cfg->cred_store->elements;
512 count = cfg->cred_store->count;
514 if (count >= MAX_CRED_OPTIONS) {
515 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
516 "Too many GssapiCredStore options (MAX: %d)",
520 cfg->cred_store->count++;
522 elements[count].key = key;
523 elements[count].value = value;
528 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
531 struct mag_config *cfg = (struct mag_config *)mconfig;
533 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
534 if (!cfg->deleg_ccache_dir) {
535 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
536 "%s", "OOM handling GssapiDelegCcacheDir option");
542 static const command_rec mag_commands[] = {
543 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
544 "Work only if connection is SSL Secured"),
545 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
546 "Translate principals to local names"),
547 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
548 "Authentication is bound to the TCP connection"),
549 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
550 "Authentication uses mod_sessions to hold status"),
551 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
552 "Key Used to seal session data."),
553 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
554 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
555 "Initializes credentials for s4u2proxy usage"),
557 #ifdef HAVE_GSS_STORE_CRED_INTO
558 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
560 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
561 OR_AUTHCFG, "Directory to store delegated credentials"),
567 mag_register_hooks(apr_pool_t *p)
569 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
570 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
571 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
574 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
576 STANDARD20_MODULE_STUFF,
577 mag_create_dir_config,