Fix use after free
[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_ctx_id_t *pctx;
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;
144     uint32_t flags;
145     uint32_t maj, min;
146     char *reply;
147     size_t replen;
148     char *clientname;
149     gss_OID mech_type = GSS_C_NO_OID;
150     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
151     struct mag_conn *mc = NULL;
152
153     type = ap_auth_type(req);
154     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
155         return DECLINED;
156     }
157
158     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
159
160     if (cfg->ssl_only) {
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!");
164             goto done;
165         }
166     }
167
168     if (cfg->gss_conn_ctx) {
169         mc = (struct mag_conn *)ap_get_module_config(
170                                                 req->connection->conn_config,
171                                                 &auth_gssapi_module);
172         if (!mc) {
173             return DECLINED;
174         }
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);
181             ret = OK;
182             goto done;
183         }
184         pctx = &mc->ctx;
185     } else {
186         pctx = &ctx;
187     }
188
189     auth_header = apr_table_get(req->headers_in, "Authorization");
190     if (!auth_header) goto done;
191
192     auth_header_type = ap_getword_white(req->pool, &auth_header);
193     if (!auth_header_type) goto done;
194
195     if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
196
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);
203
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,
207                                  &delegated_cred);
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",
211                                 maj, min));
212         goto done;
213     }
214
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);
222             output.length = 0;
223         }
224         goto done;
225     }
226
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);
231
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 */
236
237         maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
238                                   GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
239     }
240 #endif
241
242     req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
243
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",
249                                 maj, min));
250         goto done;
251     }
252     clientname = apr_pstrndup(req->pool, name.value, name.length);
253     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
254
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));
260             goto done;
261         }
262         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
263     } else {
264         req->user = clientname;
265     }
266
267     if (mc) {
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;
271     }
272
273     ret = OK;
274
275 done:
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);
280             if (reply) {
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);
285             }
286         } else {
287             apr_table_add(req->err_headers_out,
288                           "WWW-Authenticate", "Negotiate");
289         }
290     }
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);
296     return ret;
297 }
298
299
300 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
301 {
302     struct mag_config *cfg;
303
304     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
305     if (!cfg) return NULL;
306
307     return cfg;
308 }
309
310 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
311 {
312     struct mag_config *cfg = (struct mag_config *)mconfig;
313     cfg->ssl_only = on ? true : false;
314     return NULL;
315 }
316
317 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
318 {
319     struct mag_config *cfg = (struct mag_config *)mconfig;
320     cfg->map_to_local = on ? true : false;
321     return NULL;
322 }
323
324 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
325 {
326     struct mag_config *cfg = (struct mag_config *)mconfig;
327     cfg->gss_conn_ctx = on ? true : false;
328     return NULL;
329 }
330
331 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
332                                   const char *w)
333 {
334     struct mag_config *cfg = (struct mag_config *)mconfig;
335     gss_key_value_element_desc *elements;
336     uint32_t count;
337     size_t size;
338     const char *p;
339     char *value;
340     char *key;
341
342     p = strchr(w, ':');
343     if (!p) {
344         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
345                      "%s [%s]", "Invalid syntax for GSSCredStore option", w);
346         return NULL;
347     }
348
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");
354         return NULL;
355     }
356
357     size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
358     elements = apr_palloc(parms->pool, size);
359     if (!elements) {
360         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
361                      "%s", "OOM handling GSSCredStore option");
362         return NULL;
363     }
364
365     for (count = 0; count < cfg->cred_store.count; count++) {
366         elements[count] = cfg->cred_store.elements[count];
367     }
368     elements[count].key = key;
369     elements[count].value = value;
370
371     cfg->cred_store.elements = elements;
372     cfg->cred_store.count = count;
373
374     return NULL;
375 }
376
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,
385                     "Credential Store"),
386     { NULL }
387 };
388
389 static void
390 mag_register_hooks(apr_pool_t *p)
391 {
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);
395 }
396
397 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
398 {
399     STANDARD20_MODULE_STUFF,
400     mag_create_dir_config,
401     NULL,
402     NULL,
403     NULL,
404     mag_commands,
405     mag_register_hooks
406 };