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;
139 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
140 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
141 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
142 gss_name_t client = GSS_C_NO_NAME;
143 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
149 gss_OID mech_type = GSS_C_NO_OID;
150 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
151 struct mag_conn *mc = NULL;
153 type = ap_auth_type(req);
154 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
158 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
161 if (!mag_conn_is_https(req->connection)) {
162 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
163 "Not a TLS connection, refusing to authenticate!");
168 if (cfg->gss_conn_ctx) {
169 mc = (struct mag_conn *)ap_get_module_config(
170 req->connection->conn_config,
171 &auth_gssapi_module);
175 if (mc->established) {
176 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
177 "Connection bound pre-authentication found.");
178 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
179 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
180 req->user = apr_pstrdup(req->pool, mc->user_name);
189 auth_header = apr_table_get(req->headers_in, "Authorization");
190 if (!auth_header) goto done;
192 auth_header_type = ap_getword_white(req->pool, &auth_header);
193 if (!auth_header_type) goto done;
195 if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
197 auth_header_value = ap_getword_white(req->pool, &auth_header);
198 if (!auth_header_value) goto done;
199 input.length = apr_base64_decode_len(auth_header_value) + 1;
200 input.value = apr_pcalloc(req->pool, input.length);
201 if (!input.value) goto done;
202 input.length = apr_base64_decode(input.value, auth_header_value);
204 maj = gss_accept_sec_context(&min, pctx, GSS_C_NO_CREDENTIAL,
205 &input, GSS_C_NO_CHANNEL_BINDINGS,
206 &client, &mech_type, &output, &flags, NULL,
208 if (GSS_ERROR(maj)) {
209 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
210 mag_error(req, "gss_accept_sec_context() failed",
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 "GSSConnectionContext is off.");
220 gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
221 gss_release_buffer(&min, &output);
227 /* once the connection has been accepted we do not need the context
228 * anymore, discard it. FIXME: we also need a destructor for those
229 * mechanisms (like NTLMSSP) that do not complete in one step */
230 gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
232 #ifdef HAVE_GSS_STORE_CRED_INTO
233 if (cfg->cred_store && delegated_cred != GSS_C_NO_CREDENTIAL) {
234 gss_key_value_set_desc store = {0, NULL};
235 /* FIXME: run substtutions */
237 maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
238 GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
242 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
244 /* Always set the GSS name in an env var */
245 maj = gss_display_name(&min, client, &name, NULL);
246 if (GSS_ERROR(maj)) {
247 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
248 mag_error(req, "gss_accept_sec_context() failed",
252 clientname = apr_pstrndup(req->pool, name.value, name.length);
253 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
255 if (cfg->map_to_local) {
256 maj = gss_localname(&min, client, mech_type, &lname);
257 if (maj != GSS_S_COMPLETE) {
258 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
259 mag_error(req, "gss_localname() failed", maj, min));
262 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
264 req->user = clientname;
268 mc->user_name = apr_pstrdup(req->connection->pool, req->user);
269 mc->gss_name = apr_pstrdup(req->connection->pool, clientname);
270 mc->established = true;
276 if (ret == HTTP_UNAUTHORIZED) {
277 if (output.length != 0) {
278 replen = apr_base64_encode_len(output.length) + 1;
279 reply = apr_pcalloc(req->pool, 10 + replen);
281 memcpy(reply, "Negotiate ", 10);
282 apr_base64_encode(&reply[10], output.value, output.length);
283 apr_table_add(req->err_headers_out,
284 "WWW-Authenticate", reply);
287 apr_table_add(req->err_headers_out,
288 "WWW-Authenticate", "Negotiate");
291 gss_release_cred(&min, &delegated_cred);
292 gss_release_buffer(&min, &output);
293 gss_release_name(&min, &client);
294 gss_release_buffer(&min, &name);
295 gss_release_buffer(&min, &lname);
300 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
302 struct mag_config *cfg;
304 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
305 if (!cfg) return NULL;
310 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
312 struct mag_config *cfg = (struct mag_config *)mconfig;
313 cfg->ssl_only = on ? true : false;
317 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
319 struct mag_config *cfg = (struct mag_config *)mconfig;
320 cfg->map_to_local = on ? true : false;
324 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
326 struct mag_config *cfg = (struct mag_config *)mconfig;
327 cfg->gss_conn_ctx = on ? true : false;
331 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
334 struct mag_config *cfg = (struct mag_config *)mconfig;
335 gss_key_value_element_desc *elements;
344 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
345 "%s [%s]", "Invalid syntax for GSSCredStore option", w);
349 key = apr_pstrndup(parms->pool, w, (p-w));
350 value = apr_pstrdup(parms->pool, p + 1);
351 if (!key || !value) {
352 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
353 "%s", "OOM handling GSSCredStore option");
357 size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
358 elements = apr_palloc(parms->pool, size);
360 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
361 "%s", "OOM handling GSSCredStore option");
365 for (count = 0; count < cfg->cred_store.count; count++) {
366 elements[count] = cfg->cred_store.elements[count];
368 elements[count].key = key;
369 elements[count].value = value;
371 cfg->cred_store.elements = elements;
372 cfg->cred_store.count = count;
377 static const command_rec mag_commands[] = {
378 AP_INIT_FLAG("GSSSSLOnly", mag_ssl_only, NULL, OR_AUTHCFG,
379 "Work only if connection is SSL Secured"),
380 AP_INIT_FLAG("GSSLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
381 "Work only if connection is SSL Secured"),
382 AP_INIT_FLAG("GSSConnectionContext", mag_conn_ctx, NULL, OR_AUTHCFG,
383 "Authentication is valid for the life of the connection"),
384 AP_INIT_ITERATE("GSSCredStore", mag_cred_store, NULL, OR_AUTHCFG,
390 mag_register_hooks(apr_pool_t *p)
392 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
393 ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
394 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
397 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
399 STANDARD20_MODULE_STUFF,
400 mag_create_dir_config,