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 int mag_auth(request_rec *req)
121 struct mag_config *cfg;
122 const char *auth_header;
123 char *auth_header_type;
124 char *auth_header_value;
125 int ret = HTTP_UNAUTHORIZED;
126 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
128 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
129 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
130 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
131 gss_name_t client = GSS_C_NO_NAME;
132 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
139 gss_OID mech_type = GSS_C_NO_OID;
140 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
141 struct mag_conn *mc = NULL;
143 type = ap_auth_type(req);
144 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
148 /* ignore auth for subrequests */
149 if (!ap_is_initial_req(req)) {
153 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
156 if (!mag_conn_is_https(req->connection)) {
157 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
158 "Not a TLS connection, refusing to authenticate!");
163 if (cfg->gss_conn_ctx) {
164 mc = (struct mag_conn *)ap_get_module_config(
165 req->connection->conn_config,
166 &auth_gssapi_module);
168 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
169 "Failed to retrieve connection context!");
174 /* if available, session always supersedes connection bound data */
175 mag_check_session(req, cfg, &mc);
178 /* register the context in the memory pool, so it can be freed
179 * when the connection/request is terminated */
180 apr_pool_userdata_set(mc, "mag_conn_ptr",
181 mag_conn_destroy, mc->parent);
183 if (mc->established) {
184 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
185 "Already established context found!");
186 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
187 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
188 req->user = apr_pstrdup(req->pool, mc->user_name);
197 auth_header = apr_table_get(req->headers_in, "Authorization");
198 if (!auth_header) goto done;
200 auth_header_type = ap_getword_white(req->pool, &auth_header);
201 if (!auth_header_type) goto done;
203 if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
205 auth_header_value = ap_getword_white(req->pool, &auth_header);
206 if (!auth_header_value) goto done;
207 input.length = apr_base64_decode_len(auth_header_value) + 1;
208 input.value = apr_pcalloc(req->pool, input.length);
209 if (!input.value) goto done;
210 input.length = apr_base64_decode(input.value, auth_header_value);
212 maj = gss_accept_sec_context(&min, pctx, GSS_C_NO_CREDENTIAL,
213 &input, GSS_C_NO_CHANNEL_BINDINGS,
214 &client, &mech_type, &output, &flags, &vtime,
216 if (GSS_ERROR(maj)) {
217 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
218 mag_error(req, "gss_accept_sec_context() failed",
223 if (maj == GSS_S_CONTINUE_NEEDED) {
225 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
226 "Mechanism needs continuation but neither "
227 "GssapiConnectionBound nor "
228 "GssapiUseSessions are available");
229 gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
230 gss_release_buffer(&min, &output);
233 /* auth not complete send token and wait next packet */
237 #ifdef HAVE_GSS_STORE_CRED_INTO
238 if (cfg->cred_store.count != 0 && delegated_cred != GSS_C_NO_CREDENTIAL) {
239 gss_key_value_set_desc store = {0, NULL};
240 /* FIXME: run substitutions */
242 maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
243 GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
247 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
249 /* Always set the GSS name in an env var */
250 maj = gss_display_name(&min, client, &name, NULL);
251 if (GSS_ERROR(maj)) {
252 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
253 mag_error(req, "gss_accept_sec_context() failed",
257 clientname = apr_pstrndup(req->pool, name.value, name.length);
258 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
260 if (cfg->map_to_local) {
261 maj = gss_localname(&min, client, mech_type, &lname);
262 if (maj != GSS_S_COMPLETE) {
263 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
264 mag_error(req, "gss_localname() failed", maj, min));
267 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
269 req->user = clientname;
273 mc->user_name = apr_pstrdup(mc->parent, req->user);
274 mc->gss_name = apr_pstrdup(mc->parent, clientname);
275 mc->established = true;
276 if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
277 vtime = MIN_SESS_EXP_TIME;
279 mc->expiration = time(NULL) + vtime;
280 mag_attempt_session(req, cfg, mc);
286 if (ret == HTTP_UNAUTHORIZED) {
287 if (output.length != 0) {
288 replen = apr_base64_encode_len(output.length) + 1;
289 reply = apr_pcalloc(req->pool, 10 + replen);
291 memcpy(reply, "Negotiate ", 10);
292 apr_base64_encode(&reply[10], output.value, output.length);
293 apr_table_add(req->err_headers_out,
294 "WWW-Authenticate", reply);
297 apr_table_add(req->err_headers_out,
298 "WWW-Authenticate", "Negotiate");
301 gss_release_cred(&min, &delegated_cred);
302 gss_release_buffer(&min, &output);
303 gss_release_name(&min, &client);
304 gss_release_buffer(&min, &name);
305 gss_release_buffer(&min, &lname);
310 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
312 struct mag_config *cfg;
314 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
315 if (!cfg) return NULL;
321 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
323 struct mag_config *cfg = (struct mag_config *)mconfig;
324 cfg->ssl_only = on ? true : false;
328 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
330 struct mag_config *cfg = (struct mag_config *)mconfig;
331 cfg->map_to_local = on ? true : false;
335 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
337 struct mag_config *cfg = (struct mag_config *)mconfig;
338 cfg->gss_conn_ctx = on ? true : false;
342 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
344 struct mag_config *cfg = (struct mag_config *)mconfig;
345 cfg->use_sessions = on ? true : false;
349 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
351 struct mag_config *cfg = (struct mag_config *)mconfig;
358 if (strncmp(w, "key:", 4) != 0) {
359 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
360 "Invalid key format, expected prefix 'key:'");
365 l = apr_base64_decode_len(k);
366 val = apr_palloc(parms->temp_pool, l);
368 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
369 "Failed to get memory to decode key");
373 keys.length = (int)apr_base64_decode_binary(val, k);
374 keys.value = (unsigned char *)val;
376 if (keys.length != 32) {
377 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
378 "Invalid key lenght, expected 32 got %d", keys.length);
382 rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
384 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
385 "Failed to import sealing key!");
390 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
393 struct mag_config *cfg = (struct mag_config *)mconfig;
394 gss_key_value_element_desc *elements;
403 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
404 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
408 key = apr_pstrndup(parms->pool, w, (p-w));
409 value = apr_pstrdup(parms->pool, p + 1);
410 if (!key || !value) {
411 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
412 "%s", "OOM handling GssapiCredStore option");
416 size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
417 elements = apr_palloc(parms->pool, size);
419 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
420 "%s", "OOM handling GssapiCredStore option");
424 for (count = 0; count < cfg->cred_store.count; count++) {
425 elements[count] = cfg->cred_store.elements[count];
427 elements[count].key = key;
428 elements[count].value = value;
430 cfg->cred_store.elements = elements;
431 cfg->cred_store.count = count;
436 static const command_rec mag_commands[] = {
437 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
438 "Work only if connection is SSL Secured"),
439 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
440 "Work only if connection is SSL Secured"),
441 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
442 "Authentication is bound to the TCP connection"),
443 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
444 "Authentication uses mod_sessions to hold status"),
445 AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
446 "Key Used to seal session data."),
447 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
453 mag_register_hooks(apr_pool_t *p)
455 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
456 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
457 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
460 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
462 STANDARD20_MODULE_STUFF,
463 mag_create_dir_config,