Announce module name and version in the server signature
[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     uint32_t flags;
170     uint32_t vtime;
171     uint32_t maj, min;
172     char *reply;
173     size_t replen;
174     char *clientname;
175     gss_OID mech_type = GSS_C_NO_OID;
176     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
177     struct mag_conn *mc = NULL;
178
179     type = ap_auth_type(req);
180     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
181         return DECLINED;
182     }
183
184     /* ignore auth for subrequests */
185     if (!ap_is_initial_req(req)) {
186         return OK;
187     }
188
189     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
190
191     if (cfg->ssl_only) {
192         if (!mag_conn_is_https(req->connection)) {
193             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
194                           "Not a TLS connection, refusing to authenticate!");
195             goto done;
196         }
197     }
198
199     if (cfg->gss_conn_ctx) {
200         mc = (struct mag_conn *)ap_get_module_config(
201                                                 req->connection->conn_config,
202                                                 &auth_gssapi_module);
203         if (!mc) {
204             ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
205                           "Failed to retrieve connection context!");
206             goto done;
207         }
208     }
209
210     /* if available, session always supersedes connection bound data */
211     mag_check_session(req, cfg, &mc);
212
213     if (mc) {
214         /* register the context in the memory pool, so it can be freed
215          * when the connection/request is terminated */
216         apr_pool_userdata_set(mc, "mag_conn_ptr",
217                               mag_conn_destroy, mc->parent);
218
219         if (mc->established) {
220             ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req,
221                           "Already established context found!");
222             apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
223             req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
224             req->user = apr_pstrdup(req->pool, mc->user_name);
225             ret = OK;
226             goto done;
227         }
228         pctx = &mc->ctx;
229     } else {
230         pctx = &ctx;
231     }
232
233     auth_header = apr_table_get(req->headers_in, "Authorization");
234     if (!auth_header) goto done;
235
236     auth_header_type = ap_getword_white(req->pool, &auth_header);
237     if (!auth_header_type) goto done;
238
239     if (strcasecmp(auth_header_type, "Negotiate") != 0) goto done;
240
241     auth_header_value = ap_getword_white(req->pool, &auth_header);
242     if (!auth_header_value) goto done;
243     input.length = apr_base64_decode_len(auth_header_value) + 1;
244     input.value = apr_pcalloc(req->pool, input.length);
245     if (!input.value) goto done;
246     input.length = apr_base64_decode(input.value, auth_header_value);
247
248 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
249     if (cfg->use_s4u2proxy) {
250         maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, 0,
251                                     GSS_C_NO_OID_SET, GSS_C_BOTH,
252                                     cfg->cred_store, &acquired_cred,
253                                     NULL, NULL);
254         if (GSS_ERROR(maj)) {
255             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
256                           mag_error(req, "gss_acquire_cred_from() failed",
257                                     maj, min));
258             goto done;
259         }
260     }
261 #endif
262
263     maj = gss_accept_sec_context(&min, pctx, acquired_cred,
264                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
265                                  &client, &mech_type, &output, &flags, &vtime,
266                                  &delegated_cred);
267     if (GSS_ERROR(maj)) {
268         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
269                       mag_error(req, "gss_accept_sec_context() failed",
270                                 maj, min));
271         goto done;
272     }
273
274     if (maj == GSS_S_CONTINUE_NEEDED) {
275         if (!mc) {
276             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
277                           "Mechanism needs continuation but neither "
278                           "GssapiConnectionBound nor "
279                           "GssapiUseSessions are available");
280             gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
281             gss_release_buffer(&min, &output);
282             output.length = 0;
283         }
284         /* auth not complete send token and wait next packet */
285         goto done;
286     }
287
288     req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate");
289
290     /* Always set the GSS name in an env var */
291     maj = gss_display_name(&min, client, &name, NULL);
292     if (GSS_ERROR(maj)) {
293         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
294                       mag_error(req, "gss_accept_sec_context() failed",
295                                 maj, min));
296         goto done;
297     }
298     clientname = apr_pstrndup(req->pool, name.value, name.length);
299     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
300
301 #ifdef HAVE_GSS_STORE_CRED_INTO
302     if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
303         char *ccachefile = NULL;
304
305         mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
306                               delegated_cred, &ccachefile);
307
308         if (ccachefile) {
309             apr_table_set(req->subprocess_env, "KRB5CCNAME", ccachefile);
310         }
311     }
312 #endif
313
314     if (cfg->map_to_local) {
315         maj = gss_localname(&min, client, mech_type, &lname);
316         if (maj != GSS_S_COMPLETE) {
317             ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, "%s",
318                           mag_error(req, "gss_localname() failed", maj, min));
319             goto done;
320         }
321         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
322     } else {
323         req->user = clientname;
324     }
325
326     if (mc) {
327         mc->user_name = apr_pstrdup(mc->parent, req->user);
328         mc->gss_name = apr_pstrdup(mc->parent, clientname);
329         mc->established = true;
330         if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
331             vtime = MIN_SESS_EXP_TIME;
332         }
333         mc->expiration = time(NULL) + vtime;
334         mag_attempt_session(req, cfg, mc);
335     }
336
337     ret = OK;
338
339 done:
340     if (ret == HTTP_UNAUTHORIZED) {
341         if (output.length != 0) {
342             replen = apr_base64_encode_len(output.length) + 1;
343             reply = apr_pcalloc(req->pool, 10 + replen);
344             if (reply) {
345                 memcpy(reply, "Negotiate ", 10);
346                 apr_base64_encode(&reply[10], output.value, output.length);
347                 apr_table_add(req->err_headers_out,
348                               "WWW-Authenticate", reply);
349             }
350         } else {
351             apr_table_add(req->err_headers_out,
352                           "WWW-Authenticate", "Negotiate");
353         }
354     }
355     gss_release_cred(&min, &delegated_cred);
356     gss_release_buffer(&min, &output);
357     gss_release_name(&min, &client);
358     gss_release_buffer(&min, &name);
359     gss_release_buffer(&min, &lname);
360     return ret;
361 }
362
363
364 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
365 {
366     struct mag_config *cfg;
367
368     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
369     if (!cfg) return NULL;
370     cfg->pool = p;
371
372     return cfg;
373 }
374
375 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
376 {
377     struct mag_config *cfg = (struct mag_config *)mconfig;
378     cfg->ssl_only = on ? true : false;
379     return NULL;
380 }
381
382 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
383 {
384     struct mag_config *cfg = (struct mag_config *)mconfig;
385     cfg->map_to_local = on ? true : false;
386     return NULL;
387 }
388
389 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
390 {
391     struct mag_config *cfg = (struct mag_config *)mconfig;
392     cfg->gss_conn_ctx = on ? true : false;
393     return NULL;
394 }
395
396 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
397 {
398     struct mag_config *cfg = (struct mag_config *)mconfig;
399     cfg->use_sessions = on ? true : false;
400     return NULL;
401 }
402
403 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
404 {
405     struct mag_config *cfg = (struct mag_config *)mconfig;
406     cfg->use_s4u2proxy = on ? true : false;
407
408     if (cfg->deleg_ccache_dir == NULL) {
409         cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
410         if (!cfg->deleg_ccache_dir) {
411             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0,
412                          parms->server, "%s", "OOM setting deleg_ccache_dir.");
413         }
414     }
415     return NULL;
416 }
417
418 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
419 {
420     struct mag_config *cfg = (struct mag_config *)mconfig;
421     struct databuf keys;
422     unsigned char *val;
423     apr_status_t rc;
424     const char *k;
425     int l;
426
427     if (strncmp(w, "key:", 4) != 0) {
428         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
429                      "Invalid key format, expected prefix 'key:'");
430         return NULL;
431     }
432     k = w + 4;
433
434     l = apr_base64_decode_len(k);
435     val = apr_palloc(parms->temp_pool, l);
436     if (!val) {
437         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
438                      "Failed to get memory to decode key");
439         return NULL;
440     }
441
442     keys.length = (int)apr_base64_decode_binary(val, k);
443     keys.value = (unsigned char *)val;
444
445     if (keys.length != 32) {
446         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
447                      "Invalid key lenght, expected 32 got %d", keys.length);
448         return NULL;
449     }
450
451     rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
452     if (rc != OK) {
453         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
454                      "Failed to import sealing key!");
455     }
456     return NULL;
457 }
458
459 #define MAX_CRED_OPTIONS 10
460
461 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
462                                   const char *w)
463 {
464     struct mag_config *cfg = (struct mag_config *)mconfig;
465     gss_key_value_element_desc *elements;
466     uint32_t count;
467     size_t size;
468     const char *p;
469     char *value;
470     char *key;
471
472     p = strchr(w, ':');
473     if (!p) {
474         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
475                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
476         return NULL;
477     }
478
479     key = apr_pstrndup(parms->pool, w, (p-w));
480     value = apr_pstrdup(parms->pool, p + 1);
481     if (!key || !value) {
482         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
483                      "%s", "OOM handling GssapiCredStore option");
484         return NULL;
485     }
486
487     if (!cfg->cred_store) {
488         cfg->cred_store = apr_pcalloc(parms->pool,
489                                       sizeof(gss_key_value_set_desc));
490         if (!cfg->cred_store) {
491             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
492                          "%s", "OOM handling GssapiCredStore option");
493             return NULL;
494         }
495         size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
496         cfg->cred_store->elements = apr_palloc(parms->pool, size);
497         if (!cfg->cred_store->elements) {
498             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
499                          "%s", "OOM handling GssapiCredStore option");
500         }
501     }
502
503     elements = cfg->cred_store->elements;
504     count = cfg->cred_store->count;
505
506     if (count >= MAX_CRED_OPTIONS) {
507         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
508                      "Too many GssapiCredStore options (MAX: %d)",
509                      MAX_CRED_OPTIONS);
510         return NULL;
511     }
512     cfg->cred_store->count++;
513
514     elements[count].key = key;
515     elements[count].value = value;
516
517     return NULL;
518 }
519
520 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
521                                         const char *value)
522 {
523     struct mag_config *cfg = (struct mag_config *)mconfig;
524
525     cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
526     if (!cfg->deleg_ccache_dir) {
527         ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, parms->server,
528                      "%s", "OOM handling GssapiDelegCcacheDir option");
529     }
530
531     return NULL;
532 }
533
534 static const command_rec mag_commands[] = {
535     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
536                   "Work only if connection is SSL Secured"),
537     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
538                   "Translate principals to local names"),
539     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
540                   "Authentication is bound to the TCP connection"),
541     AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
542                   "Authentication uses mod_sessions to hold status"),
543     AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
544                      "Key Used to seal session data."),
545 #ifdef HAVE_GSS_ACQUIRE_CRED_FROM
546     AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
547                   "Initializes credentials for s4u2proxy usage"),
548 #endif
549 #ifdef HAVE_GSS_STORE_CRED_INTO
550     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
551                     "Credential Store"),
552     AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
553                      OR_AUTHCFG, "Directory to store delegated credentials"),
554 #endif
555     { NULL }
556 };
557
558 static void
559 mag_register_hooks(apr_pool_t *p)
560 {
561     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
562     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
563     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
564 }
565
566 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
567 {
568     STANDARD20_MODULE_STUFF,
569     mag_create_dir_config,
570     NULL,
571     NULL,
572     NULL,
573     mag_commands,
574     mag_register_hooks
575 };