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 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
29 APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
31 static char *mag_status(request_rec *req, int type, uint32_t err)
33 uint32_t maj_ret, min_ret;
42 maj_ret = gss_display_status(&min_ret, err, type,
43 GSS_C_NO_OID, &msg_ctx, &text);
44 if (maj_ret != GSS_S_COMPLETE) {
50 msg_ret = apr_psprintf(req->pool, "%s, %*s",
51 msg_ret, len, (char *)text.value);
53 msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
55 gss_release_buffer(&min_ret, &text);
56 } while (msg_ctx != 0);
61 static char *mag_error(request_rec *req, const char *msg,
62 uint32_t maj, uint32_t min)
67 msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
68 msg_min = mag_status(req, GSS_C_MECH_CODE, min);
69 return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
72 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
74 static int mag_post_config(apr_pool_t *cfg, apr_pool_t *log,
75 apr_pool_t *temp, server_rec *s)
77 /* FIXME: create mutex to deal with connections and contexts ? */
78 mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
92 static int mag_pre_connection(conn_rec *c, void *csd)
96 mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
97 if (!mc) return DECLINED;
100 ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
104 static apr_status_t mag_conn_destroy(void *ptr)
106 struct mag_conn *mc = (struct mag_conn *)ptr;
110 (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
111 mc->established = false;
116 static bool mag_conn_is_https(conn_rec *c)
119 if (mag_is_https(c)) return true;
125 static int mag_auth(request_rec *req)
128 struct mag_config *cfg;
129 const char *auth_header;
130 char *auth_header_type;
131 char *auth_header_value;
132 int ret = HTTP_UNAUTHORIZED;
133 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
135 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
136 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
137 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
138 gss_name_t client = GSS_C_NO_NAME;
139 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
145 gss_OID mech_type = GSS_C_NO_OID;
146 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
147 struct mag_conn *mc = NULL;
149 type = ap_auth_type(req);
150 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
154 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
157 if (!mag_conn_is_https(req->connection)) {
158 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
159 "Not a TLS connection, refusing to authenticate!");
164 if (cfg->gss_conn_ctx) {
165 mc = (struct mag_conn *)ap_get_module_config(
166 req->connection->conn_config,
167 &auth_gssapi_module);
171 if (mc->established) {
172 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
173 "Connection bound pre-authentication found.");
174 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
175 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
176 req->user = apr_pstrdup(req->pool, mc->user_name);
185 auth_header = apr_table_get(req->headers_in, "Authorization");
186 if (!auth_header) goto done;
188 auth_header_type = ap_getword_white(req->pool, &auth_header);
189 if (!auth_header_type) goto done;
191 if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
193 auth_header_value = ap_getword_white(req->pool, &auth_header);
194 if (!auth_header_value) goto done;
195 input.length = apr_base64_decode_len(auth_header_value) + 1;
196 input.value = apr_pcalloc(req->pool, input.length);
197 if (!input.value) goto done;
198 input.length = apr_base64_decode(input.value, auth_header_value);
200 maj = gss_accept_sec_context(&min, pctx, GSS_C_NO_CREDENTIAL,
201 &input, GSS_C_NO_CHANNEL_BINDINGS,
202 &client, &mech_type, &output, &flags, NULL,
204 if (GSS_ERROR(maj)) {
205 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
206 mag_error(req, "gss_accept_sec_context() failed",
211 /* register the context in the connection pool, so it can be freed
212 * when the connection is terminated */
213 apr_pool_userdata_set(mc, "mag_conn_ptr", mag_conn_destroy, mc->parent);
215 if (maj == GSS_S_CONTINUE_NEEDED) {
216 if (!cfg->gss_conn_ctx) {
217 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
218 "Mechanism needs continuation but "
219 "GssapiConnectionBound is off.");
220 gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
221 gss_release_buffer(&min, &output);
227 #ifdef HAVE_GSS_STORE_CRED_INTO
228 if (cfg->cred_store.count != 0 && delegated_cred != GSS_C_NO_CREDENTIAL) {
229 gss_key_value_set_desc store = {0, NULL};
230 /* FIXME: run substitutions */
232 maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
233 GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
237 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
239 /* Always set the GSS name in an env var */
240 maj = gss_display_name(&min, client, &name, NULL);
241 if (GSS_ERROR(maj)) {
242 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
243 mag_error(req, "gss_accept_sec_context() failed",
247 clientname = apr_pstrndup(req->pool, name.value, name.length);
248 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
250 if (cfg->map_to_local) {
251 maj = gss_localname(&min, client, mech_type, &lname);
252 if (maj != GSS_S_COMPLETE) {
253 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
254 mag_error(req, "gss_localname() failed", maj, min));
257 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
259 req->user = clientname;
263 mc->user_name = apr_pstrdup(mc->parent, req->user);
264 mc->gss_name = apr_pstrdup(mc->parent, clientname);
265 mc->established = true;
271 if (ret == HTTP_UNAUTHORIZED) {
272 if (output.length != 0) {
273 replen = apr_base64_encode_len(output.length) + 1;
274 reply = apr_pcalloc(req->pool, 10 + replen);
276 memcpy(reply, "Negotiate ", 10);
277 apr_base64_encode(&reply[10], output.value, output.length);
278 apr_table_add(req->err_headers_out,
279 "WWW-Authenticate", reply);
282 apr_table_add(req->err_headers_out,
283 "WWW-Authenticate", "Negotiate");
286 gss_release_cred(&min, &delegated_cred);
287 gss_release_buffer(&min, &output);
288 gss_release_name(&min, &client);
289 gss_release_buffer(&min, &name);
290 gss_release_buffer(&min, &lname);
295 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
297 struct mag_config *cfg;
299 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
300 if (!cfg) return NULL;
305 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
307 struct mag_config *cfg = (struct mag_config *)mconfig;
308 cfg->ssl_only = on ? true : false;
312 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
314 struct mag_config *cfg = (struct mag_config *)mconfig;
315 cfg->map_to_local = on ? true : false;
319 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
321 struct mag_config *cfg = (struct mag_config *)mconfig;
322 cfg->gss_conn_ctx = on ? true : false;
326 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
329 struct mag_config *cfg = (struct mag_config *)mconfig;
330 gss_key_value_element_desc *elements;
339 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
340 "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
344 key = apr_pstrndup(parms->pool, w, (p-w));
345 value = apr_pstrdup(parms->pool, p + 1);
346 if (!key || !value) {
347 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
348 "%s", "OOM handling GssapiCredStore option");
352 size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
353 elements = apr_palloc(parms->pool, size);
355 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
356 "%s", "OOM handling GssapiCredStore option");
360 for (count = 0; count < cfg->cred_store.count; count++) {
361 elements[count] = cfg->cred_store.elements[count];
363 elements[count].key = key;
364 elements[count].value = value;
366 cfg->cred_store.elements = elements;
367 cfg->cred_store.count = count;
372 static const command_rec mag_commands[] = {
373 AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
374 "Work only if connection is SSL Secured"),
375 AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
376 "Work only if connection is SSL Secured"),
377 AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
378 "Authentication is bound to the TCP connection"),
379 AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
385 mag_register_hooks(apr_pool_t *p)
387 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
388 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
389 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
392 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
394 STANDARD20_MODULE_STUFF,
395 mag_create_dir_config,