Attempt to use sessions only when they are enabled
[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 "mod_auth_gssapi.h"
26
27 #define MOD_AUTH_GSSAPI_VERSION PACKAGE_NAME "/" PACKAGE_VERSION
28
29 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
30
31 APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
32
33 static char *mag_status(request_rec *req, int type, uint32_t err)
34 {
35     uint32_t maj_ret, min_ret;
36     gss_buffer_desc text;
37     uint32_t msg_ctx;
38     char *msg_ret;
39     int len;
40
41     msg_ret = NULL;
42     msg_ctx = 0;
43     do {
44         maj_ret = gss_display_status(&min_ret, err, type,
45                                      GSS_C_NO_OID, &msg_ctx, &text);
46         if (maj_ret != GSS_S_COMPLETE) {
47             return msg_ret;
48         }
49
50         len = text.length;
51         if (msg_ret) {
52             msg_ret = apr_psprintf(req->pool, "%s, %*s",
53                                    msg_ret, len, (char *)text.value);
54         } else {
55             msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
56         }
57         gss_release_buffer(&min_ret, &text);
58     } while (msg_ctx != 0);
59
60     return msg_ret;
61 }
62
63 static char *mag_error(request_rec *req, const char *msg,
64                        uint32_t maj, uint32_t min)
65 {
66     char *msg_maj;
67     char *msg_min;
68
69     msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
70     msg_min = mag_status(req, GSS_C_MECH_CODE, min);
71     return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
72 }
73
74 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
75
76 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
77                            apr_pool_t *temp, server_rec *s)
78 {
79     /* FIXME: create mutex to deal with connections and contexts ? */
80     mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
81     mag_post_config_session();
82     ap_add_version_component(cfgpool, MOD_AUTH_GSSAPI_VERSION);
83
84     return OK;
85 }
86
87 static int mag_pre_connection(conn_rec *c, void *csd)
88 {
89     struct mag_conn *mc;
90
91     mc = apr_pcalloc(c->pool, sizeof(struct mag_conn));
92     if (!mc) return DECLINED;
93
94     mc->parent = c->pool;
95     ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
96     return OK;
97 }
98
99 static apr_status_t mag_conn_destroy(void *ptr)
100 {
101     struct mag_conn *mc = (struct mag_conn *)ptr;
102     uint32_t min;
103
104     if (mc->ctx) {
105         (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
106         mc->established = false;
107     }
108     return APR_SUCCESS;
109 }
110
111 static bool mag_conn_is_https(conn_rec *c)
112 {
113     if (mag_is_https) {
114         if (mag_is_https(c)) return true;
115     }
116
117     return false;
118 }
119
120 static void mag_store_deleg_creds(request_rec *req,
121                                   char *dir, char *clientname,
122                                   gss_cred_id_t delegated_cred,
123                                   char **ccachefile)
124 {
125     gss_key_value_element_desc element;
126     gss_key_value_set_desc store;
127     char *value;
128     uint32_t maj, min;
129
130     value = apr_psprintf(req->pool, "FILE:%s/%s", dir, clientname);
131     if (!value) {
132         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, NULL,
133                      "OOM storing delegated credentials");
134         return;
135     }
136
137     element.key = "ccache";
138     element.value = value;
139     store.elements = &element;
140     store.count = 1;
141
142     maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
143                               GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
144     if (GSS_ERROR(maj)) {
145         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
146                       mag_error(req, "failed to store delegated creds",
147                                 maj, min));
148     }
149
150     *ccachefile = value;
151 }
152
153 static int mag_auth(request_rec *req)
154 {
155     const char *type;
156     struct mag_config *cfg;
157     const char *auth_header;
158     char *auth_header_type;
159     char *auth_header_value;
160     int ret = HTTP_UNAUTHORIZED;
161     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
162     gss_ctx_id_t *pctx;
163     gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
164     gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
165     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
166     gss_name_t client = GSS_C_NO_NAME;
167     gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
168     gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
169     gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
170     uint32_t flags;
171     uint32_t vtime;
172     uint32_t maj, min;
173     char *reply;
174     size_t replen;
175     char *clientname;
176     gss_OID mech_type = GSS_C_NO_OID;
177     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
178     struct mag_conn *mc = NULL;
179
180     type = ap_auth_type(req);
181     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
182         return DECLINED;
183     }
184
185     /* ignore auth for subrequests */
186     if (!ap_is_initial_req(req)) {
187         return OK;
188     }
189
190     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
191
192     if (cfg->ssl_only) {
193         if (!mag_conn_is_https(req->connection)) {
194             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
195                           "Not a TLS connection, refusing to authenticate!");
196             goto done;
197         }
198     }
199
200     if (cfg->gss_conn_ctx) {
201         mc = (struct mag_conn *)ap_get_module_config(
202                                                 req->connection->conn_config,
203                                                 &auth_gssapi_module);
204         if (!mc) {
205             ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
206                           "Failed to retrieve connection context!");
207             goto done;
208         }
209     }
210
211     /* if available, session always supersedes connection bound data */
212     if (cfg->use_sessions) {
213         mag_check_session(req, cfg, &mc);
214     }
215
216     if (mc) {
217         /* register the context in the memory pool, so it can be freed
218          * when the connection/request is terminated */
219         apr_pool_userdata_set(mc, "mag_conn_ptr",
220                               mag_conn_destroy, mc->parent);
221
222         if (mc->established) {
223             ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
224                           "Already established context found!");
225             apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
226             req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
227             req->user = apr_pstrdup(req->pool, mc->user_name);
228             ret = OK;
229             goto done;
230         }
231         pctx = &mc->ctx;
232     } else {
233         pctx = &ctx;
234     }
235
236     auth_header = apr_table_get(req->headers_in, "Authorization");
237     if (!auth_header) goto done;
238
239     auth_header_type = ap_getword_white(req->pool, &auth_header);
240     if (!auth_header_type) goto done;
241
242     if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
243
244     auth_header_value = ap_getword_white(req->pool, &auth_header);
245     if (!auth_header_value) goto done;
246     input.length = apr_base64_decode_len(auth_header_value) + 1;
247     input.value = apr_pcalloc(req->pool, input.length);
248     if (!input.value) goto done;
249     input.length = apr_base64_decode(input.value, auth_header_value);
250
251 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
252     if (cfg->use_s4u2proxy) {
253         cred_usage = GSS_C_BOTH;
254     }
255     if (cfg->cred_store) {
256         maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, 0,
257                                     GSS_C_NO_OID_SET, cred_usage,
258                                     cfg->cred_store, &acquired_cred,
259                                     NULL, NULL);
260         if (GSS_ERROR(maj)) {
261             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
262                           mag_error(req, "gss_acquire_cred_from() failed",
263                                     maj, min));
264             goto done;
265         }
266     }
267 #endif
268
269     maj = gss_accept_sec_context(&min, pctx, acquired_cred,
270                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
271                                  &client, &mech_type, &output, &flags, &vtime,
272                                  &delegated_cred);
273     if (GSS_ERROR(maj)) {
274         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
275                       mag_error(req, "gss_accept_sec_context() failed",
276                                 maj, min));
277         goto done;
278     }
279
280     if (maj == GSS_S_CONTINUE_NEEDED) {
281         if (!mc) {
282             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
283                           "Mechanism needs continuation but neither "
284                           "GssapiConnectionBound nor "
285                           "GssapiUseSessions are available");
286             gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
287             gss_release_buffer(&min, &output);
288             output.length = 0;
289         }
290         /* auth not complete send token and wait next packet */
291         goto done;
292     }
293
294     req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
295
296     /* Always set the GSS name in an env var */
297     maj = gss_display_name(&min, client, &name, NULL);
298     if (GSS_ERROR(maj)) {
299         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
300                       mag_error(req, "gss_accept_sec_context() failed",
301                                 maj, min));
302         goto done;
303     }
304     clientname = apr_pstrndup(req->pool, name.value, name.length);
305     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
306
307 #ifdef HAVE_GSS_STORE_CRED_INTO
308     if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
309         char *ccachefile = NULL;
310
311         mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
312                               delegated_cred, &ccachefile);
313
314         if (ccachefile) {
315             apr_table_set(req->subprocess_env, "KRB5CCNAME", ccachefile);
316         }
317     }
318 #endif
319
320     if (cfg->map_to_local) {
321         maj = gss_localname(&min, client, mech_type, &lname);
322         if (maj != GSS_S_COMPLETE) {
323             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
324                           mag_error(req, "gss_localname() failed", maj, min));
325             goto done;
326         }
327         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
328     } else {
329         req->user = clientname;
330     }
331
332     if (mc) {
333         mc->user_name = apr_pstrdup(mc->parent, req->user);
334         mc->gss_name = apr_pstrdup(mc->parent, clientname);
335         mc->established = true;
336         if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
337             vtime = MIN_SESS_EXP_TIME;
338         }
339         mc->expiration = time(NULL) + vtime;
340         if (cfg->use_sessions) {
341             mag_attempt_session(req, cfg, mc);
342         }
343     }
344
345     ret = OK;
346
347 done:
348     if (ret == HTTP_UNAUTHORIZED) {
349         if (output.length != 0) {
350             replen = apr_base64_encode_len(output.length) + 1;
351             reply = apr_pcalloc(req->pool, 10 + replen);
352             if (reply) {
353                 memcpy(reply, "Negotiate ", 10);
354                 apr_base64_encode(&reply[10], output.value, output.length);
355                 apr_table_add(req->err_headers_out,
356                               "WWW-Authenticate", reply);
357             }
358         } else {
359             apr_table_add(req->err_headers_out,
360                           "WWW-Authenticate", "Negotiate");
361         }
362     }
363     gss_release_cred(&min, &delegated_cred);
364     gss_release_buffer(&min, &output);
365     gss_release_name(&min, &client);
366     gss_release_buffer(&min, &name);
367     gss_release_buffer(&min, &lname);
368     return ret;
369 }
370
371
372 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
373 {
374     struct mag_config *cfg;
375
376     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
377     if (!cfg) return NULL;
378     cfg->pool = p;
379
380     return cfg;
381 }
382
383 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
384 {
385     struct mag_config *cfg = (struct mag_config *)mconfig;
386     cfg->ssl_only = on ? true : false;
387     return NULL;
388 }
389
390 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
391 {
392     struct mag_config *cfg = (struct mag_config *)mconfig;
393     cfg->map_to_local = on ? true : false;
394     return NULL;
395 }
396
397 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
398 {
399     struct mag_config *cfg = (struct mag_config *)mconfig;
400     cfg->gss_conn_ctx = on ? true : false;
401     return NULL;
402 }
403
404 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
405 {
406     struct mag_config *cfg = (struct mag_config *)mconfig;
407     cfg->use_sessions = on ? true : false;
408     return NULL;
409 }
410
411 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
412 {
413     struct mag_config *cfg = (struct mag_config *)mconfig;
414     cfg->use_s4u2proxy = on ? true : false;
415
416     if (cfg->deleg_ccache_dir == NULL) {
417         cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
418         if (!cfg->deleg_ccache_dir) {
419             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0,
420                          parms->server, "%s", "OOM setting deleg_ccache_dir.");
421         }
422     }
423     return NULL;
424 }
425
426 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
427 {
428     struct mag_config *cfg = (struct mag_config *)mconfig;
429     struct databuf keys;
430     unsigned char *val;
431     apr_status_t rc;
432     const char *k;
433     int l;
434
435     if (strncmp(w, "key:", 4) != 0) {
436         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
437                      "Invalid key format, expected prefix 'key:'");
438         return NULL;
439     }
440     k = w + 4;
441
442     l = apr_base64_decode_len(k);
443     val = apr_palloc(parms->temp_pool, l);
444     if (!val) {
445         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
446                      "Failed to get memory to decode key");
447         return NULL;
448     }
449
450     keys.length = (int)apr_base64_decode_binary(val, k);
451     keys.value = (unsigned char *)val;
452
453     if (keys.length != 32) {
454         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
455                      "Invalid key lenght, expected 32 got %d", keys.length);
456         return NULL;
457     }
458
459     rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
460     if (rc != OK) {
461         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
462                      "Failed to import sealing key!");
463     }
464     return NULL;
465 }
466
467 #define MAX_CRED_OPTIONS 10
468
469 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
470                                   const char *w)
471 {
472     struct mag_config *cfg = (struct mag_config *)mconfig;
473     gss_key_value_element_desc *elements;
474     uint32_t count;
475     size_t size;
476     const char *p;
477     char *value;
478     char *key;
479
480     p = strchr(w, ':');
481     if (!p) {
482         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
483                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
484         return NULL;
485     }
486
487     key = apr_pstrndup(parms->pool, w, (p-w));
488     value = apr_pstrdup(parms->pool, p + 1);
489     if (!key || !value) {
490         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
491                      "%s", "OOM handling GssapiCredStore option");
492         return NULL;
493     }
494
495     if (!cfg->cred_store) {
496         cfg->cred_store = apr_pcalloc(parms->pool,
497                                       sizeof(gss_key_value_set_desc));
498         if (!cfg->cred_store) {
499             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
500                          "%s", "OOM handling GssapiCredStore option");
501             return NULL;
502         }
503         size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
504         cfg->cred_store->elements = apr_palloc(parms->pool, size);
505         if (!cfg->cred_store->elements) {
506             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
507                          "%s", "OOM handling GssapiCredStore option");
508         }
509     }
510
511     elements = cfg->cred_store->elements;
512     count = cfg->cred_store->count;
513
514     if (count >= MAX_CRED_OPTIONS) {
515         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
516                      "Too many GssapiCredStore options (MAX: %d)",
517                      MAX_CRED_OPTIONS);
518         return NULL;
519     }
520     cfg->cred_store->count++;
521
522     elements[count].key = key;
523     elements[count].value = value;
524
525     return NULL;
526 }
527
528 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
529                                         const char *value)
530 {
531     struct mag_config *cfg = (struct mag_config *)mconfig;
532
533     cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
534     if (!cfg->deleg_ccache_dir) {
535         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
536                      "%s", "OOM handling GssapiDelegCcacheDir option");
537     }
538
539     return NULL;
540 }
541
542 static const command_rec mag_commands[] = {
543     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
544                   "Work only if connection is SSL Secured"),
545     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
546                   "Translate principals to local names"),
547     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
548                   "Authentication is bound to the TCP connection"),
549     AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
550                   "Authentication uses mod_sessions to hold status"),
551     AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
552                      "Key Used to seal session data."),
553 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
554     AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
555                   "Initializes credentials for s4u2proxy usage"),
556 #endif
557 #ifdef HAVE_GSS_STORE_CRED_INTO
558     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
559                     "Credential Store"),
560     AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
561                      OR_AUTHCFG, "Directory to store delegated credentials"),
562 #endif
563     { NULL }
564 };
565
566 static void
567 mag_register_hooks(apr_pool_t *p)
568 {
569     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
570     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
571     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
572 }
573
574 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
575 {
576     STANDARD20_MODULE_STUFF,
577     mag_create_dir_config,
578     NULL,
579     NULL,
580     NULL,
581     mag_commands,
582     mag_register_hooks
583 };