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