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"
28 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
30 APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
32 static char *mag_status(request_rec *req, int type, uint32_t err)
34 uint32_t maj_ret, min_ret;
43 maj_ret = gss_display_status(&min_ret, err, type,
44 GSS_C_NO_OID, &msg_ctx, &text);
45 if (maj_ret != GSS_S_COMPLETE) {
51 msg_ret = apr_psprintf(req->pool, "%s, %*s",
52 msg_ret, len, (char *)text.value);
54 msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
56 gss_release_buffer(&min_ret, &text);
57 } while (msg_ctx != 0);
62 static char *mag_error(request_rec *req, const char *msg,
63 uint32_t maj, uint32_t min)
68 msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
69 msg_min = mag_status(req, GSS_C_MECH_CODE, min);
70 return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
73 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
75 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
76 apr_pool_t *temp, server_rec *s)
78 /* FIXME: create mutex to deal with connections and contexts ? */
79 mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
80 mag_post_config_session();
85 static int mag_pre_connection(conn_rec *c, void *csd)
89 mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
90 if (!mc) return DECLINED;
93 ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
97 static apr_status_t mag_conn_destroy(void *ptr)
99 struct mag_conn *mc = (struct mag_conn *)ptr;
103 (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
104 mc->established = false;
109 static bool mag_conn_is_https(conn_rec *c)
112 if (mag_is_https(c)) return true;
118 static void mag_store_deleg_creds(request_rec *req,
119 char *dir, char *clientname,
120 gss_cred_id_t delegated_cred,
123 gss_key_value_element_desc element;
124 gss_key_value_set_desc store;
128 value = apr_psprintf(req->pool, "FILE:%s/%s", dir, clientname);
130 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, NULL,
131 "OOM storing delegated credentials");
135 element.key = "ccache";
136 element.value = value;
137 store.elements = &element;
140 maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
141 GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
142 if (GSS_ERROR(maj)) {
143 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
144 mag_error(req, "failed to store delegated creds",
151 static int mag_auth(request_rec *req)
154 struct mag_config *cfg;
155 const char *auth_header;
156 char *auth_header_type;
157 char *auth_header_value;
158 int ret = HTTP_UNAUTHORIZED;
159 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
161 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
162 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
163 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
164 gss_name_t client = GSS_C_NO_NAME;
165 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
166 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
173 gss_OID mech_type = GSS_C_NO_OID;
174 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
175 struct mag_conn *mc = NULL;
177 type = ap_auth_type(req);
178 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
182 /* ignore auth for subrequests */
183 if (!ap_is_initial_req(req)) {
187 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
190 if (!mag_conn_is_https(req->connection)) {
191 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
192 "Not a TLS connection, refusing to authenticate!");
197 if (cfg->gss_conn_ctx) {
198 mc = (struct mag_conn *)ap_get_module_config(
199 req->connection->conn_config,
200 &auth_gssapi_module);
202 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
203 "Failed to retrieve connection context!");
208 /* if available, session always supersedes connection bound data */
209 mag_check_session(req, cfg, &mc);
212 /* register the context in the memory pool, so it can be freed
213 * when the connection/request is terminated */
214 apr_pool_userdata_set(mc, "mag_conn_ptr",
215 mag_conn_destroy, mc->parent);
217 if (mc->established) {
218 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
219 "Already established context found!");
220 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
221 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
222 req->user = apr_pstrdup(req->pool, mc->user_name);
231 auth_header = apr_table_get(req->headers_in, "Authorization");
232 if (!auth_header) goto done;
234 auth_header_type = ap_getword_white(req->pool, &auth_header);
235 if (!auth_header_type) goto done;
237 if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
239 auth_header_value = ap_getword_white(req->pool, &auth_header);
240 if (!auth_header_value) goto done;
241 input.length = apr_base64_decode_len(auth_header_value) + 1;
242 input.value = apr_pcalloc(req->pool, input.length);
243 if (!input.value) goto done;
244 input.length = apr_base64_decode(input.value, auth_header_value);
246 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
247 if (cfg->use_s4u2proxy) {
248 maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, 0,
249 GSS_C_NO_OID_SET, GSS_C_BOTH,
250 cfg->cred_store, &acquired_cred,
252 if (GSS_ERROR(maj)) {
253 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
254 mag_error(req, "gss_acquire_cred_from() failed",
261 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
262 &input, GSS_C_NO_CHANNEL_BINDINGS,
263 &client, &mech_type, &output, &flags, &vtime,
265 if (GSS_ERROR(maj)) {
266 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
267 mag_error(req, "gss_accept_sec_context() failed",
272 if (maj == GSS_S_CONTINUE_NEEDED) {
274 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
275 "Mechanism needs continuation but neither "
276 "GssapiConnectionBound nor "
277 "GssapiUseSessions are available");
278 gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
279 gss_release_buffer(&min, &output);
282 /* auth not complete send token and wait next packet */
286 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
288 /* Always set the GSS name in an env var */
289 maj = gss_display_name(&min, client, &name, NULL);
290 if (GSS_ERROR(maj)) {
291 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
292 mag_error(req, "gss_accept_sec_context() failed",
296 clientname = apr_pstrndup(req->pool, name.value, name.length);
297 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
299 #ifdef HAVE_GSS_STORE_CRED_INTO
300 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
301 char *ccachefile = NULL;
303 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
304 delegated_cred, &ccachefile);
307 apr_table_set(req->subprocess_env, "KRB5CCNAME", ccachefile);
312 if (cfg->map_to_local) {
313 maj = gss_localname(&min, client, mech_type, &lname);
314 if (maj != GSS_S_COMPLETE) {
315 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
316 mag_error(req, "gss_localname() failed", maj, min));
319 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
321 req->user = clientname;
325 mc->user_name = apr_pstrdup(mc->parent, req->user);
326 mc->gss_name = apr_pstrdup(mc->parent, clientname);
327 mc->established = true;
328 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
329 vtime = MIN_SESS_EXP_TIME;
331 mc->expiration = time(NULL) + vtime;
332 mag_attempt_session(req, cfg, mc);
338 if (ret == HTTP_UNAUTHORIZED) {
339 if (output.length != 0) {
340 replen = apr_base64_encode_len(output.length) + 1;
341 reply = apr_pcalloc(req->pool, 10 + replen);
343 memcpy(reply, "Negotiate ", 10);
344 apr_base64_encode(&reply[10], output.value, output.length);
345 apr_table_add(req->err_headers_out,
346 "WWW-Authenticate", reply);
349 apr_table_add(req->err_headers_out,
350 "WWW-Authenticate", "Negotiate");
353 gss_release_cred(&min, &delegated_cred);
354 gss_release_buffer(&min, &output);
355 gss_release_name(&min, &client);
356 gss_release_buffer(&min, &name);
357 gss_release_buffer(&min, &lname);
362 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
364 struct mag_config *cfg;
366 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
367 if (!cfg) return NULL;
373 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
375 struct mag_config *cfg = (struct mag_config *)mconfig;
376 cfg->ssl_only = on ? true : false;
380 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
382 struct mag_config *cfg = (struct mag_config *)mconfig;
383 cfg->map_to_local = on ? true : false;
387 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
389 struct mag_config *cfg = (struct mag_config *)mconfig;
390 cfg->gss_conn_ctx = on ? true : false;
394 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
396 struct mag_config *cfg = (struct mag_config *)mconfig;
397 cfg->use_sessions = on ? true : false;
401 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
403 struct mag_config *cfg = (struct mag_config *)mconfig;
404 cfg->use_s4u2proxy = on ? true : false;
406 if (cfg->deleg_ccache_dir == NULL) {
407 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
408 if (!cfg->deleg_ccache_dir) {
409 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0,
410 parms->server, "%s", "OOM setting deleg_ccache_dir.");
416 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
418 struct mag_config *cfg = (struct mag_config *)mconfig;
425 if (strncmp(w, "key:", 4) != 0) {
426 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
427 "Invalid key format, expected prefix 'key:'");
432 l = apr_base64_decode_len(k);
433 val = apr_palloc(parms->temp_pool, l);
435 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
436 "Failed to get memory to decode key");
440 keys.length = (int)apr_base64_decode_binary(val, k);
441 keys.value = (unsigned char *)val;
443 if (keys.length != 32) {
444 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
445 "Invalid key lenght, expected 32 got %d", keys.length);
449 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
451 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
452 "Failed to import sealing key!");
457 #define MAX_CRED_OPTIONS 10
459 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
462 struct mag_config *cfg = (struct mag_config *)mconfig;
463 gss_key_value_element_desc *elements;
472 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
473 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
477 key = apr_pstrndup(parms->pool, w, (p-w));
478 value = apr_pstrdup(parms->pool, p + 1);
479 if (!key || !value) {
480 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
481 "%s", "OOM handling GssapiCredStore option");
485 if (!cfg->cred_store) {
486 cfg->cred_store = apr_pcalloc(parms->pool,
487 sizeof(gss_key_value_set_desc));
488 if (!cfg->cred_store) {
489 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
490 "%s", "OOM handling GssapiCredStore option");
493 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
494 cfg->cred_store->elements = apr_palloc(parms->pool, size);
495 if (!cfg->cred_store->elements) {
496 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
497 "%s", "OOM handling GssapiCredStore option");
501 elements = cfg->cred_store->elements;
502 count = cfg->cred_store->count;
504 if (count >= MAX_CRED_OPTIONS) {
505 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
506 "Too many GssapiCredStore options (MAX: %d)",
510 cfg->cred_store->count++;
512 elements[count].key = key;
513 elements[count].value = value;
518 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
521 struct mag_config *cfg = (struct mag_config *)mconfig;
523 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
524 if (!cfg->deleg_ccache_dir) {
525 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
526 "%s", "OOM handling GssapiDelegCcacheDir option");
532 static const command_rec mag_commands[] = {
533 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
534 "Work only if connection is SSL Secured"),
535 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
536 "Work only if connection is SSL Secured"),
537 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
538 "Authentication is bound to the TCP connection"),
539 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
540 "Authentication uses mod_sessions to hold status"),
541 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
542 "Key Used to seal session data."),
543 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
544 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
545 "Initializes credentials for s4u2proxy usage"),
547 #ifdef HAVE_GSS_STORE_CRED_INTO
548 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
550 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
551 OR_AUTHCFG, "Directory to store delegated credentials"),
557 mag_register_hooks(apr_pool_t *p)
559 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
560 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
561 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
564 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
566 STANDARD20_MODULE_STUFF,
567 mag_create_dir_config,