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.
27 #include <gssapi/gssapi.h>
28 #include <gssapi/gssapi_ext.h>
31 #include <http_core.h>
32 #include <http_connection.h>
34 #include <http_request.h>
35 #include <apr_strings.h>
36 #include <apr_base64.h>
38 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
40 APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
46 gss_key_value_set_desc cred_store;
49 static char *mag_status(request_rec *req, int type, uint32_t err)
51 uint32_t maj_ret, min_ret;
60 maj_ret = gss_display_status(&min_ret, err, type,
61 GSS_C_NO_OID, &msg_ctx, &text);
62 if (maj_ret != GSS_S_COMPLETE) {
68 msg_ret = apr_psprintf(req->pool, "%s, %*s",
69 msg_ret, len, (char *)text.value);
71 msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
73 gss_release_buffer(&min_ret, &text);
74 } while (msg_ctx != 0);
79 static char *mag_error(request_rec *req, const char *msg,
80 uint32_t maj, uint32_t min)
85 msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
86 msg_min = mag_status(req, GSS_C_MECH_CODE, min);
87 return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
90 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
92 static int mag_post_config(apr_pool_t *cfg, apr_pool_t *log,
93 apr_pool_t *temp, server_rec *s)
95 /* FIXME: create mutex to deal with connections and contexts ? */
96 mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
109 static int mag_pre_connection(conn_rec *c, void *csd)
113 mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
114 if (!mc) return DECLINED;
116 ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
120 static bool mag_conn_is_https(conn_rec *c)
123 if (mag_is_https(c)) return true;
129 static int mag_auth(request_rec *req)
132 struct mag_config *cfg;
133 const char *auth_header;
134 char *auth_header_type;
135 char *auth_header_value;
136 int ret = HTTP_UNAUTHORIZED;
137 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
138 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
139 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
140 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
141 gss_name_t client = GSS_C_NO_NAME;
142 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
148 gss_OID mech_type = GSS_C_NO_OID;
149 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
150 struct mag_conn *mc = NULL;
152 type = ap_auth_type(req);
153 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
157 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
160 if (!mag_conn_is_https(req->connection)) {
161 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
162 "Not a TLS connection, refusing to authenticate!");
167 if (cfg->gss_conn_ctx) {
168 mc = (struct mag_conn *)ap_get_module_config(
169 req->connection->conn_config,
170 &auth_gssapi_module);
174 if (mc->established) {
175 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
176 "Connection bound pre-authentication found.");
177 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
178 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
179 req->user = apr_pstrdup(req->pool, mc->user_name);
187 auth_header = apr_table_get(req->headers_in, "Authorization");
188 if (!auth_header) goto done;
190 auth_header_type = ap_getword_white(req->pool, &auth_header);
191 if (!auth_header_type) goto done;
193 if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
195 auth_header_value = ap_getword_white(req->pool, &auth_header);
196 if (!auth_header_value) goto done;
197 input.length = apr_base64_decode_len(auth_header_value) + 1;
198 input.value = apr_pcalloc(req->pool, input.length);
199 if (!input.value) goto done;
200 input.length = apr_base64_decode(input.value, auth_header_value);
202 maj = gss_accept_sec_context(&min, &ctx, GSS_C_NO_CREDENTIAL,
203 &input, GSS_C_NO_CHANNEL_BINDINGS,
204 &client, &mech_type, &output, &flags, NULL,
206 if (GSS_ERROR(maj)) {
207 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
208 mag_error(req, "gss_accept_sec_context() failed",
215 ctx = GSS_C_NO_CONTEXT;
218 if (maj == GSS_S_CONTINUE_NEEDED) goto done;
220 #ifdef HAVE_GSS_STORE_CRED_INTO
221 if (cfg->cred_store && delegated_cred != GSS_C_NO_CREDENTIAL) {
222 gss_key_value_set_desc store = {0, NULL};
223 /* FIXME: run substtutions */
225 maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
226 GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
230 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
232 /* Always set the GSS name in an env var */
233 maj = gss_display_name(&min, client, &name, NULL);
234 if (GSS_ERROR(maj)) {
235 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
236 mag_error(req, "gss_accept_sec_context() failed",
240 clientname = apr_pstrndup(req->pool, name.value, name.length);
241 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
243 if (cfg->map_to_local) {
244 maj = gss_localname(&min, client, mech_type, &lname);
245 if (maj != GSS_S_COMPLETE) {
246 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
247 mag_error(req, "gss_localname() failed", maj, min));
250 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
252 req->user = clientname;
256 mc->user_name = apr_pstrdup(req->connection->pool, req->user);
257 mc->gss_name = apr_pstrdup(req->connection->pool, clientname);
258 mc->established = true;
264 if (ret == HTTP_UNAUTHORIZED) {
265 if (output.length != 0) {
266 replen = apr_base64_encode_len(output.length) + 1;
267 reply = apr_pcalloc(req->pool, 10 + replen);
269 memcpy(reply, "Negotiate ", 10);
270 apr_base64_encode(&reply[10], output.value, output.length);
271 reply[replen] = '\0';
272 apr_table_add(req->err_headers_out,
273 "WWW-Authenticate", reply);
276 apr_table_add(req->err_headers_out,
277 "WWW-Authenticate", "Negotiate");
280 gss_release_cred(&min, &delegated_cred);
281 gss_release_buffer(&min, &output);
282 gss_release_name(&min, &client);
283 gss_release_buffer(&min, &name);
284 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
285 gss_release_buffer(&min, &lname);
290 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
292 struct mag_config *cfg;
294 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
295 if (!cfg) return NULL;
300 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
302 struct mag_config *cfg = (struct mag_config *)mconfig;
303 cfg->ssl_only = on ? true : false;
307 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
309 struct mag_config *cfg = (struct mag_config *)mconfig;
310 cfg->map_to_local = on ? true : false;
314 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
316 struct mag_config *cfg = (struct mag_config *)mconfig;
317 cfg->gss_conn_ctx = on ? true : false;
321 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
324 struct mag_config *cfg = (struct mag_config *)mconfig;
325 gss_key_value_element_desc *elements;
334 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
335 "%s [%s]", "Invalid syntax for GSSCredStore option", w);
339 key = apr_pstrndup(parms->pool, w, (p-w));
340 value = apr_pstrdup(parms->pool, p + 1);
341 if (!key || !value) {
342 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
343 "%s", "OOM handling GSSCredStore option");
347 size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
348 elements = apr_palloc(parms->pool, size);
350 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
351 "%s", "OOM handling GSSCredStore option");
355 for (count = 0; count < cfg->cred_store.count; count++) {
356 elements[count] = cfg->cred_store.elements[count];
358 elements[count].key = key;
359 elements[count].value = value;
361 cfg->cred_store.elements = elements;
362 cfg->cred_store.count = count;
367 static const command_rec mag_commands[] = {
368 AP_INIT_FLAG("GSSSSLOnly", mag_ssl_only, NULL, OR_AUTHCFG,
369 "Work only if connection is SSL Secured"),
370 AP_INIT_FLAG("GSSLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
371 "Work only if connection is SSL Secured"),
372 AP_INIT_FLAG("GSSConnectionContext", mag_conn_ctx, NULL, OR_AUTHCFG,
373 "Authentication is valid for the life of the connection"),
374 AP_INIT_ITERATE("GSSCredStore", mag_cred_store, NULL, OR_AUTHCFG,
380 mag_register_hooks(apr_pool_t *p)
382 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
383 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
384 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
387 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
389 STANDARD20_MODULE_STUFF,
390 mag_create_dir_config,