Fix base64 encoding of tokens
[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 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
91
92 static int mag_post_config(apr_pool_t *cfg, apr_pool_t *log,
93                            apr_pool_t *temp, server_rec *s)
94 {
95     /* FIXME: create mutex to deal with connections and contexts ? */
96     mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
97
98     return OK;
99 }
100
101
102 struct mag_conn {
103     gss_ctx_id_t ctx;
104     bool established;
105     char *user_name;
106     char *gss_name;
107 };
108
109 static int mag_pre_connection(conn_rec *c, void *csd)
110 {
111     struct mag_conn *mc;
112
113     mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
114     if (!mc) return DECLINED;
115
116     ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
117     return OK;
118 }
119
120 static bool mag_conn_is_https(conn_rec *c)
121 {
122     if (mag_is_https) {
123         if (mag_is_https(c)) return true;
124     }
125
126     return false;
127 }
128
129 static int mag_auth(request_rec *req)
130 {
131     const char *type;
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;
143     uint32_t flags;
144     uint32_t maj, min;
145     char *reply;
146     size_t replen;
147     char *clientname;
148     gss_OID mech_type = GSS_C_NO_OID;
149     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
150     struct mag_conn *mc = NULL;
151
152     type = ap_auth_type(req);
153     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
154         return DECLINED;
155     }
156
157     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
158
159     if (cfg->ssl_only) {
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!");
163             goto done;
164         }
165     }
166
167     if (cfg->gss_conn_ctx) {
168         mc = (struct mag_conn *)ap_get_module_config(
169                                                 req->connection->conn_config,
170                                                 &auth_gssapi_module);
171         if (!mc) {
172             return DECLINED;
173         }
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);
180             ret = OK;
181             goto done;
182         } else {
183             ctx = mc->ctx;
184         }
185     }
186
187     auth_header = apr_table_get(req->headers_in, "Authorization");
188     if (!auth_header) goto done;
189
190     auth_header_type = ap_getword_white(req->pool, &auth_header);
191     if (!auth_header_type) goto done;
192
193     if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
194
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);
201
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,
205                                  &delegated_cred);
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",
209                                 maj, min));
210         goto done;
211     }
212
213     if (mc) {
214         mc->ctx = ctx;
215         ctx = GSS_C_NO_CONTEXT;
216     }
217
218     if (maj == GSS_S_CONTINUE_NEEDED) goto done;
219
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 */
224
225         maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
226                                   GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
227     }
228 #endif
229
230     req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
231
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",
237                                 maj, min));
238         goto done;
239     }
240     clientname = apr_pstrndup(req->pool, name.value, name.length);
241     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
242
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));
248             goto done;
249         }
250         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
251     } else {
252         req->user = clientname;
253     }
254
255     if (mc) {
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;
259     }
260
261     ret = OK;
262
263 done:
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);
268             if (reply) {
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);
273             }
274         } else {
275             apr_table_add(req->err_headers_out,
276                           "WWW-Authenticate", "Negotiate");
277         }
278     }
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);
285     return ret;
286 }
287
288
289 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
290 {
291     struct mag_config *cfg;
292
293     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
294     if (!cfg) return NULL;
295
296     return cfg;
297 }
298
299 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
300 {
301     struct mag_config *cfg = (struct mag_config *)mconfig;
302     cfg->ssl_only = on ? true : false;
303     return NULL;
304 }
305
306 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
307 {
308     struct mag_config *cfg = (struct mag_config *)mconfig;
309     cfg->map_to_local = on ? true : false;
310     return NULL;
311 }
312
313 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
314 {
315     struct mag_config *cfg = (struct mag_config *)mconfig;
316     cfg->gss_conn_ctx = on ? true : false;
317     return NULL;
318 }
319
320 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
321                                   const char *w)
322 {
323     struct mag_config *cfg = (struct mag_config *)mconfig;
324     gss_key_value_element_desc *elements;
325     uint32_t count;
326     size_t size;
327     const char *p;
328     char *value;
329     char *key;
330
331     p = strchr(w, ':');
332     if (!p) {
333         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
334                      "%s [%s]", "Invalid syntax for GSSCredStore option", w);
335         return NULL;
336     }
337
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");
343         return NULL;
344     }
345
346     size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
347     elements = apr_palloc(parms->pool, size);
348     if (!elements) {
349         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
350                      "%s", "OOM handling GSSCredStore option");
351         return NULL;
352     }
353
354     for (count = 0; count < cfg->cred_store.count; count++) {
355         elements[count] = cfg->cred_store.elements[count];
356     }
357     elements[count].key = key;
358     elements[count].value = value;
359
360     cfg->cred_store.elements = elements;
361     cfg->cred_store.count = count;
362
363     return NULL;
364 }
365
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,
374                     "Credential Store"),
375     { NULL }
376 };
377
378 static void
379 mag_register_hooks(apr_pool_t *p)
380 {
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);
384 }
385
386 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
387 {
388     STANDARD20_MODULE_STUFF,
389     mag_create_dir_config,
390     NULL,
391     NULL,
392     NULL,
393     mag_commands,
394     mag_register_hooks
395 };