Allow context to be attached to the connection
[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 struct mag_config {
41     bool ssl_only;
42     bool map_to_local;
43     bool gss_conn_ctx;
44     gss_key_value_set_desc cred_store;
45 };
46
47 static char *mag_status(request_rec *req, int type, uint32_t err)
48 {
49     uint32_t maj_ret, min_ret;
50     gss_buffer_desc text;
51     uint32_t msg_ctx;
52     char *msg_ret;
53     int len;
54
55     msg_ret = NULL;
56     msg_ctx = 0;
57     do {
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) {
61             return msg_ret;
62         }
63
64         len = text.length;
65         if (msg_ret) {
66             msg_ret = apr_psprintf(req->pool, "%s, %*s",
67                                    msg_ret, len, (char *)text.value);
68         } else {
69             msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
70         }
71         gss_release_buffer(&min_ret, &text);
72     } while (msg_ctx != 0);
73
74     return msg_ret;
75 }
76
77 static char *mag_error(request_rec *req, const char *msg,
78                        uint32_t maj, uint32_t min)
79 {
80     char *msg_maj;
81     char *msg_min;
82
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);
86 }
87
88 struct mag_conn {
89     gss_ctx_id_t ctx;
90     bool established;
91     char *user_name;
92     char *gss_name;
93 };
94
95 static int mag_pre_connection(conn_rec *c, void *csd)
96 {
97     struct mag_conn *mc;
98
99     mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
100     if (!mc) return DECLINED;
101
102     ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
103     return OK;
104 }
105
106 static int mag_auth(request_rec *req)
107 {
108     const char *type;
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;
120     uint32_t flags;
121     uint32_t maj, min;
122     char *reply;
123     size_t replen;
124     char *clientname;
125     gss_OID mech_type = GSS_C_NO_OID;
126     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
127     struct mag_conn *mc = NULL;
128
129     type = ap_auth_type(req);
130     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
131         return DECLINED;
132     }
133
134     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
135
136     if (cfg->ssl_only) {
137         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
138                       "FIXME: check for ssl!");
139     }
140
141     if (cfg->gss_conn_ctx) {
142         mc = (struct mag_conn *)ap_get_module_config(
143                                                 req->connection->conn_config,
144                                                 &auth_gssapi_module);
145         if (!mc) {
146             return DECLINED;
147         }
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);
154             ret = OK;
155             goto done;
156         } else {
157             ctx = mc->ctx;
158         }
159     }
160
161     auth_header = apr_table_get(req->headers_in, "Authorization");
162     if (!auth_header) goto done;
163
164     auth_header_type = ap_getword_white(req->pool, &auth_header);
165     if (!auth_header_type) goto done;
166
167     if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
168
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);
175
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,
179                                  &delegated_cred);
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",
183                                 maj, min));
184         goto done;
185     }
186
187     if (mc) {
188         mc->ctx = ctx;
189         ctx = GSS_C_NO_CONTEXT;
190     }
191
192     if (maj == GSS_S_CONTINUE_NEEDED) goto done;
193
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 */
198
199         maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
200                                   GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
201     }
202 #endif
203
204     req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
205
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",
211                                 maj, min));
212         goto done;
213     }
214     clientname = apr_pstrndup(req->pool, name.value, name.length);
215     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
216
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));
222             goto done;
223         }
224         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
225     } else {
226         req->user = clientname;
227     }
228
229     if (mc) {
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;
233     }
234
235     ret = OK;
236
237 done:
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);
242             if (reply) {
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);
248             }
249         } else {
250             apr_table_add(req->err_headers_out,
251                           "WWW-Authenticate", "Negotiate");
252         }
253     }
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);
260     return ret;
261 }
262
263
264 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
265 {
266     struct mag_config *cfg;
267
268     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
269     if (!cfg) return NULL;
270
271     return cfg;
272 }
273
274 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
275 {
276     struct mag_config *cfg = (struct mag_config *)mconfig;
277     cfg->ssl_only = on ? true : false;
278     return NULL;
279 }
280
281 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
282 {
283     struct mag_config *cfg = (struct mag_config *)mconfig;
284     cfg->map_to_local = on ? true : false;
285     return NULL;
286 }
287
288 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
289 {
290     struct mag_config *cfg = (struct mag_config *)mconfig;
291     cfg->gss_conn_ctx = on ? true : false;
292     return NULL;
293 }
294
295 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
296                                   const char *w)
297 {
298     struct mag_config *cfg = (struct mag_config *)mconfig;
299     gss_key_value_element_desc *elements;
300     uint32_t count;
301     size_t size;
302     const char *p;
303     char *value;
304     char *key;
305
306     p = strchr(w, ':');
307     if (!p) {
308         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
309                      "%s [%s]", "Invalid syntax for GSSCredStore option", w);
310         return NULL;
311     }
312
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");
318         return NULL;
319     }
320
321     size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
322     elements = apr_palloc(parms->pool, size);
323     if (!elements) {
324         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
325                      "%s", "OOM handling GSSCredStore option");
326         return NULL;
327     }
328
329     for (count = 0; count < cfg->cred_store.count; count++) {
330         elements[count] = cfg->cred_store.elements[count];
331     }
332     elements[count].key = key;
333     elements[count].value = value;
334
335     cfg->cred_store.elements = elements;
336     cfg->cred_store.count = count;
337
338     return NULL;
339 }
340
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,
349                     "Credential Store"),
350     { NULL }
351 };
352
353 static void
354 mag_register_hooks(apr_pool_t *p)
355 {
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);
358 }
359
360 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
361 {
362     STANDARD20_MODULE_STUFF,
363     mag_create_dir_config,
364     NULL,
365     NULL,
366     NULL,
367     mag_commands,
368     mag_register_hooks
369 };