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);
110 static int mag_pre_connection(conn_rec *c, void *csd)
114 mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
115 if (!mc) return DECLINED;
117 mc->parent = c->pool;
118 ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
122 static apr_status_t mag_conn_destroy(void *ptr)
124 struct mag_conn *mc = (struct mag_conn *)ptr;
128 (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
129 mc->established = false;
134 static bool mag_conn_is_https(conn_rec *c)
137 if (mag_is_https(c)) return true;
143 static int mag_auth(request_rec *req)
146 struct mag_config *cfg;
147 const char *auth_header;
148 char *auth_header_type;
149 char *auth_header_value;
150 int ret = HTTP_UNAUTHORIZED;
151 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
153 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
154 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
155 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
156 gss_name_t client = GSS_C_NO_NAME;
157 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
163 gss_OID mech_type = GSS_C_NO_OID;
164 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
165 struct mag_conn *mc = NULL;
167 type = ap_auth_type(req);
168 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
172 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
175 if (!mag_conn_is_https(req->connection)) {
176 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
177 "Not a TLS connection, refusing to authenticate!");
182 if (cfg->gss_conn_ctx) {
183 mc = (struct mag_conn *)ap_get_module_config(
184 req->connection->conn_config,
185 &auth_gssapi_module);
189 if (mc->established) {
190 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
191 "Connection bound pre-authentication found.");
192 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
193 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
194 req->user = apr_pstrdup(req->pool, mc->user_name);
203 auth_header = apr_table_get(req->headers_in, "Authorization");
204 if (!auth_header) goto done;
206 auth_header_type = ap_getword_white(req->pool, &auth_header);
207 if (!auth_header_type) goto done;
209 if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
211 auth_header_value = ap_getword_white(req->pool, &auth_header);
212 if (!auth_header_value) goto done;
213 input.length = apr_base64_decode_len(auth_header_value) + 1;
214 input.value = apr_pcalloc(req->pool, input.length);
215 if (!input.value) goto done;
216 input.length = apr_base64_decode(input.value, auth_header_value);
218 maj = gss_accept_sec_context(&min, pctx, GSS_C_NO_CREDENTIAL,
219 &input, GSS_C_NO_CHANNEL_BINDINGS,
220 &client, &mech_type, &output, &flags, NULL,
222 if (GSS_ERROR(maj)) {
223 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
224 mag_error(req, "gss_accept_sec_context() failed",
229 /* register the context in the connection pool, so it can be freed
230 * when the connection is terminated */
231 apr_pool_userdata_set(mc, "mag_conn_ptr", mag_conn_destroy, mc->parent);
233 if (maj == GSS_S_CONTINUE_NEEDED) {
234 if (!cfg->gss_conn_ctx) {
235 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
236 "Mechanism needs continuation but "
237 "GSSConnectionContext is off.");
238 gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
239 gss_release_buffer(&min, &output);
245 #ifdef HAVE_GSS_STORE_CRED_INTO
246 if (cfg->cred_store && delegated_cred != GSS_C_NO_CREDENTIAL) {
247 gss_key_value_set_desc store = {0, NULL};
248 /* FIXME: run substtutions */
250 maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
251 GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
255 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
257 /* Always set the GSS name in an env var */
258 maj = gss_display_name(&min, client, &name, NULL);
259 if (GSS_ERROR(maj)) {
260 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
261 mag_error(req, "gss_accept_sec_context() failed",
265 clientname = apr_pstrndup(req->pool, name.value, name.length);
266 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
268 if (cfg->map_to_local) {
269 maj = gss_localname(&min, client, mech_type, &lname);
270 if (maj != GSS_S_COMPLETE) {
271 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
272 mag_error(req, "gss_localname() failed", maj, min));
275 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
277 req->user = clientname;
281 mc->user_name = apr_pstrdup(mc->parent, req->user);
282 mc->gss_name = apr_pstrdup(mc->parent, clientname);
283 mc->established = true;
289 if (ret == HTTP_UNAUTHORIZED) {
290 if (output.length != 0) {
291 replen = apr_base64_encode_len(output.length) + 1;
292 reply = apr_pcalloc(req->pool, 10 + replen);
294 memcpy(reply, "Negotiate ", 10);
295 apr_base64_encode(&reply[10], output.value, output.length);
296 apr_table_add(req->err_headers_out,
297 "WWW-Authenticate", reply);
300 apr_table_add(req->err_headers_out,
301 "WWW-Authenticate", "Negotiate");
304 gss_release_cred(&min, &delegated_cred);
305 gss_release_buffer(&min, &output);
306 gss_release_name(&min, &client);
307 gss_release_buffer(&min, &name);
308 gss_release_buffer(&min, &lname);
313 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
315 struct mag_config *cfg;
317 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
318 if (!cfg) return NULL;
323 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
325 struct mag_config *cfg = (struct mag_config *)mconfig;
326 cfg->ssl_only = on ? true : false;
330 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
332 struct mag_config *cfg = (struct mag_config *)mconfig;
333 cfg->map_to_local = on ? true : false;
337 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
339 struct mag_config *cfg = (struct mag_config *)mconfig;
340 cfg->gss_conn_ctx = on ? true : false;
344 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
347 struct mag_config *cfg = (struct mag_config *)mconfig;
348 gss_key_value_element_desc *elements;
357 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
358 "%s [%s]", "Invalid syntax for GSSCredStore option", w);
362 key = apr_pstrndup(parms->pool, w, (p-w));
363 value = apr_pstrdup(parms->pool, p + 1);
364 if (!key || !value) {
365 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
366 "%s", "OOM handling GSSCredStore option");
370 size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
371 elements = apr_palloc(parms->pool, size);
373 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
374 "%s", "OOM handling GSSCredStore option");
378 for (count = 0; count < cfg->cred_store.count; count++) {
379 elements[count] = cfg->cred_store.elements[count];
381 elements[count].key = key;
382 elements[count].value = value;
384 cfg->cred_store.elements = elements;
385 cfg->cred_store.count = count;
390 static const command_rec mag_commands[] = {
391 AP_INIT_FLAG("GSSSSLOnly", mag_ssl_only, NULL, OR_AUTHCFG,
392 "Work only if connection is SSL Secured"),
393 AP_INIT_FLAG("GSSLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
394 "Work only if connection is SSL Secured"),
395 AP_INIT_FLAG("GSSConnectionContext", mag_conn_ctx, NULL, OR_AUTHCFG,
396 "Authentication is valid for the life of the connection"),
397 AP_INIT_ITERATE("GSSCredStore", mag_cred_store, NULL, OR_AUTHCFG,
403 mag_register_hooks(apr_pool_t *p)
405 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
406 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
407 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
410 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
412 STANDARD20_MODULE_STUFF,
413 mag_create_dir_config,