Fix typo
[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     apr_pool_t *parent;
104     gss_ctx_id_t ctx;
105     bool established;
106     char *user_name;
107     char *gss_name;
108 };
109
110 static int mag_pre_connection(conn_rec *c, void *csd)
111 {
112     struct mag_conn *mc;
113
114     mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
115     if (!mc) return DECLINED;
116
117     mc->parent = c->pool;
118     ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
119     return OK;
120 }
121
122 static apr_status_t mag_conn_destroy(void *ptr)
123 {
124     struct mag_conn *mc = (struct mag_conn *)ptr;
125     uint32_t min;
126
127     if (mc->ctx) {
128         (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
129         mc->established = false;
130     }
131     return APR_SUCCESS;
132 }
133
134 static bool mag_conn_is_https(conn_rec *c)
135 {
136     if (mag_is_https) {
137         if (mag_is_https(c)) return true;
138     }
139
140     return false;
141 }
142
143 static int mag_auth(request_rec *req)
144 {
145     const char *type;
146     struct mag_config *cfg;
147     const char *auth_header;
148     char *auth_header_type;
149     char *auth_header_value;
150     int ret = HTTP_UNAUTHORIZED;
151     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
152     gss_ctx_id_t *pctx;
153     gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
154     gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
155     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
156     gss_name_t client = GSS_C_NO_NAME;
157     gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
158     uint32_t flags;
159     uint32_t maj, min;
160     char *reply;
161     size_t replen;
162     char *clientname;
163     gss_OID mech_type = GSS_C_NO_OID;
164     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
165     struct mag_conn *mc = NULL;
166
167     type = ap_auth_type(req);
168     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
169         return DECLINED;
170     }
171
172     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
173
174     if (cfg->ssl_only) {
175         if (!mag_conn_is_https(req->connection)) {
176             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
177                           "Not a TLS connection, refusing to authenticate!");
178             goto done;
179         }
180     }
181
182     if (cfg->gss_conn_ctx) {
183         mc = (struct mag_conn *)ap_get_module_config(
184                                                 req->connection->conn_config,
185                                                 &auth_gssapi_module);
186         if (!mc) {
187             return DECLINED;
188         }
189         if (mc->established) {
190             ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
191                           "Connection bound pre-authentication found.");
192             apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
193             req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
194             req->user = apr_pstrdup(req->pool, mc->user_name);
195             ret = OK;
196             goto done;
197         }
198         pctx = &mc->ctx;
199     } else {
200         pctx = &ctx;
201     }
202
203     auth_header = apr_table_get(req->headers_in, "Authorization");
204     if (!auth_header) goto done;
205
206     auth_header_type = ap_getword_white(req->pool, &auth_header);
207     if (!auth_header_type) goto done;
208
209     if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
210
211     auth_header_value = ap_getword_white(req->pool, &auth_header);
212     if (!auth_header_value) goto done;
213     input.length = apr_base64_decode_len(auth_header_value) + 1;
214     input.value = apr_pcalloc(req->pool, input.length);
215     if (!input.value) goto done;
216     input.length = apr_base64_decode(input.value, auth_header_value);
217
218     maj = gss_accept_sec_context(&min, pctx, GSS_C_NO_CREDENTIAL,
219                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
220                                  &client, &mech_type, &output, &flags, NULL,
221                                  &delegated_cred);
222     if (GSS_ERROR(maj)) {
223         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
224                       mag_error(req, "gss_accept_sec_context() failed",
225                                 maj, min));
226         goto done;
227     }
228
229     /* register the context in the connection pool, so it can be freed
230      * when the connection is terminated */
231     apr_pool_userdata_set(mc, "mag_conn_ptr", mag_conn_destroy, mc->parent);
232
233     if (maj == GSS_S_CONTINUE_NEEDED) {
234         if (!cfg->gss_conn_ctx) {
235             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
236                           "Mechanism needs continuation but "
237                           "GssapiConnectionBound is off.");
238             gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
239             gss_release_buffer(&min, &output);
240             output.length = 0;
241         }
242         goto done;
243     }
244
245 #ifdef HAVE_GSS_STORE_CRED_INTO
246     if (cfg->cred_store && delegated_cred != GSS_C_NO_CREDENTIAL) {
247         gss_key_value_set_desc store = {0, NULL};
248         /* FIXME: run substitutions */
249
250         maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
251                                   GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
252     }
253 #endif
254
255     req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
256
257     /* Always set the GSS name in an env var */
258     maj = gss_display_name(&min, client, &name, NULL);
259     if (GSS_ERROR(maj)) {
260         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
261                       mag_error(req, "gss_accept_sec_context() failed",
262                                 maj, min));
263         goto done;
264     }
265     clientname = apr_pstrndup(req->pool, name.value, name.length);
266     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
267
268     if (cfg->map_to_local) {
269         maj = gss_localname(&min, client, mech_type, &lname);
270         if (maj != GSS_S_COMPLETE) {
271             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
272                           mag_error(req, "gss_localname() failed", maj, min));
273             goto done;
274         }
275         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
276     } else {
277         req->user = clientname;
278     }
279
280     if (mc) {
281         mc->user_name = apr_pstrdup(mc->parent, req->user);
282         mc->gss_name = apr_pstrdup(mc->parent, clientname);
283         mc->established = true;
284     }
285
286     ret = OK;
287
288 done:
289     if (ret == HTTP_UNAUTHORIZED) {
290         if (output.length != 0) {
291             replen = apr_base64_encode_len(output.length) + 1;
292             reply = apr_pcalloc(req->pool, 10 + replen);
293             if (reply) {
294                 memcpy(reply, "Negotiate ", 10);
295                 apr_base64_encode(&reply[10], output.value, output.length);
296                 apr_table_add(req->err_headers_out,
297                               "WWW-Authenticate", reply);
298             }
299         } else {
300             apr_table_add(req->err_headers_out,
301                           "WWW-Authenticate", "Negotiate");
302         }
303     }
304     gss_release_cred(&min, &delegated_cred);
305     gss_release_buffer(&min, &output);
306     gss_release_name(&min, &client);
307     gss_release_buffer(&min, &name);
308     gss_release_buffer(&min, &lname);
309     return ret;
310 }
311
312
313 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
314 {
315     struct mag_config *cfg;
316
317     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
318     if (!cfg) return NULL;
319
320     return cfg;
321 }
322
323 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
324 {
325     struct mag_config *cfg = (struct mag_config *)mconfig;
326     cfg->ssl_only = on ? true : false;
327     return NULL;
328 }
329
330 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
331 {
332     struct mag_config *cfg = (struct mag_config *)mconfig;
333     cfg->map_to_local = on ? true : false;
334     return NULL;
335 }
336
337 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
338 {
339     struct mag_config *cfg = (struct mag_config *)mconfig;
340     cfg->gss_conn_ctx = on ? true : false;
341     return NULL;
342 }
343
344 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
345                                   const char *w)
346 {
347     struct mag_config *cfg = (struct mag_config *)mconfig;
348     gss_key_value_element_desc *elements;
349     uint32_t count;
350     size_t size;
351     const char *p;
352     char *value;
353     char *key;
354
355     p = strchr(w, ':');
356     if (!p) {
357         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
358                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
359         return NULL;
360     }
361
362     key = apr_pstrndup(parms->pool, w, (p-w));
363     value = apr_pstrdup(parms->pool, p + 1);
364     if (!key || !value) {
365         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
366                      "%s", "OOM handling GssapiCredStore option");
367         return NULL;
368     }
369
370     size = sizeof(gss_key_value_element_desc) * cfg->cred_store.count + 1;
371     elements = apr_palloc(parms->pool, size);
372     if (!elements) {
373         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
374                      "%s", "OOM handling GssapiCredStore option");
375         return NULL;
376     }
377
378     for (count = 0; count < cfg->cred_store.count; count++) {
379         elements[count] = cfg->cred_store.elements[count];
380     }
381     elements[count].key = key;
382     elements[count].value = value;
383
384     cfg->cred_store.elements = elements;
385     cfg->cred_store.count = count;
386
387     return NULL;
388 }
389
390 static const command_rec mag_commands[] = {
391     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
392                   "Work only if connection is SSL Secured"),
393     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
394                   "Work only if connection is SSL Secured"),
395     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
396                   "Authentication is bound to the TCP connection"),
397     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
398                     "Credential Store"),
399     { NULL }
400 };
401
402 static void
403 mag_register_hooks(apr_pool_t *p)
404 {
405     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
406     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
407     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
408 }
409
410 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
411 {
412     STANDARD20_MODULE_STUFF,
413     mag_create_dir_config,
414     NULL,
415     NULL,
416     NULL,
417     mag_commands,
418     mag_register_hooks
419 };