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;
44 gss_key_value_set_desc cred_store;
47 static char *mag_status(request_rec *req, int type, uint32_t err)
49 uint32_t maj_ret, min_ret;
58 maj_ret = gss_display_status(&min_ret, err, type,
59 GSS_C_NO_OID, &msg_ctx, &text);
60 if (maj_ret != GSS_S_COMPLETE) {
66 msg_ret = apr_psprintf(req->pool, "%s, %*s",
67 msg_ret, len, (char *)text.value);
69 msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
71 gss_release_buffer(&min_ret, &text);
72 } while (msg_ctx != 0);
77 static char *mag_error(request_rec *req, const char *msg,
78 uint32_t maj, uint32_t min)
83 msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
84 msg_min = mag_status(req, GSS_C_MECH_CODE, min);
85 return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
95 static int mag_pre_connection(conn_rec *c, void *csd)
99 mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
100 if (!mc) return DECLINED;
102 ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
106 static int mag_auth(request_rec *req)
109 struct mag_config *cfg;
110 const char *auth_header;
111 char *auth_header_type;
112 char *auth_header_value;
113 int ret = HTTP_UNAUTHORIZED;
114 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
115 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
116 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
117 gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
118 gss_name_t client = GSS_C_NO_NAME;
119 gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
125 gss_OID mech_type = GSS_C_NO_OID;
126 gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
127 struct mag_conn *mc = NULL;
129 type = ap_auth_type(req);
130 if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
134 cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
137 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
138 "FIXME: check for ssl!");
141 if (cfg->gss_conn_ctx) {
142 mc = (struct mag_conn *)ap_get_module_config(
143 req->connection->conn_config,
144 &auth_gssapi_module);
148 if (mc->established) {
149 ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
150 "Connection bound pre-authentication found.");
151 apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
152 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
153 req->user = apr_pstrdup(req->pool, mc->user_name);
161 auth_header = apr_table_get(req->headers_in, "Authorization");
162 if (!auth_header) goto done;
164 auth_header_type = ap_getword_white(req->pool, &auth_header);
165 if (!auth_header_type) goto done;
167 if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
169 auth_header_value = ap_getword_white(req->pool, &auth_header);
170 if (!auth_header_value) goto done;
171 input.length = apr_base64_decode_len(auth_header_value) + 1;
172 input.value = apr_pcalloc(req->pool, input.length);
173 if (!input.value) goto done;
174 input.length = apr_base64_decode(input.value, auth_header_value);
176 maj = gss_accept_sec_context(&min, &ctx, GSS_C_NO_CREDENTIAL,
177 &input, GSS_C_NO_CHANNEL_BINDINGS,
178 &client, &mech_type, &output, &flags, NULL,
180 if (GSS_ERROR(maj)) {
181 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
182 mag_error(req, "gss_accept_sec_context() failed",
189 ctx = GSS_C_NO_CONTEXT;
192 if (maj == GSS_S_CONTINUE_NEEDED) goto done;
194 #ifdef HAVE_GSS_STORE_CRED_INTO
195 if (cfg->cred_store && delegated_cred != GSS_C_NO_CREDENTIAL) {
196 gss_key_value_set_desc store = {0, NULL};
197 /* FIXME: run substtutions */
199 maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
200 GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
204 req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
206 /* Always set the GSS name in an env var */
207 maj = gss_display_name(&min, client, &name, 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",
214 clientname = apr_pstrndup(req->pool, name.value, name.length);
215 apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
217 if (cfg->map_to_local) {
218 maj = gss_localname(&min, client, mech_type, &lname);
219 if (maj != GSS_S_COMPLETE) {
220 ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
221 mag_error(req, "gss_localname() failed", maj, min));
224 req->user = apr_pstrndup(req->pool, lname.value, lname.length);
226 req->user = clientname;
230 mc->user_name = apr_pstrdup(req->connection->pool, req->user);
231 mc->gss_name = apr_pstrdup(req->connection->pool, clientname);
232 mc->established = true;
238 if (ret == HTTP_UNAUTHORIZED) {
239 if (output.length != 0) {
240 replen = apr_base64_encode_len(output.length) + 1;
241 reply = apr_pcalloc(req->pool, 10 + replen);
243 memcpy(reply, "Negotiate ", 10);
244 apr_base64_encode(&reply[10], output.value, output.length);
245 reply[replen] = '\0';
246 apr_table_add(req->err_headers_out,
247 "WWW-Authenticate", reply);
250 apr_table_add(req->err_headers_out,
251 "WWW-Authenticate", "Negotiate");
254 gss_release_cred(&min, &delegated_cred);
255 gss_release_buffer(&min, &output);
256 gss_release_name(&min, &client);
257 gss_release_buffer(&min, &name);
258 gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
259 gss_release_buffer(&min, &lname);
264 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
266 struct mag_config *cfg;
268 cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
269 if (!cfg) return NULL;
274 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
276 struct mag_config *cfg = (struct mag_config *)mconfig;
277 cfg->ssl_only = on ? true : false;
281 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
283 struct mag_config *cfg = (struct mag_config *)mconfig;
284 cfg->map_to_local = on ? true : false;
288 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
290 struct mag_config *cfg = (struct mag_config *)mconfig;
291 cfg->gss_conn_ctx = on ? true : false;
295 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
298 struct mag_config *cfg = (struct mag_config *)mconfig;
299 gss_key_value_element_desc *elements;
308 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
309 "%s [%s]", "Invalid syntax for GSSCredStore option", w);
313 key = apr_pstrndup(parms->pool, w, (p-w));
314 value = apr_pstrdup(parms->pool, p + 1);
315 if (!key || !value) {
316 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
317 "%s", "OOM handling GSSCredStore option");
321 size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
322 elements = apr_palloc(parms->pool, size);
324 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
325 "%s", "OOM handling GSSCredStore option");
329 for (count = 0; count < cfg->cred_store.count; count++) {
330 elements[count] = cfg->cred_store.elements[count];
332 elements[count].key = key;
333 elements[count].value = value;
335 cfg->cred_store.elements = elements;
336 cfg->cred_store.count = count;
341 static const command_rec mag_commands[] = {
342 AP_INIT_FLAG("GSSSSLOnly", mag_ssl_only, NULL, OR_AUTHCFG,
343 "Work only if connection is SSL Secured"),
344 AP_INIT_FLAG("GSSLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
345 "Work only if connection is SSL Secured"),
346 AP_INIT_FLAG("GSSConnectionContext", mag_conn_ctx, NULL, OR_AUTHCFG,
347 "Authentication is valid for the life of the connection"),
348 AP_INIT_ITERATE("GSSCredStore", mag_cred_store, NULL, OR_AUTHCFG,
354 mag_register_hooks(apr_pool_t *p)
356 ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
357 ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
360 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
362 STANDARD20_MODULE_STUFF,
363 mag_create_dir_config,