Implement checking for TLS connections
[mod_auth_gssapi.git] / src / mod_auth_gssapi.c
1 /*
2    MOD AUTH GSSAPI
3
4    Copyright (C) 2014 Simo Sorce <simo@redhat.com>
5
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:
12
13    The above copyright notice and this permission notice shall be included in
14    all copies or substantial portions of the Software.
15
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.
23 */
24
25 #include <stdbool.h>
26 #include <stdint.h>
27 #include <gssapi/gssapi.h>
28 #include <gssapi/gssapi_ext.h>
29
30 #include <httpd.h>
31 #include <http_core.h>
32 #include <http_connection.h>
33 #include <http_log.h>
34 #include <http_request.h>
35 #include <apr_strings.h>
36 #include <apr_base64.h>
37
38 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
39
40 APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
41
42 struct mag_config {
43     bool ssl_only;
44     bool map_to_local;
45     bool gss_conn_ctx;
46     gss_key_value_set_desc cred_store;
47 };
48
49 static char *mag_status(request_rec *req, int type, uint32_t err)
50 {
51     uint32_t maj_ret, min_ret;
52     gss_buffer_desc text;
53     uint32_t msg_ctx;
54     char *msg_ret;
55     int len;
56
57     msg_ret = NULL;
58     msg_ctx = 0;
59     do {
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) {
63             return msg_ret;
64         }
65
66         len = text.length;
67         if (msg_ret) {
68             msg_ret = apr_psprintf(req->pool, "%s, %*s",
69                                    msg_ret, len, (char *)text.value);
70         } else {
71             msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
72         }
73         gss_release_buffer(&min_ret, &text);
74     } while (msg_ctx != 0);
75
76     return msg_ret;
77 }
78
79 static char *mag_error(request_rec *req, const char *msg,
80                        uint32_t maj, uint32_t min)
81 {
82     char *msg_maj;
83     char *msg_min;
84
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);
88 }
89
90 struct mag_conn {
91     gss_ctx_id_t ctx;
92     bool established;
93     char *user_name;
94     char *gss_name;
95 };
96
97 static int mag_pre_connection(conn_rec *c, void *csd)
98 {
99     struct mag_conn *mc;
100
101     mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
102     if (!mc) return DECLINED;
103
104     ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
105     return OK;
106 }
107
108 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
109
110 static bool mag_conn_is_https(conn_rec *c)
111 {
112     if (mag_is_https) {
113         if (mag_is_https(c)) return true;
114     }
115
116     return false;
117 }
118
119 static int mag_auth(request_rec *req)
120 {
121     const char *type;
122     struct mag_config *cfg;
123     const char *auth_header;
124     char *auth_header_type;
125     char *auth_header_value;
126     int ret = HTTP_UNAUTHORIZED;
127     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
128     gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
129     gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
130     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
131     gss_name_t client = GSS_C_NO_NAME;
132     gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
133     uint32_t flags;
134     uint32_t maj, min;
135     char *reply;
136     size_t replen;
137     char *clientname;
138     gss_OID mech_type = GSS_C_NO_OID;
139     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
140     struct mag_conn *mc = NULL;
141
142     type = ap_auth_type(req);
143     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
144         return DECLINED;
145     }
146
147     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
148
149     if (cfg->ssl_only) {
150         if (!mag_conn_is_https(req->connection)) {
151             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
152                           "Not a TLS connection, refusing to authenticate!");
153             goto done;
154         }
155     }
156
157     if (cfg->gss_conn_ctx) {
158         mc = (struct mag_conn *)ap_get_module_config(
159                                                 req->connection->conn_config,
160                                                 &auth_gssapi_module);
161         if (!mc) {
162             return DECLINED;
163         }
164         if (mc->established) {
165             ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
166                           "Connection bound pre-authentication found.");
167             apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
168             req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
169             req->user = apr_pstrdup(req->pool, mc->user_name);
170             ret = OK;
171             goto done;
172         } else {
173             ctx = mc->ctx;
174         }
175     }
176
177     auth_header = apr_table_get(req->headers_in, "Authorization");
178     if (!auth_header) goto done;
179
180     auth_header_type = ap_getword_white(req->pool, &auth_header);
181     if (!auth_header_type) goto done;
182
183     if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
184
185     auth_header_value = ap_getword_white(req->pool, &auth_header);
186     if (!auth_header_value) goto done;
187     input.length = apr_base64_decode_len(auth_header_value) + 1;
188     input.value = apr_pcalloc(req->pool, input.length);
189     if (!input.value) goto done;
190     input.length = apr_base64_decode(input.value, auth_header_value);
191
192     maj = gss_accept_sec_context(&min, &ctx, GSS_C_NO_CREDENTIAL,
193                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
194                                  &client, &mech_type, &output, &flags, NULL,
195                                  &delegated_cred);
196     if (GSS_ERROR(maj)) {
197         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
198                       mag_error(req, "gss_accept_sec_context() failed",
199                                 maj, min));
200         goto done;
201     }
202
203     if (mc) {
204         mc->ctx = ctx;
205         ctx = GSS_C_NO_CONTEXT;
206     }
207
208     if (maj == GSS_S_CONTINUE_NEEDED) goto done;
209
210 #ifdef HAVE_GSS_STORE_CRED_INTO
211     if (cfg->cred_store && delegated_cred != GSS_C_NO_CREDENTIAL) {
212         gss_key_value_set_desc store = {0, NULL};
213         /* FIXME: run substtutions */
214
215         maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
216                                   GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
217     }
218 #endif
219
220     req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
221
222     /* Always set the GSS name in an env var */
223     maj = gss_display_name(&min, client, &name, NULL);
224     if (GSS_ERROR(maj)) {
225         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
226                       mag_error(req, "gss_accept_sec_context() failed",
227                                 maj, min));
228         goto done;
229     }
230     clientname = apr_pstrndup(req->pool, name.value, name.length);
231     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
232
233     if (cfg->map_to_local) {
234         maj = gss_localname(&min, client, mech_type, &lname);
235         if (maj != GSS_S_COMPLETE) {
236             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
237                           mag_error(req, "gss_localname() failed", maj, min));
238             goto done;
239         }
240         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
241     } else {
242         req->user = clientname;
243     }
244
245     if (mc) {
246         mc->user_name = apr_pstrdup(req->connection->pool, req->user);
247         mc->gss_name = apr_pstrdup(req->connection->pool, clientname);
248         mc->established = true;
249     }
250
251     ret = OK;
252
253 done:
254     if (ret == HTTP_UNAUTHORIZED) {
255         if (output.length != 0) {
256             replen = apr_base64_encode_len(output.length) + 1;
257             reply = apr_pcalloc(req->pool, 10 + replen);
258             if (reply) {
259                 memcpy(reply, "Negotiate ", 10);
260                 apr_base64_encode(&reply[10], output.value, output.length);
261                 reply[replen] = '\0';
262                 apr_table_add(req->err_headers_out,
263                               "WWW-Authenticate", reply);
264             }
265         } else {
266             apr_table_add(req->err_headers_out,
267                           "WWW-Authenticate", "Negotiate");
268         }
269     }
270     gss_release_cred(&min, &delegated_cred);
271     gss_release_buffer(&min, &output);
272     gss_release_name(&min, &client);
273     gss_release_buffer(&min, &name);
274     gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
275     gss_release_buffer(&min, &lname);
276     return ret;
277 }
278
279
280 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
281 {
282     struct mag_config *cfg;
283
284     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
285     if (!cfg) return NULL;
286
287     return cfg;
288 }
289
290 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
291 {
292     struct mag_config *cfg = (struct mag_config *)mconfig;
293     cfg->ssl_only = on ? true : false;
294     return NULL;
295 }
296
297 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
298 {
299     struct mag_config *cfg = (struct mag_config *)mconfig;
300     cfg->map_to_local = on ? true : false;
301     return NULL;
302 }
303
304 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
305 {
306     struct mag_config *cfg = (struct mag_config *)mconfig;
307     cfg->gss_conn_ctx = on ? true : false;
308     return NULL;
309 }
310
311 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
312                                   const char *w)
313 {
314     struct mag_config *cfg = (struct mag_config *)mconfig;
315     gss_key_value_element_desc *elements;
316     uint32_t count;
317     size_t size;
318     const char *p;
319     char *value;
320     char *key;
321
322     p = strchr(w, ':');
323     if (!p) {
324         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
325                      "%s [%s]", "Invalid syntax for GSSCredStore option", w);
326         return NULL;
327     }
328
329     key = apr_pstrndup(parms->pool, w, (p-w));
330     value = apr_pstrdup(parms->pool, p + 1);
331     if (!key || !value) {
332         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
333                      "%s", "OOM handling GSSCredStore option");
334         return NULL;
335     }
336
337     size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
338     elements = apr_palloc(parms->pool, size);
339     if (!elements) {
340         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
341                      "%s", "OOM handling GSSCredStore option");
342         return NULL;
343     }
344
345     for (count = 0; count < cfg->cred_store.count; count++) {
346         elements[count] = cfg->cred_store.elements[count];
347     }
348     elements[count].key = key;
349     elements[count].value = value;
350
351     cfg->cred_store.elements = elements;
352     cfg->cred_store.count = count;
353
354     return NULL;
355 }
356
357 static const command_rec mag_commands[] = {
358     AP_INIT_FLAG("GSSSSLOnly", mag_ssl_only, NULL, OR_AUTHCFG,
359                   "Work only if connection is SSL Secured"),
360     AP_INIT_FLAG("GSSLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
361                   "Work only if connection is SSL Secured"),
362     AP_INIT_FLAG("GSSConnectionContext", mag_conn_ctx, NULL, OR_AUTHCFG,
363                   "Authentication is valid for the life of the connection"),
364     AP_INIT_ITERATE("GSSCredStore", mag_cred_store, NULL, OR_AUTHCFG,
365                     "Credential Store"),
366     { NULL }
367 };
368
369 static void
370 mag_register_hooks(apr_pool_t *p)
371 {
372     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
373     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
374 }
375
376 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
377 {
378     STANDARD20_MODULE_STUFF,
379     mag_create_dir_config,
380     NULL,
381     NULL,
382     NULL,
383     mag_commands,
384     mag_register_hooks
385 };