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_cred_store(cmd_parms *parms, void *mconfig,
352 struct mag_config *cfg = (struct mag_config *)mconfig;
353 gss_key_value_element_desc *elements;
362 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
363 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
367 key = apr_pstrndup(parms->pool, w, (p-w));
368 value = apr_pstrdup(parms->pool, p + 1);
369 if (!key || !value) {
370 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
371 "%s", "OOM handling GssapiCredStore option");
375 size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
376 elements = apr_palloc(parms->pool, size);
378 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
379 "%s", "OOM handling GssapiCredStore option");
383 for (count = 0; count < cfg->cred_store.count; count++) {
384 elements[count] = cfg->cred_store.elements[count];
386 elements[count].key = key;
387 elements[count].value = value;
389 cfg->cred_store.elements = elements;
390 cfg->cred_store.count = count;
395 static const command_rec mag_commands[] = {
396 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
397 "Work only if connection is SSL Secured"),
398 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
399 "Work only if connection is SSL Secured"),
400 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
401 "Authentication is bound to the TCP connection"),
402 AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
403 "Authentication uses mod_sessions to hold status"),
404 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
410 mag_register_hooks(apr_pool_t *p)
412 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
413 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
414 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
417 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
419 STANDARD20_MODULE_STUFF,
420 mag_create_dir_config,