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 APLOG_USE_MODULE(auth_gssapi);
33 APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
35 static char *mag_status(request_rec *req, int type, uint32_t err)
37 uint32_t maj_ret, min_ret;
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) {
54 msg_ret = apr_psprintf(req->pool, "%s, %*s",
55 msg_ret, len, (char *)text.value);
57 msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
59 gss_release_buffer(&min_ret, &text);
60 } while (msg_ctx != 0);
65 static char *mag_error(request_rec *req, const char *msg,
66 uint32_t maj, uint32_t min)
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);
76 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
78 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
79 apr_pool_t *temp, server_rec *s)
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);
89 static int mag_pre_connection(conn_rec *c, void *csd)
93 mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
94 if (!mc) return DECLINED;
97 ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
101 static apr_status_t mag_conn_destroy(void *ptr)
103 struct mag_conn *mc = (struct mag_conn *)ptr;
107 (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
108 mc->established = false;
113 static bool mag_conn_is_https(conn_rec *c)
116 if (mag_is_https(c)) return true;
122 static void mag_store_deleg_creds(request_rec *req,
123 char *dir, char *clientname,
124 gss_cred_id_t delegated_cred,
127 gss_key_value_element_desc element;
128 gss_key_value_set_desc store;
132 value = apr_psprintf(req->pool, "FILE:%s/%s", dir, clientname);
134 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, NULL,
135 "OOM storing delegated credentials");
139 element.key = "ccache";
140 element.value = value;
141 store.elements = &element;
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",
155 static int mag_auth(request_rec *req)
158 struct mag_config *cfg;
159 const char *auth_header;
160 char *auth_header_type;
161 char *auth_header_value;
162 int ret = HTTP_UNAUTHORIZED;
163 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
165 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
166 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
167 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
168 gss_name_t client = GSS_C_NO_NAME;
169 gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
170 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
171 gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
178 gss_OID mech_type = GSS_C_NO_OID;
179 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
180 struct mag_conn *mc = NULL;
182 type = ap_auth_type(req);
183 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
187 /* ignore auth for subrequests */
188 if (!ap_is_initial_req(req)) {
192 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
195 if (!mag_conn_is_https(req->connection)) {
196 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
197 "Not a TLS connection, refusing to authenticate!");
202 if (cfg->gss_conn_ctx) {
203 mc = (struct mag_conn *)ap_get_module_config(
204 req->connection->conn_config,
205 &auth_gssapi_module);
207 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
208 "Failed to retrieve connection context!");
213 /* if available, session always supersedes connection bound data */
214 if (cfg->use_sessions) {
215 mag_check_session(req, cfg, &mc);
219 /* register the context in the memory pool, so it can be freed
220 * when the connection/request is terminated */
221 apr_pool_userdata_set(mc, "mag_conn_ptr",
222 mag_conn_destroy, mc->parent);
224 if (mc->established) {
225 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
226 "Already established context found!");
227 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
228 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
229 req->user = apr_pstrdup(req->pool, mc->user_name);
238 auth_header = apr_table_get(req->headers_in, "Authorization");
239 if (!auth_header) goto done;
241 auth_header_type = ap_getword_white(req->pool, &auth_header);
242 if (!auth_header_type) goto done;
244 if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
246 auth_header_value = ap_getword_white(req->pool, &auth_header);
247 if (!auth_header_value) goto done;
248 input.length = apr_base64_decode_len(auth_header_value) + 1;
249 input.value = apr_pcalloc(req->pool, input.length);
250 if (!input.value) goto done;
251 input.length = apr_base64_decode(input.value, auth_header_value);
253 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
254 if (cfg->use_s4u2proxy) {
255 cred_usage = GSS_C_BOTH;
257 if (cfg->cred_store) {
258 maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, 0,
259 GSS_C_NO_OID_SET, cred_usage,
260 cfg->cred_store, &acquired_cred,
262 if (GSS_ERROR(maj)) {
263 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
264 mag_error(req, "gss_acquire_cred_from() failed",
271 maj = gss_accept_sec_context(&min, pctx, acquired_cred,
272 &input, GSS_C_NO_CHANNEL_BINDINGS,
273 &client, &mech_type, &output, &flags, &vtime,
275 if (GSS_ERROR(maj)) {
276 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
277 mag_error(req, "gss_accept_sec_context() failed",
282 if (maj == GSS_S_CONTINUE_NEEDED) {
284 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
285 "Mechanism needs continuation but neither "
286 "GssapiConnectionBound nor "
287 "GssapiUseSessions are available");
288 gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
289 gss_release_buffer(&min, &output);
292 /* auth not complete send token and wait next packet */
296 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
298 /* Always set the GSS name in an env var */
299 maj = gss_display_name(&min, client, &name, NULL);
300 if (GSS_ERROR(maj)) {
301 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
302 mag_error(req, "gss_display_name() failed",
306 clientname = apr_pstrndup(req->pool, name.value, name.length);
307 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
309 #ifdef HAVE_GSS_STORE_CRED_INTO
310 if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
311 char *ccachefile = NULL;
313 mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
314 delegated_cred, &ccachefile);
317 apr_table_set(req->subprocess_env, "KRB5CCNAME", ccachefile);
322 if (cfg->map_to_local) {
323 maj = gss_localname(&min, client, mech_type, &lname);
324 if (maj != GSS_S_COMPLETE) {
325 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
326 mag_error(req, "gss_localname() failed", maj, min));
329 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
331 req->user = clientname;
335 mc->user_name = apr_pstrdup(mc->parent, req->user);
336 mc->gss_name = apr_pstrdup(mc->parent, clientname);
337 mc->established = true;
338 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
339 vtime = MIN_SESS_EXP_TIME;
341 mc->expiration = time(NULL) + vtime;
342 if (cfg->use_sessions) {
343 mag_attempt_session(req, cfg, mc);
350 if (ret == HTTP_UNAUTHORIZED) {
351 if (output.length != 0) {
352 replen = apr_base64_encode_len(output.length) + 1;
353 reply = apr_pcalloc(req->pool, 10 + replen);
355 memcpy(reply, "Negotiate ", 10);
356 apr_base64_encode(&reply[10], output.value, output.length);
357 apr_table_add(req->err_headers_out,
358 "WWW-Authenticate", reply);
361 apr_table_add(req->err_headers_out,
362 "WWW-Authenticate", "Negotiate");
365 gss_release_cred(&min, &acquired_cred);
366 gss_release_cred(&min, &delegated_cred);
367 gss_release_buffer(&min, &output);
368 gss_release_name(&min, &client);
369 gss_release_buffer(&min, &name);
370 gss_release_buffer(&min, &lname);
375 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
377 struct mag_config *cfg;
379 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
380 if (!cfg) return NULL;
386 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
388 struct mag_config *cfg = (struct mag_config *)mconfig;
389 cfg->ssl_only = on ? true : false;
393 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
395 struct mag_config *cfg = (struct mag_config *)mconfig;
396 cfg->map_to_local = on ? true : false;
400 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
402 struct mag_config *cfg = (struct mag_config *)mconfig;
403 cfg->gss_conn_ctx = on ? true : false;
407 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
409 struct mag_config *cfg = (struct mag_config *)mconfig;
410 cfg->use_sessions = on ? true : false;
414 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
416 struct mag_config *cfg = (struct mag_config *)mconfig;
417 cfg->use_s4u2proxy = on ? true : false;
419 if (cfg->deleg_ccache_dir == NULL) {
420 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
421 if (!cfg->deleg_ccache_dir) {
422 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0,
423 parms->server, "%s", "OOM setting deleg_ccache_dir.");
429 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
431 struct mag_config *cfg = (struct mag_config *)mconfig;
438 if (strncmp(w, "key:", 4) != 0) {
439 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
440 "Invalid key format, expected prefix 'key:'");
445 l = apr_base64_decode_len(k);
446 val = apr_palloc(parms->temp_pool, l);
448 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
449 "Failed to get memory to decode key");
453 keys.length = (int)apr_base64_decode_binary(val, k);
454 keys.value = (unsigned char *)val;
456 if (keys.length != 32) {
457 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
458 "Invalid key lenght, expected 32 got %d", keys.length);
462 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
464 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
465 "Failed to import sealing key!");
470 #define MAX_CRED_OPTIONS 10
472 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
475 struct mag_config *cfg = (struct mag_config *)mconfig;
476 gss_key_value_element_desc *elements;
485 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
486 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
490 key = apr_pstrndup(parms->pool, w, (p-w));
491 value = apr_pstrdup(parms->pool, p + 1);
492 if (!key || !value) {
493 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
494 "%s", "OOM handling GssapiCredStore option");
498 if (!cfg->cred_store) {
499 cfg->cred_store = apr_pcalloc(parms->pool,
500 sizeof(gss_key_value_set_desc));
501 if (!cfg->cred_store) {
502 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
503 "%s", "OOM handling GssapiCredStore option");
506 size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
507 cfg->cred_store->elements = apr_palloc(parms->pool, size);
508 if (!cfg->cred_store->elements) {
509 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
510 "%s", "OOM handling GssapiCredStore option");
514 elements = cfg->cred_store->elements;
515 count = cfg->cred_store->count;
517 if (count >= MAX_CRED_OPTIONS) {
518 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
519 "Too many GssapiCredStore options (MAX: %d)",
523 cfg->cred_store->count++;
525 elements[count].key = key;
526 elements[count].value = value;
531 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
534 struct mag_config *cfg = (struct mag_config *)mconfig;
536 cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
537 if (!cfg->deleg_ccache_dir) {
538 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
539 "%s", "OOM handling GssapiDelegCcacheDir option");
545 static const command_rec mag_commands[] = {
546 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
547 "Work only if connection is SSL Secured"),
548 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
549 "Translate principals to local names"),
550 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
551 "Authentication is bound to the TCP connection"),
552 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
553 "Authentication uses mod_sessions to hold status"),
554 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
555 "Key Used to seal session data."),
556 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
557 AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
558 "Initializes credentials for s4u2proxy usage"),
560 #ifdef HAVE_GSS_STORE_CRED_INTO
561 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
563 AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
564 OR_AUTHCFG, "Directory to store delegated credentials"),
570 mag_register_hooks(apr_pool_t *p)
572 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
573 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
574 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
577 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
579 STANDARD20_MODULE_STUFF,
580 mag_create_dir_config,