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 apr_table_add(req->err_headers_out,
272 "WWW-Authenticate", reply);
275 apr_table_add(req->err_headers_out,
276 "WWW-Authenticate", "Negotiate");
279 gss_release_cred(&min, &delegated_cred);
280 gss_release_buffer(&min, &output);
281 gss_release_name(&min, &client);
282 gss_release_buffer(&min, &name);
283 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
284 gss_release_buffer(&min, &lname);
289 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
291 struct mag_config *cfg;
293 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
294 if (!cfg) return NULL;
299 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
301 struct mag_config *cfg = (struct mag_config *)mconfig;
302 cfg->ssl_only = on ? true : false;
306 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
308 struct mag_config *cfg = (struct mag_config *)mconfig;
309 cfg->map_to_local = on ? true : false;
313 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
315 struct mag_config *cfg = (struct mag_config *)mconfig;
316 cfg->gss_conn_ctx = on ? true : false;
320 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
323 struct mag_config *cfg = (struct mag_config *)mconfig;
324 gss_key_value_element_desc *elements;
333 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
334 "%s [%s]", "Invalid syntax for GSSCredStore option", w);
338 key = apr_pstrndup(parms->pool, w, (p-w));
339 value = apr_pstrdup(parms->pool, p + 1);
340 if (!key || !value) {
341 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
342 "%s", "OOM handling GSSCredStore option");
346 size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
347 elements = apr_palloc(parms->pool, size);
349 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
350 "%s", "OOM handling GSSCredStore option");
354 for (count = 0; count < cfg->cred_store.count; count++) {
355 elements[count] = cfg->cred_store.elements[count];
357 elements[count].key = key;
358 elements[count].value = value;
360 cfg->cred_store.elements = elements;
361 cfg->cred_store.count = count;
366 static const command_rec mag_commands[] = {
367 AP_INIT_FLAG("GSSSSLOnly", mag_ssl_only, NULL, OR_AUTHCFG,
368 "Work only if connection is SSL Secured"),
369 AP_INIT_FLAG("GSSLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
370 "Work only if connection is SSL Secured"),
371 AP_INIT_FLAG("GSSConnectionContext", mag_conn_ctx, NULL, OR_AUTHCFG,
372 "Authentication is valid for the life of the connection"),
373 AP_INIT_ITERATE("GSSCredStore", mag_cred_store, NULL, OR_AUTHCFG,
379 mag_register_hooks(apr_pool_t *p)
381 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
382 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
383 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
386 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
388 STANDARD20_MODULE_STUFF,
389 mag_create_dir_config,