95838d383d1ca9bbee9da9b0f71333dc2b55b43f
[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 const gss_OID_desc gss_mech_ntlmssp = {
28     GSS_NTLMSSP_OID_LENGTH, GSS_NTLMSSP_OID_STRING
29 };
30
31 const gss_OID_set_desc gss_mech_set_ntlmssp = {
32     1, discard_const(&gss_mech_ntlmssp)
33 };
34
35 #define MOD_AUTH_GSSAPI_VERSION PACKAGE_NAME "/" PACKAGE_VERSION
36
37 module AP_MODULE_DECLARE_DATA auth_gssapi_module;
38
39 APLOG_USE_MODULE(auth_gssapi);
40
41 static char *mag_status(request_rec *req, int type, uint32_t err)
42 {
43     uint32_t maj_ret, min_ret;
44     gss_buffer_desc text;
45     uint32_t msg_ctx;
46     char *msg_ret;
47     int len;
48
49     msg_ret = NULL;
50     msg_ctx = 0;
51     do {
52         maj_ret = gss_display_status(&min_ret, err, type,
53                                      GSS_C_NO_OID, &msg_ctx, &text);
54         if (maj_ret != GSS_S_COMPLETE) {
55             return msg_ret;
56         }
57
58         len = text.length;
59         if (msg_ret) {
60             msg_ret = apr_psprintf(req->pool, "%s, %*s",
61                                    msg_ret, len, (char *)text.value);
62         } else {
63             msg_ret = apr_psprintf(req->pool, "%*s", len, (char *)text.value);
64         }
65         gss_release_buffer(&min_ret, &text);
66     } while (msg_ctx != 0);
67
68     return msg_ret;
69 }
70
71 static char *mag_error(request_rec *req, const char *msg,
72                        uint32_t maj, uint32_t min)
73 {
74     char *msg_maj;
75     char *msg_min;
76
77     msg_maj = mag_status(req, GSS_C_GSS_CODE, maj);
78     msg_min = mag_status(req, GSS_C_MECH_CODE, min);
79     return apr_psprintf(req->pool, "%s: [%s (%s)]", msg, msg_maj, msg_min);
80 }
81
82 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL;
83
84 static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log,
85                            apr_pool_t *temp, server_rec *s)
86 {
87     /* FIXME: create mutex to deal with connections and contexts ? */
88     mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
89     mag_post_config_session();
90     ap_add_version_component(cfgpool, MOD_AUTH_GSSAPI_VERSION);
91
92     return OK;
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
101     mc->parent = c->pool;
102     ap_set_module_config(c->conn_config, &auth_gssapi_module, (void*)mc);
103     return OK;
104 }
105
106 static apr_status_t mag_conn_destroy(void *ptr)
107 {
108     struct mag_conn *mc = (struct mag_conn *)ptr;
109     uint32_t min;
110
111     if (mc->ctx) {
112         (void)gss_delete_sec_context(&min, &mc->ctx, GSS_C_NO_BUFFER);
113         mc->established = false;
114     }
115     return APR_SUCCESS;
116 }
117
118 static bool mag_conn_is_https(conn_rec *c)
119 {
120     if (mag_is_https) {
121         if (mag_is_https(c)) return true;
122     }
123
124     return false;
125 }
126
127 static bool mag_acquire_creds(request_rec *req,
128                               struct mag_config *cfg,
129                               gss_OID_set desired_mechs,
130                               gss_cred_usage_t cred_usage,
131                               gss_cred_id_t *creds,
132                               gss_OID_set *actual_mechs)
133 {
134     uint32_t maj, min;
135 #ifdef HAVE_CRED_STORE
136     gss_const_key_value_set_t store = cfg->cred_store;
137
138     maj = gss_acquire_cred_from(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
139                                 desired_mechs, cred_usage, store, creds,
140                                 actual_mechs, NULL);
141 #else
142     maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
143                            desired_mechs, cred_usage, creds,
144                            actual_mechs, NULL);
145 #endif
146
147     if (GSS_ERROR(maj)) {
148         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
149                       mag_error(req, "gss_acquire_cred[_from]() "
150                                 "failed to get server creds",
151                                 maj, min));
152         return false;
153     }
154
155     return true;
156 }
157
158 #ifdef HAVE_CRED_STORE
159 static char *escape(apr_pool_t *pool, const char *name,
160                     char find, const char *replace)
161 {
162     char *escaped = NULL;
163     char *namecopy;
164     char *n;
165     char *p;
166
167     namecopy = apr_pstrdup(pool, name);
168
169     p = strchr(namecopy, find);
170     if (!p) return namecopy;
171
172     /* first segment */
173     n = namecopy;
174     while (p) {
175         /* terminate previous segment */
176         *p = '\0';
177         if (escaped) {
178             escaped = apr_pstrcat(pool, escaped, n, replace, NULL);
179         } else {
180             escaped = apr_pstrcat(pool, n, replace, NULL);
181         }
182         /* move to next segment */
183         n = p + 1;
184         p = strchr(n, find);
185     }
186     /* append last segment if any */
187     if (*n) {
188         escaped = apr_pstrcat(pool, escaped, n, NULL);
189     }
190
191     return escaped;
192 }
193
194 static char *mag_gss_name_to_ccache_name(request_rec *req,
195                                          char *dir, const char *gss_name)
196 {
197     char *escaped;
198
199     /* We need to escape away '/', we can't have path separators in
200      * a ccache file name */
201     /* first double escape the esacping char (~) if any */
202     escaped = escape(req->pool, gss_name, '~', "~~");
203     /* then escape away the separator (/) if any */
204     escaped = escape(req->pool, escaped, '/', "~");
205
206     return apr_psprintf(req->pool, "%s/%s", dir, escaped);
207 }
208
209 static void mag_set_KRB5CCANME(request_rec *req, char *ccname)
210 {
211     apr_status_t status;
212     apr_finfo_t finfo;
213     char *value;
214
215     status = apr_stat(&finfo, ccname, APR_FINFO_MIN, req->pool);
216     if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
217         /* set the file cache anyway, but warn */
218         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req,
219                       "KRB5CCNAME file (%s) lookup failed!", ccname);
220     }
221
222     value = apr_psprintf(req->pool, "FILE:%s", ccname);
223     apr_table_set(req->subprocess_env, "KRB5CCNAME", value);
224 }
225
226 static void mag_store_deleg_creds(request_rec *req,
227                                   char *dir, char *clientname,
228                                   gss_cred_id_t delegated_cred,
229                                   char **ccachefile)
230 {
231     gss_key_value_element_desc element;
232     gss_key_value_set_desc store;
233     char *ccname;
234     uint32_t maj, min;
235     element.key = "ccache";
236     store.elements = &element;
237     store.count = 1;
238
239     ccname = mag_gss_name_to_ccache_name(req, dir, clientname);
240     element.value = apr_psprintf(req->pool, "FILE:%s", ccname);
241
242     maj = gss_store_cred_into(&min, delegated_cred, GSS_C_INITIATE,
243                               GSS_C_NULL_OID, 1, 1, &store, NULL, NULL);
244     if (GSS_ERROR(maj)) {
245         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
246                       mag_error(req, "failed to store delegated creds",
247                                 maj, min));
248     }
249
250     *ccachefile = ccname;
251 }
252 #endif
253
254 static bool parse_auth_header(apr_pool_t *pool, const char **auth_header,
255                               gss_buffer_t value)
256 {
257     char *auth_header_value;
258
259     auth_header_value = ap_getword_white(pool, auth_header);
260     if (!auth_header_value) return false;
261     value->length = apr_base64_decode_len(auth_header_value) + 1;
262     value->value = apr_pcalloc(pool, value->length);
263     if (!value->value) return false;
264     value->length = apr_base64_decode(value->value, auth_header_value);
265
266     return true;
267 }
268
269 static bool is_mech_allowed(struct mag_config *cfg, gss_const_OID mech)
270 {
271     if (cfg->allowed_mechs == GSS_C_NO_OID_SET) return true;
272
273     for (int i = 0; i < cfg->allowed_mechs->count; i++) {
274         if (gss_oid_equal(&cfg->allowed_mechs->elements[i], mech)) {
275             return true;
276         }
277     }
278     return false;
279 }
280
281 #define AUTH_TYPE_NEGOTIATE 0
282 #define AUTH_TYPE_BASIC 1
283 #define AUTH_TYPE_RAW_NTLM 2
284 const char *auth_types[] = {
285     "Negotiate",
286     "Basic",
287     "NTLM",
288     NULL
289 };
290
291 static int mag_auth(request_rec *req)
292 {
293     const char *type;
294     int auth_type = -1;
295     struct mag_config *cfg;
296     const char *auth_header;
297     char *auth_header_type;
298     int ret = HTTP_UNAUTHORIZED;
299     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
300     gss_ctx_id_t *pctx;
301     gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
302     gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
303     gss_buffer_desc name = GSS_C_EMPTY_BUFFER;
304     gss_buffer_desc ba_user;
305     gss_buffer_desc ba_pwd;
306     gss_name_t client = GSS_C_NO_NAME;
307     gss_cred_id_t user_cred = GSS_C_NO_CREDENTIAL;
308     gss_cred_id_t acquired_cred = GSS_C_NO_CREDENTIAL;
309     gss_cred_id_t server_cred = GSS_C_NO_CREDENTIAL;
310     gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
311     gss_cred_usage_t cred_usage = GSS_C_ACCEPT;
312     uint32_t flags;
313     uint32_t vtime;
314     uint32_t maj, min;
315     char *reply;
316     size_t replen;
317     char *clientname;
318     gss_OID mech_type = GSS_C_NO_OID;
319     gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
320     gss_buffer_desc lname = GSS_C_EMPTY_BUFFER;
321     struct mag_conn *mc = NULL;
322     gss_ctx_id_t user_ctx = GSS_C_NO_CONTEXT;
323     gss_name_t server = GSS_C_NO_NAME;
324 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
325     const char *user_ccache = NULL;
326     const char *orig_ccache = NULL;
327 #endif
328     uint32_t init_flags = 0;
329     time_t expiration;
330     int i;
331
332     type = ap_auth_type(req);
333     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
334         return DECLINED;
335     }
336
337     cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
338
339     if (!cfg->allowed_mechs) {
340         /* Try to fetch the default set if not explicitly configured */
341         (void)mag_acquire_creds(req, cfg, GSS_C_NO_OID_SET, GSS_C_ACCEPT,
342                                 &server_cred, &cfg->allowed_mechs);
343         (void)gss_release_cred(&min, &server_cred);
344     }
345
346     /* implicit auth for subrequests if main auth already happened */
347     if (!ap_is_initial_req(req) && req->main != NULL) {
348         type = ap_auth_type(req->main);
349         if ((type != NULL) && (strcasecmp(type, "GSSAPI") == 0)) {
350             /* warn if the subrequest location and the main request
351              * location have different configs */
352             if (cfg != ap_get_module_config(req->main->per_dir_config,
353                                             &auth_gssapi_module)) {
354                 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0,
355                               req, "Subrequest authentication bypass on "
356                                    "location with different configuration!");
357             }
358             if (req->main->user) {
359                 req->user = apr_pstrdup(req->pool, req->main->user);
360                 return OK;
361             } else {
362                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
363                               "The main request is tasked to establish the "
364                               "security context, can't proceed!");
365                 return HTTP_UNAUTHORIZED;
366             }
367         } else {
368             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
369                           "Subrequest GSSAPI auth with no auth on the main "
370                           "request. This operation may fail if other "
371                           "subrequests already established a context or the "
372                           "mechanism requires multiple roundtrips.");
373         }
374     }
375
376     if (cfg->ssl_only) {
377         if (!mag_conn_is_https(req->connection)) {
378             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
379                           "Not a TLS connection, refusing to authenticate!");
380             goto done;
381         }
382     }
383
384     if (cfg->gss_conn_ctx) {
385         mc = (struct mag_conn *)ap_get_module_config(
386                                                 req->connection->conn_config,
387                                                 &auth_gssapi_module);
388         if (!mc) {
389             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
390                           "Failed to retrieve connection context!");
391             goto done;
392         }
393     }
394
395     /* if available, session always supersedes connection bound data */
396     if (cfg->use_sessions) {
397         mag_check_session(req, cfg, &mc);
398     }
399
400     if (mc) {
401         /* register the context in the memory pool, so it can be freed
402          * when the connection/request is terminated */
403         apr_pool_userdata_set(mc, "mag_conn_ptr",
404                               mag_conn_destroy, mc->parent);
405
406         if (mc->established) {
407             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
408                           "Already established context found!");
409             apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name);
410             apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
411                           apr_psprintf(req->pool,
412                                        "%ld", (long)mc->expiration));
413             req->ap_auth_type = apr_pstrdup(req->pool,
414                                             auth_types[mc->auth_type]);
415             req->user = apr_pstrdup(req->pool, mc->user_name);
416             if (cfg->deleg_ccache_dir && mc->delegated) {
417                 char *ccname;
418                 ccname = mag_gss_name_to_ccache_name(req,
419                                                      cfg->deleg_ccache_dir,
420                                                      mc->gss_name);
421                 if (ccname) {
422                     mag_set_KRB5CCANME(req, ccname);
423                 }
424             }
425             if (mc->auth_type != AUTH_TYPE_BASIC) {
426                 /* In case we have basic auth, we need to check if the session
427                  * matches the credentials that have been sent */
428                 ret = OK;
429                 goto done;
430             }
431         }
432         pctx = &mc->ctx;
433     } else {
434         pctx = &ctx;
435     }
436
437     auth_header = apr_table_get(req->headers_in, "Authorization");
438     if (!auth_header) goto done;
439
440     auth_header_type = ap_getword_white(req->pool, &auth_header);
441     if (!auth_header_type) goto done;
442
443     for (i = 0; auth_types[i] != NULL; i++) {
444         if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
445             auth_type = i;
446             break;
447         }
448     }
449
450     switch (auth_type) {
451     case AUTH_TYPE_NEGOTIATE:
452         if (!parse_auth_header(req->pool, &auth_header, &input)) {
453             goto done;
454         }
455         break;
456     case AUTH_TYPE_BASIC:
457         if (!cfg->use_basic_auth) {
458             goto done;
459         }
460
461         ba_pwd.value = ap_pbase64decode(req->pool, auth_header);
462         if (!ba_pwd.value) goto done;
463         ba_user.value = ap_getword_nulls_nc(req->pool,
464                                             (char **)&ba_pwd.value, ':');
465         if (!ba_user.value) goto done;
466         if (((char *)ba_user.value)[0] == '\0' ||
467             ((char *)ba_pwd.value)[0] == '\0') {
468             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
469                           "Invalid empty user or password for Basic Auth");
470             goto done;
471         }
472         ba_user.length = strlen(ba_user.value);
473         ba_pwd.length = strlen(ba_pwd.value);
474
475         if (mc && mag_basic_check(cfg, mc, ba_user, ba_pwd)) {
476             ret = OK;
477             goto done;
478         }
479
480         maj = gss_import_name(&min, &ba_user, GSS_C_NT_USER_NAME, &client);
481         if (GSS_ERROR(maj)) {
482             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
483                           "In Basic Auth, %s",
484                           mag_error(req, "gss_import_name() failed",
485                                     maj, min));
486             goto done;
487         }
488 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
489         /* Set a per-thread ccache in case we are using kerberos,
490          * it is not elegant but avoids interference between threads */
491         long long unsigned int rndname;
492         apr_status_t rs;
493         rs = apr_generate_random_bytes((unsigned char *)(&rndname),
494                                        sizeof(long long unsigned int));
495         if (rs != APR_SUCCESS) {
496             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
497                           "Failed to generate random ccache name");
498             goto done;
499         }
500         user_ccache = apr_psprintf(req->pool, "MEMORY:user_%qu", rndname);
501         maj = gss_krb5_ccache_name(&min, user_ccache, &orig_ccache);
502         if (GSS_ERROR(maj)) {
503             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
504                           "In Basic Auth, %s",
505                           mag_error(req, "gss_krb5_ccache_name() "
506                                     "failed", maj, min));
507             goto done;
508         }
509 #endif
510         maj = gss_acquire_cred_with_password(&min, client, &ba_pwd,
511                                              GSS_C_INDEFINITE,
512                                              cfg->allowed_mechs,
513                                              GSS_C_INITIATE,
514                                              &user_cred, NULL, NULL);
515         if (GSS_ERROR(maj)) {
516             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
517                           "In Basic Auth, %s",
518                           mag_error(req, "gss_acquire_cred_with_password() "
519                                     "failed", maj, min));
520             goto done;
521         }
522         gss_release_name(&min, &client);
523         break;
524
525     case AUTH_TYPE_RAW_NTLM:
526         if (!is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
527             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
528                           "NTLM Authentication is not allowed!");
529             goto done;
530         }
531
532         if (!parse_auth_header(req->pool, &auth_header, &input)) {
533             goto done;
534         }
535
536         desired_mechs = discard_const(&gss_mech_set_ntlmssp);
537         break;
538
539     default:
540         goto done;
541     }
542
543     req->ap_auth_type = apr_pstrdup(req->pool, auth_types[auth_type]);
544
545 #ifdef HAVE_CRED_STORE
546     if (cfg->use_s4u2proxy) {
547         cred_usage = GSS_C_BOTH;
548     }
549 #endif
550     if (!mag_acquire_creds(req, cfg, desired_mechs,
551                            cred_usage, &acquired_cred, NULL)) {
552         goto done;
553     }
554
555     if (auth_type == AUTH_TYPE_BASIC) {
556         if (cred_usage == GSS_C_BOTH) {
557             /* If GSS_C_BOTH is used then inquire_cred will return the client
558              * name instead of the SPN of the server credentials. Therefore we
559              * need to acquire a different set of credential setting
560              * GSS_C_ACCEPT explicitly */
561             if (!mag_acquire_creds(req, cfg, cfg->allowed_mechs,
562                                    GSS_C_ACCEPT, &server_cred, NULL)) {
563                 goto done;
564             }
565         } else {
566             server_cred = acquired_cred;
567         }
568         maj = gss_inquire_cred(&min, server_cred, &server,
569                                NULL, NULL, NULL);
570         if (GSS_ERROR(maj)) {
571             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
572                           "%s", mag_error(req, "gss_inquired_cred_() "
573                                           "failed", maj, min));
574             goto done;
575         }
576         if (server_cred != acquired_cred) {
577             gss_release_cred(&min, &server_cred);
578         }
579
580 #ifdef HAVE_CRED_STORE
581         if (cfg->deleg_ccache_dir) {
582             /* delegate ourselves credentials so we store them as requested */
583             init_flags |= GSS_C_DELEG_FLAG;
584         }
585 #endif
586
587         /* output and input are inverted here, this is intentional */
588         maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
589                                    GSS_C_NO_OID, init_flags, 300,
590                                    GSS_C_NO_CHANNEL_BINDINGS, &output,
591                                    NULL, &input, NULL, NULL);
592         if (GSS_ERROR(maj)) {
593             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
594                           "%s", mag_error(req, "gss_init_sec_context() "
595                                           "failed", maj, min));
596             goto done;
597         }
598     }
599
600     if (auth_type == AUTH_TYPE_NEGOTIATE &&
601         cfg->allowed_mechs != GSS_C_NO_OID_SET) {
602         maj = gss_set_neg_mechs(&min, acquired_cred, cfg->allowed_mechs);
603         if (GSS_ERROR(maj)) {
604             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
605                           mag_error(req, "gss_set_neg_mechs() failed",
606                                     maj, min));
607             goto done;
608         }
609     }
610
611     maj = gss_accept_sec_context(&min, pctx, acquired_cred,
612                                  &input, GSS_C_NO_CHANNEL_BINDINGS,
613                                  &client, &mech_type, &output, &flags, &vtime,
614                                  &delegated_cred);
615     if (GSS_ERROR(maj)) {
616         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
617                       mag_error(req, "gss_accept_sec_context() failed",
618                                 maj, min));
619         goto done;
620     }
621     if (auth_type == AUTH_TYPE_BASIC) {
622         while (maj == GSS_S_CONTINUE_NEEDED) {
623             gss_release_buffer(&min, &input);
624             /* output and input are inverted here, this is intentional */
625             maj = gss_init_sec_context(&min, user_cred, &user_ctx, server,
626                                        GSS_C_NO_OID, init_flags, 300,
627                                        GSS_C_NO_CHANNEL_BINDINGS, &output,
628                                        NULL, &input, NULL, NULL);
629             if (GSS_ERROR(maj)) {
630                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
631                               "%s", mag_error(req, "gss_init_sec_context() "
632                                               "failed", maj, min));
633                 goto done;
634             }
635             gss_release_buffer(&min, &output);
636             maj = gss_accept_sec_context(&min, pctx, acquired_cred,
637                                          &input, GSS_C_NO_CHANNEL_BINDINGS,
638                                          &client, &mech_type, &output, &flags,
639                                          &vtime, &delegated_cred);
640             if (GSS_ERROR(maj)) {
641                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
642                               "%s", mag_error(req, "gss_accept_sec_context()"
643                                               " failed", maj, min));
644                 goto done;
645             }
646         }
647     } else if (maj == GSS_S_CONTINUE_NEEDED) {
648         if (!mc) {
649             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
650                           "Mechanism needs continuation but neither "
651                           "GssapiConnectionBound nor "
652                           "GssapiUseSessions are available");
653             gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER);
654             gss_release_buffer(&min, &output);
655             output.length = 0;
656         }
657         /* auth not complete send token and wait next packet */
658         goto done;
659     }
660
661     /* Always set the GSS name in an env var */
662     maj = gss_display_name(&min, client, &name, NULL);
663     if (GSS_ERROR(maj)) {
664         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
665                       mag_error(req, "gss_display_name() failed",
666                                 maj, min));
667         goto done;
668     }
669     clientname = apr_pstrndup(req->pool, name.value, name.length);
670     apr_table_set(req->subprocess_env, "GSS_NAME", clientname);
671     expiration = time(NULL) + vtime;
672     apr_table_set(req->subprocess_env, "GSS_SESSION_EXPIRATION",
673                   apr_psprintf(req->pool, "%ld", (long)expiration));
674
675 #ifdef HAVE_CRED_STORE
676     if (cfg->deleg_ccache_dir && delegated_cred != GSS_C_NO_CREDENTIAL) {
677         char *ccachefile = NULL;
678
679         mag_store_deleg_creds(req, cfg->deleg_ccache_dir, clientname,
680                               delegated_cred, &ccachefile);
681
682         if (ccachefile) {
683             mag_set_KRB5CCANME(req, ccachefile);
684         }
685
686         if (mc) {
687             mc->delegated = true;
688         }
689     }
690 #endif
691
692     if (cfg->map_to_local) {
693         maj = gss_localname(&min, client, mech_type, &lname);
694         if (maj != GSS_S_COMPLETE) {
695             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req, "%s",
696                           mag_error(req, "gss_localname() failed", maj, min));
697             goto done;
698         }
699         req->user = apr_pstrndup(req->pool, lname.value, lname.length);
700     } else {
701         req->user = clientname;
702     }
703
704     if (mc) {
705         mc->user_name = apr_pstrdup(mc->parent, req->user);
706         mc->gss_name = apr_pstrdup(mc->parent, clientname);
707         mc->established = true;
708         if (vtime == GSS_C_INDEFINITE || vtime < MIN_SESS_EXP_TIME) {
709             vtime = MIN_SESS_EXP_TIME;
710         }
711         mc->expiration = expiration;
712         mc->auth_type = auth_type;
713         if (auth_type == AUTH_TYPE_BASIC) {
714             mag_basic_cache(cfg, mc, ba_user, ba_pwd);
715         }
716         if (cfg->use_sessions) {
717             mag_attempt_session(req, cfg, mc);
718         }
719     }
720
721     if (cfg->send_persist)
722         apr_table_set(req->headers_out, "Persistent-Auth",
723             cfg->gss_conn_ctx ? "true" : "false");
724
725     ret = OK;
726
727 done:
728     if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
729         int prefixlen = strlen(auth_types[auth_type]) + 1;
730         replen = apr_base64_encode_len(output.length) + 1;
731         reply = apr_pcalloc(req->pool, prefixlen + replen);
732         if (reply) {
733             memcpy(reply, auth_types[auth_type], prefixlen - 1);
734             reply[prefixlen - 1] = ' ';
735             apr_base64_encode(&reply[prefixlen], output.value, output.length);
736             apr_table_add(req->err_headers_out,
737                           "WWW-Authenticate", reply);
738         }
739     } else if (ret == HTTP_UNAUTHORIZED) {
740         apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
741         if (is_mech_allowed(cfg, &gss_mech_ntlmssp)) {
742             apr_table_add(req->err_headers_out, "WWW-Authenticate", "NTLM");
743         }
744         if (cfg->use_basic_auth) {
745             apr_table_add(req->err_headers_out,
746                           "WWW-Authenticate",
747                           apr_psprintf(req->pool, "Basic realm=\"%s\"",
748                                        ap_auth_name(req)));
749         }
750     }
751 #ifdef HAVE_GSS_KRB5_CCACHE_NAME
752     if (user_ccache != NULL) {
753         maj = gss_krb5_ccache_name(&min, orig_ccache, NULL);
754         if (maj != GSS_S_COMPLETE) {
755             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
756                           "Failed to restore per-thread ccache, %s",
757                           mag_error(req, "gss_krb5_ccache_name() "
758                                     "failed", maj, min));
759         }
760     }
761 #endif
762     gss_delete_sec_context(&min, &user_ctx, &output);
763     gss_release_cred(&min, &user_cred);
764     gss_release_cred(&min, &acquired_cred);
765     gss_release_cred(&min, &delegated_cred);
766     gss_release_buffer(&min, &output);
767     gss_release_name(&min, &client);
768     gss_release_name(&min, &server);
769     gss_release_buffer(&min, &name);
770     gss_release_buffer(&min, &lname);
771     return ret;
772 }
773
774
775 static void *mag_create_dir_config(apr_pool_t *p, char *dir)
776 {
777     struct mag_config *cfg;
778
779     cfg = (struct mag_config *)apr_pcalloc(p, sizeof(struct mag_config));
780     cfg->pool = p;
781
782     return cfg;
783 }
784
785 static const char *mag_ssl_only(cmd_parms *parms, void *mconfig, int on)
786 {
787     struct mag_config *cfg = (struct mag_config *)mconfig;
788     cfg->ssl_only = on ? true : false;
789     return NULL;
790 }
791
792 static const char *mag_map_to_local(cmd_parms *parms, void *mconfig, int on)
793 {
794     struct mag_config *cfg = (struct mag_config *)mconfig;
795     cfg->map_to_local = on ? true : false;
796     return NULL;
797 }
798
799 static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on)
800 {
801     struct mag_config *cfg = (struct mag_config *)mconfig;
802     cfg->gss_conn_ctx = on ? true : false;
803     return NULL;
804 }
805
806 static const char *mag_send_persist(cmd_parms *parms, void *mconfig, int on)
807 {
808     struct mag_config *cfg = (struct mag_config *)mconfig;
809     cfg->send_persist = on ? true : false;
810     return NULL;
811 }
812
813 static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on)
814 {
815     struct mag_config *cfg = (struct mag_config *)mconfig;
816     cfg->use_sessions = on ? true : false;
817     return NULL;
818 }
819
820 #ifdef HAVE_CRED_STORE
821 static const char *mag_use_s4u2p(cmd_parms *parms, void *mconfig, int on)
822 {
823     struct mag_config *cfg = (struct mag_config *)mconfig;
824     cfg->use_s4u2proxy = on ? true : false;
825
826     if (cfg->deleg_ccache_dir == NULL) {
827         cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, "/tmp");
828     }
829     return NULL;
830 }
831 #endif
832
833 static const char *mag_sess_key(cmd_parms *parms, void *mconfig, const char *w)
834 {
835     struct mag_config *cfg = (struct mag_config *)mconfig;
836     struct databuf keys;
837     unsigned char *val;
838     apr_status_t rc;
839     const char *k;
840     int l;
841
842     if (strncmp(w, "key:", 4) != 0) {
843         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
844                      "Invalid key format, expected prefix 'key:'");
845         return NULL;
846     }
847     k = w + 4;
848
849     l = apr_base64_decode_len(k);
850     val = apr_palloc(parms->temp_pool, l);
851
852     keys.length = (int)apr_base64_decode_binary(val, k);
853     keys.value = (unsigned char *)val;
854
855     if (keys.length != 32) {
856         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
857                      "Invalid key length, expected 32 got %d", keys.length);
858         return NULL;
859     }
860
861     rc = SEAL_KEY_CREATE(cfg->pool, &cfg->mag_skey, &keys);
862     if (rc != OK) {
863         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
864                      "Failed to import sealing key!");
865     }
866     return NULL;
867 }
868
869 #ifdef HAVE_CRED_STORE
870
871 #define MAX_CRED_OPTIONS 10
872
873 static const char *mag_cred_store(cmd_parms *parms, void *mconfig,
874                                   const char *w)
875 {
876     struct mag_config *cfg = (struct mag_config *)mconfig;
877     gss_key_value_element_desc *elements;
878     uint32_t count;
879     size_t size;
880     const char *p;
881     char *value;
882     char *key;
883
884     p = strchr(w, ':');
885     if (!p) {
886         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
887                      "%s [%s]", "Invalid syntax for GssapiCredStore option", w);
888         return NULL;
889     }
890
891     key = apr_pstrndup(parms->pool, w, (p-w));
892     value = apr_pstrdup(parms->pool, p + 1);
893
894     if (!cfg->cred_store) {
895         cfg->cred_store = apr_pcalloc(parms->pool,
896                                       sizeof(gss_key_value_set_desc));
897         size = sizeof(gss_key_value_element_desc) * MAX_CRED_OPTIONS;
898         cfg->cred_store->elements = apr_palloc(parms->pool, size);
899     }
900
901     elements = cfg->cred_store->elements;
902     count = cfg->cred_store->count;
903
904     if (count >= MAX_CRED_OPTIONS) {
905         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
906                      "Too many GssapiCredStore options (MAX: %d)",
907                      MAX_CRED_OPTIONS);
908         return NULL;
909     }
910     cfg->cred_store->count++;
911
912     elements[count].key = key;
913     elements[count].value = value;
914
915     return NULL;
916 }
917
918 static const char *mag_deleg_ccache_dir(cmd_parms *parms, void *mconfig,
919                                         const char *value)
920 {
921     struct mag_config *cfg = (struct mag_config *)mconfig;
922
923     cfg->deleg_ccache_dir = apr_pstrdup(parms->pool, value);
924
925     return NULL;
926 }
927 #endif
928
929 static const char *mag_use_basic_auth(cmd_parms *parms, void *mconfig, int on)
930 {
931     struct mag_config *cfg = (struct mag_config *)mconfig;
932
933     cfg->use_basic_auth = on ? true : false;
934     return NULL;
935 }
936
937 #define MAX_ALLOWED_MECHS 10
938
939 static const char *mag_allow_mech(cmd_parms *parms, void *mconfig,
940                                   const char *w)
941 {
942     struct mag_config *cfg = (struct mag_config *)mconfig;
943     gss_const_OID oid;
944     size_t size;
945
946     if (!cfg->allowed_mechs) {
947         cfg->allowed_mechs = apr_pcalloc(parms->pool,
948                                          sizeof(gss_OID_set_desc));
949         size = sizeof(gss_OID) * MAX_ALLOWED_MECHS;
950         cfg->allowed_mechs->elements = apr_palloc(parms->pool, size);
951     }
952
953     if (strcmp(w, "krb5") == 0) {
954         oid = gss_mech_krb5;
955     } else if (strcmp(w, "iakerb") == 0) {
956         oid = gss_mech_iakerb;
957     } else if (strcmp(w, "ntlmssp") == 0) {
958         oid = &gss_mech_ntlmssp;
959     } else {
960         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
961                      "Unrecognized GSSAPI Mechanism: %s", w);
962         return NULL;
963     }
964
965     if (cfg->allowed_mechs->count >= MAX_ALLOWED_MECHS) {
966         ap_log_error(APLOG_MARK, APLOG_ERR, 0, parms->server,
967                      "Too many GssapiAllowedMech options (MAX: %d)",
968                      MAX_ALLOWED_MECHS);
969         return NULL;
970     }
971     cfg->allowed_mechs->elements[cfg->allowed_mechs->count] = *oid;
972     cfg->allowed_mechs->count++;
973
974     return NULL;
975 }
976
977 static const command_rec mag_commands[] = {
978     AP_INIT_FLAG("GssapiSSLonly", mag_ssl_only, NULL, OR_AUTHCFG,
979                   "Work only if connection is SSL Secured"),
980     AP_INIT_FLAG("GssapiLocalName", mag_map_to_local, NULL, OR_AUTHCFG,
981                   "Translate principals to local names"),
982     AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG,
983                   "Authentication is bound to the TCP connection"),
984     AP_INIT_FLAG("GssapiSignalPersistentAuth", mag_send_persist, NULL, OR_AUTHCFG,
985                   "Send Persitent-Auth header according to connection bound"),
986     AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG,
987                   "Authentication uses mod_sessions to hold status"),
988     AP_INIT_RAW_ARGS("GssapiSessionKey", mag_sess_key, NULL, OR_AUTHCFG,
989                      "Key Used to seal session data."),
990 #ifdef HAVE_CRED_STORE
991     AP_INIT_FLAG("GssapiUseS4U2Proxy", mag_use_s4u2p, NULL, OR_AUTHCFG,
992                   "Initializes credentials for s4u2proxy usage"),
993     AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG,
994                     "Credential Store"),
995     AP_INIT_RAW_ARGS("GssapiDelegCcacheDir", mag_deleg_ccache_dir, NULL,
996                      OR_AUTHCFG, "Directory to store delegated credentials"),
997 #endif
998 #ifdef HAVE_GSS_ACQUIRE_CRED_WITH_PASSWORD
999     AP_INIT_FLAG("GssapiBasicAuth", mag_use_basic_auth, NULL, OR_AUTHCFG,
1000                      "Allows use of Basic Auth for authentication"),
1001 #endif
1002     AP_INIT_ITERATE("GssapiAllowedMech", mag_allow_mech, NULL, OR_AUTHCFG,
1003                     "Allowed Mechanisms"),
1004     { NULL }
1005 };
1006
1007 static void
1008 mag_register_hooks(apr_pool_t *p)
1009 {
1010     ap_hook_check_user_id(mag_auth, NULL, NULL, APR_HOOK_MIDDLE);
1011     ap_hook_post_config(mag_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1012     ap_hook_pre_connection(mag_pre_connection, NULL, NULL, APR_HOOK_MIDDLE);
1013 }
1014
1015 module AP_MODULE_DECLARE_DATA auth_gssapi_module =
1016 {
1017     STANDARD20_MODULE_STUFF,
1018     mag_create_dir_config,
1019     NULL,
1020     NULL,
1021     NULL,
1022     mag_commands,
1023     mag_register_hooks
1024 };