Register optional functions
[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                 reply[replen] = '\0';
272                 apr_table_add(req->err_headers_out,
273                               "WWW-Authenticate", reply);
274             }
275         } else {
276             apr_table_add(req->err_headers_out,
277                           "WWW-Authenticate", "Negotiate");
278         }
279     }
280     gss_release_cred(&min, &delegated_cred);
281     gss_release_buffer(&min, &output);
282     gss_release_name(&min, &client);
283     gss_release_buffer(&min, &name);
284     gss_delete_sec_context(&min, &ctx, GSS_C_NO_BUFFER);
285     gss_release_buffer(&min, &lname);
286     return ret;
287 }
288
289
290 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
291 {
292     struct mag_config *cfg;
293
294     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
295     if (!cfg) return NULL;
296
297     return cfg;
298 }
299
300 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
301 {
302     struct mag_config *cfg = (struct mag_config *)mconfig;
303     cfg->ssl_only = on ? true : false;
304     return NULL;
305 }
306
307 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
308 {
309     struct mag_config *cfg = (struct mag_config *)mconfig;
310     cfg->map_to_local = on ? true : false;
311     return NULL;
312 }
313
314 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
315 {
316     struct mag_config *cfg = (struct mag_config *)mconfig;
317     cfg->gss_conn_ctx = on ? true : false;
318     return NULL;
319 }
320
321 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
322                                   const char *w)
323 {
324     struct mag_config *cfg = (struct mag_config *)mconfig;
325     gss_key_value_element_desc *elements;
326     uint32_t count;
327     size_t size;
328     const char *p;
329     char *value;
330     char *key;
331
332     p = strchr(w, ':');
333     if (!p) {
334         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
335                      "%s [%s]", "Invalid syntax for GSSCredStore option", w);
336         return NULL;
337     }
338
339     key = apr_pstrndup(parms->pool, w, (p-w));
340     value = apr_pstrdup(parms->pool, p + 1);
341     if (!key || !value) {
342         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
343                      "%s", "OOM handling GSSCredStore option");
344         return NULL;
345     }
346
347     size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
348     elements = apr_palloc(parms->pool, size);
349     if (!elements) {
350         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
351                      "%s", "OOM handling GSSCredStore option");
352         return NULL;
353     }
354
355     for (count = 0; count < cfg->cred_store.count; count++) {
356         elements[count] = cfg->cred_store.elements[count];
357     }
358     elements[count].key = key;
359     elements[count].value = value;
360
361     cfg->cred_store.elements = elements;
362     cfg->cred_store.count = count;
363
364     return NULL;
365 }
366
367 static const command_rec mag_commands[] = {
368     AP_INIT_FLAG("GSSSSLOnly", mag_ssl_only, NULL, OR_AUTHCFG,
369                   "Work only if connection is SSL Secured"),
370     AP_INIT_FLAG("GSSLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
371                   "Work only if connection is SSL Secured"),
372     AP_INIT_FLAG("GSSConnectionContext", mag_conn_ctx, NULL, OR_AUTHCFG,
373                   "Authentication is valid for the life of the connection"),
374     AP_INIT_ITERATE("GSSCredStore", mag_cred_store, NULL, OR_AUTHCFG,
375                     "Credential Store"),
376     { NULL }
377 };
378
379 static void
380 mag_register_hooks(apr_pool_t *p)
381 {
382     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
383     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
384     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
385 }
386
387 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
388 {
389     STANDARD20_MODULE_STUFF,
390     mag_create_dir_config,
391     NULL,
392     NULL,
393     NULL,
394     mag_commands,
395     mag_register_hooks
396 };