Used gsskrb5_register_acceptor_identity() to specify the keytab (some installations...
[mod_auth_kerb.cvs/.git] / src / mod_auth_kerb.c
index 714d5c8..56e7297 100644 (file)
 
 #include "config.h"
 
-#define MODAUTHKERB_VERSION "5.0-rc5"
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#define MODAUTHKERB_VERSION "5.0-rc6"
+#define MECH_NEGOTIATE "Negotiate"
 
 #include <httpd.h>
 #include <http_config.h>
 #include <netdb.h> /* gethostbyname() */
 #endif /* KRB4 */
 
+#ifdef WIN32
+#define vsnprintf _vsnprintf
+#define snprintf _snprintf
+#else
 /* XXX remove dependency on unistd.h ??? */
 #include <unistd.h>
+#endif
 
 #ifdef STANDARD20_MODULE_STUFF
 module AP_MODULE_DECLARE_DATA auth_kerb_module;
@@ -143,12 +153,12 @@ krb5_save_realms(cmd_parms *cmd, kerb_auth_config *sec, char *arg);
 #define command(name, func, var, type, usage)           \
   AP_INIT_ ## type (name, func,                         \
         (void*)APR_XtOffsetOf(kerb_auth_config, var),   \
-        OR_AUTHCFG, usage)
+        OR_AUTHCFG | RSRC_CONF, usage)
 #else
 #define command(name, func, var, type, usage)          \
   { name, func,                                        \
     (void*)XtOffsetOf(kerb_auth_config, var),          \
-    OR_AUTHCFG, type, usage }
+    OR_AUTHCFG | RSRC_CONF, type, usage }
 #endif
 
 static const command_rec kerb_auth_cmds[] = {
@@ -195,6 +205,41 @@ static const command_rec kerb_auth_cmds[] = {
    { NULL }
 };
 
+#ifdef WIN32
+int
+mkstemp(char *template)
+{
+    int start, i;
+    pid_t val;
+    val = getpid();
+    start = strlen(template) - 1;
+    while(template[start] == 'X') {
+       template[start] = '0' + val % 10;
+       val /= 10;
+       start--;
+    }
+    
+    do{
+       int fd;
+       fd = open(template, O_RDWR | O_CREAT | O_EXCL, 0600);
+       if(fd >= 0 || errno != EEXIST)
+           return fd;
+       i = start + 1;
+       do{
+           if(template[i] == 0)
+               return -1;
+           template[i]++;
+           if(template[i] == '9' + 1)
+               template[i] = 'a';
+           if(template[i] <= 'z')
+               break;
+           template[i] = 'a';
+           i++;
+       }while(1);
+    }while(1);
+}
+#endif
+
 #if defined(KRB5) && !defined(HEIMDAL)
 /* Needed to work around problems with replay caches */
 #include "mit-internals.h"
@@ -465,7 +510,7 @@ end:
  * we had to use this call instead, which is only a bit modified version of
  * krb5_verify_init_creds() */
 static krb5_error_code
-verify_krb5_init_creds(krb5_context context, krb5_creds *creds,
+verify_krb5_init_creds(request_rec *r, krb5_context context, krb5_creds *creds,
                        krb5_principal ap_req_server, krb5_keytab ap_req_keytab)
 {
    krb5_error_code ret;
@@ -474,6 +519,7 @@ verify_krb5_init_creds(krb5_context context, krb5_creds *creds,
    krb5_creds *new_creds = NULL;
    krb5_auth_context auth_context = NULL;
    krb5_keytab keytab = NULL;
+   char *server_name;
 
    memset(&req, 0, sizeof(req));
 
@@ -485,16 +531,35 @@ verify_krb5_init_creds(krb5_context context, krb5_creds *creds,
       keytab = ap_req_keytab;
 
    ret = krb5_cc_resolve(context, "MEMORY:", &local_ccache);
-   if (ret)
+   if (ret) {
+      log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                "krb5_cc_resolve() failed when verifying KDC");
       return ret;
+   }
 
    ret = krb5_cc_initialize(context, local_ccache, creds->client);
-   if (ret)
+   if (ret) {
+      log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                "krb5_cc_initialize() failed when verifying KDC");
       goto end;
+   }
 
    ret = krb5_cc_store_cred (context, local_ccache, creds);
-   if (ret)
+   if (ret) {
+      log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                "krb5_cc_initialize() failed when verifying KDC");
+      goto end;
+   }
+   
+   ret = krb5_unparse_name(context, ap_req_server, &server_name);
+   if (ret) {
+      log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                "krb5_unparse_name() failed when verifying KDC");
       goto end;
+   }
+   log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+             "Trying to verify authenticity of KDC using principal %s", server_name);
+   free(server_name);
 
    if (!krb5_principal_compare (context, ap_req_server, creds->server)) {
       krb5_creds match_cred;
@@ -506,28 +571,47 @@ verify_krb5_init_creds(krb5_context context, krb5_creds *creds,
 
       ret = krb5_get_credentials (context, 0, local_ccache, 
                                  &match_cred, &new_creds);
-      if (ret)
+      if (ret) {
+        log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                   "krb5_get_credentials() failed when verifying KDC");
         goto end;
+      }
       creds = new_creds;
    }
 
    ret = krb5_mk_req_extended (context, &auth_context, 0, NULL, creds, &req);
-   if (ret)
+   if (ret) {
+      log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                "krb5_mk_req_extended() failed when verifying KDC");
       goto end;
+   }
 
    krb5_auth_con_free (context, auth_context);
    auth_context = NULL;
    ret = krb5_auth_con_init(context, &auth_context);
-   if (ret)
+   if (ret) {
+      log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                "krb5_auth_con_init() failed when verifying KDC");
       goto end;
+   }
    /* use KRB5_AUTH_CONTEXT_DO_SEQUENCE to skip replay cache checks */
    krb5_auth_con_setflags(context, auth_context, KRB5_AUTH_CONTEXT_DO_SEQUENCE);
 
    ret = krb5_rd_req (context, &auth_context, &req, ap_req_server,
                      keytab, 0, NULL);
+   if (ret) {
+      log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                "krb5_rd_req() failed when verifying KDC");
+      goto end;
+   }
 
 end:
+#ifdef HEIMDAL
+   /* XXX Do I ever want to support Heimdal 0.4 ??? */
+   krb5_data_free(&req);
+#else
    krb5_free_data_contents(context, &req);
+#endif
    if (auth_context)
       krb5_auth_con_free (context, auth_context);
    if (new_creds)
@@ -550,6 +634,7 @@ verify_krb5_user(request_rec *r, krb5_context context, krb5_principal principal,
    krb5_principal server = NULL;
    krb5_error_code ret;
    krb5_ccache ret_ccache = NULL;
+   char *name = NULL;
 
    /* XXX error messages shouldn't be logged here (and in the while() loop in
     * authenticate_user_krb5pwd() as weell), in order to avoid confusing log
@@ -557,6 +642,13 @@ verify_krb5_user(request_rec *r, krb5_context context, krb5_principal principal,
 
    memset(&creds, 0, sizeof(creds));
 
+   ret = krb5_unparse_name(context, principal, &name);
+   if (ret == 0) {
+      log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                "Trying to get TGT for user %s", name);
+      free(name);
+   }
+
    ret = krb5_get_init_creds_password(context, &creds, principal, 
                                      (char *)password, NULL,
                                      NULL, 0, NULL, NULL);
@@ -564,11 +656,11 @@ verify_krb5_user(request_rec *r, krb5_context context, krb5_principal principal,
       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                 "krb5_get_init_creds_password() failed: %s",
                 krb5_get_err_text(context, ret));
-      return ret;
+      goto end;
    }
 
    ret = krb5_sname_to_principal(context, ap_get_server_name(r), service, 
-                                KRB5_NT_UNKNOWN, &server);
+                                KRB5_NT_SRV_HST, &server);
    if (ret) {
       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                 "krb5_sname_to_principal() failed: %s",
@@ -589,7 +681,7 @@ verify_krb5_user(request_rec *r, krb5_context context, krb5_principal principal,
    */
 
    if (krb_verify_kdc &&
-       (ret = verify_krb5_init_creds(context, &creds, server, keytab))) {
+       (ret = verify_krb5_init_creds(r, context, &creds, server, keytab))) {
        log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                  "failed to verify krb5 credentials: %s",
                  krb5_get_err_text(context, ret));
@@ -767,6 +859,7 @@ int authenticate_user_krb5pwd(request_rec *r,
    const char      *sent_pw = NULL; 
    const char      *sent_name = NULL;
    const char      *realms = NULL;
+   const char      *realm = NULL;
    krb5_context    kcontext = NULL;
    krb5_error_code code;
    krb5_principal  client = NULL;
@@ -806,19 +899,16 @@ int authenticate_user_krb5pwd(request_rec *r,
    all_principals_unkown = 1;
    realms = conf->krb_auth_realms;
    do {
-      if (realms && (code = krb5_set_default_realm(kcontext,
-                                          ap_getword_white(r->pool, &realms)))){
-        log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                   "krb5_set_default_realm() failed: %s",
-                   krb5_get_err_text(kcontext, code));
-        continue;
-      }
+      name = sent_name;
+      if (realms && (realm = ap_getword_white(r->pool, &realms)))
+        name = ap_psprintf(r->pool, "%s@%s", sent_name, realm);
 
       if (client) {
         krb5_free_principal(kcontext, client);
         client = NULL;
       }
-      code = krb5_parse_name(kcontext, sent_name, &client);
+
+      code = krb5_parse_name(kcontext, name, &client);
       if (code) {
         log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                    "krb5_parse_name() failed: %s",
@@ -873,7 +963,7 @@ int authenticate_user_krb5pwd(request_rec *r,
 end:
    log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
              "kerb_authenticate_user_krb5pwd ret=%d user=%s authtype=%s",
-             ret, (MK_USER)?MK_USER:"(NULL)", MK_AUTH_TYPE);
+             ret, (MK_USER)?MK_USER:"(NULL)", (MK_AUTH_TYPE)?MK_AUTH_TYPE:"(NULL)");
    if (client)
       krb5_free_principal(kcontext, client);
    if (ccache)
@@ -983,20 +1073,21 @@ get_gss_creds(request_rec *r,
               kerb_auth_config *conf,
              gss_cred_id_t *server_creds)
 {
-   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
+   gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
    OM_uint32 major_status, minor_status, minor_status2;
    gss_name_t server_name = GSS_C_NO_NAME;
    char buf[1024];
 
    snprintf(buf, sizeof(buf), "%s@%s", conf->krb_service_name,
-        ap_get_server_name(r));
+           ap_get_server_name(r));
 
-   input_token.value = buf;
-   input_token.length = strlen(buf) + 1;
+   token.value = buf;
+   token.length = strlen(buf) + 1;
 
-   major_status = gss_import_name(&minor_status, &input_token,
+   major_status = gss_import_name(&minor_status, &token,
                                  GSS_C_NT_HOSTBASED_SERVICE,
                                  &server_name);
+   memset(&token, 0, sizeof(token));
    if (GSS_ERROR(major_status)) {
       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                 "%s", get_gss_error(r->pool, major_status, minor_status,
@@ -1004,7 +1095,19 @@ get_gss_creds(request_rec *r,
       return HTTP_INTERNAL_SERVER_ERROR;
    }
 
-   log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Acquiring creds for %s", buf);
+   major_status = gss_display_name(&minor_status, server_name, &token, NULL);
+   if (GSS_ERROR(major_status)) {
+      /* Perhaps we could just ignore this error but it's safer to give up now,
+         I think */
+      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                "%s", get_gss_error(r->pool, major_status, minor_status,
+                                    "gss_display_name() failed"));
+      return HTTP_INTERNAL_SERVER_ERROR;
+   }
+
+   log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Acquiring creds for %s",
+             token.value);
+   gss_release_buffer(&minor_status, &token);
    
    major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
                                   GSS_C_NO_OID_SET, GSS_C_ACCEPT,
@@ -1050,8 +1153,6 @@ cmp_gss_type(gss_buffer_t token, gss_OID oid)
    if (token->length == 0)
       return GSS_S_DEFECTIVE_TOKEN;
 
-   /* XXX if (token->value == NTLMSSP) log_debug("NTLM mechanism used"); */
-
    p = token->value;
    if (*p++ != 0x60)
       return GSS_S_DEFECTIVE_TOKEN;
@@ -1081,7 +1182,11 @@ authenticate_user_gss(request_rec *r, kerb_auth_config *conf,
   int ret;
   gss_name_t client_name = GSS_C_NO_NAME;
   gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL;
-  OM_uint32 (*accept_sec_token)();
+  OM_uint32 
+     (*accept_sec_token)(OM_uint32 *, gss_ctx_id_t *, const gss_cred_id_t,
+                        const gss_buffer_t, const gss_channel_bindings_t,
+                        gss_name_t *, gss_OID *, gss_buffer_t, OM_uint32 *,
+                        OM_uint32 *, gss_cred_id_t *);
   gss_OID_desc spnego_oid;
   gss_ctx_id_t context = GSS_C_NO_CONTEXT;
   gss_cred_id_t server_creds = GSS_C_NO_CREDENTIAL;
@@ -1104,6 +1209,10 @@ authenticate_user_gss(request_rec *r, kerb_auth_config *conf,
      }
      sprintf(ktname, "KRB5_KTNAME=%s", conf->krb_5_keytab);
      putenv(ktname);
+#ifdef HEIMDAL
+     /* Seems to be also supported by latest MIT */
+     gsskrb5_register_acceptor_identity(conf->krb_5_keytab);
+#endif
   }
 
   ret = get_gss_creds(r, conf, &server_creds);
@@ -1132,6 +1241,7 @@ authenticate_user_gss(request_rec *r, kerb_auth_config *conf,
   accept_sec_token = (cmp_gss_type(&input_token, &spnego_oid) == 0) ?
                        gss_accept_sec_context_spnego : gss_accept_sec_context;
 
+  /* pridat: Read client Negotiate data of length XXX, prefix YYY */
   log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Verifying client data using %s",
             (accept_sec_token == gss_accept_sec_context)
               ? "KRB5 GSS-API"
@@ -1168,11 +1278,15 @@ authenticate_user_gss(request_rec *r, kerb_auth_config *conf,
      *negotiate_ret_value = token;
      log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                "GSS-API token of length %d bytes will be sent back",
-               major_status, output_token.length);
+               output_token.length);
      gss_release_buffer(&minor_status2, &output_token);
   }
 
   if (GSS_ERROR(major_status)) {
+     if (input_token.length > 7 && memcmp(input_token.value, "NTLMSSP", 7) == 0)
+       log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                 "Warning: received token seems to be NTLM, which isn't supported by the Kerberos module. Check your IE configuration.");
+
      log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                "%s", get_gss_error(r->pool, major_status, minor_status,
                                    "gss_accept_sec_context() failed"));
@@ -1202,7 +1316,7 @@ authenticate_user_gss(request_rec *r, kerb_auth_config *conf,
     goto end;
   }
 
-  MK_AUTH_TYPE = "Negotiate";
+  MK_AUTH_TYPE = MECH_NEGOTIATE;
   MK_USER = ap_pstrdup(r->pool, output_token.value);
 
   if (conf->krb_save_credentials && delegated_cred != GSS_C_NO_CREDENTIAL)
@@ -1240,7 +1354,7 @@ already_succeeded(request_rec *r)
 {
    if (ap_is_initial_req(r) || MK_AUTH_TYPE == NULL)
       return 0;
-   if (strcmp(MK_AUTH_TYPE, "Negotiate") ||
+   if (strcmp(MK_AUTH_TYPE, MECH_NEGOTIATE) ||
        (strcmp(MK_AUTH_TYPE, "Basic") && strchr(MK_USER, '@')))
       return 1;
    return 0;
@@ -1263,8 +1377,8 @@ set_kerb_auth_headers(request_rec *r, const kerb_auth_config *conf,
     * apache in the proxy mode should retain client's authN headers? */
 #ifdef KRB5
    if (negotiate_ret_value != NULL && conf->krb_method_gssapi) {
-      negoauth_param = (*negotiate_ret_value == '\0') ? "Negotiate" :
-                 ap_pstrcat(r->pool, "Negotiate ", negotiate_ret_value, NULL);
+      negoauth_param = (*negotiate_ret_value == '\0') ? MECH_NEGOTIATE :
+                 ap_pstrcat(r->pool, MECH_NEGOTIATE " ", negotiate_ret_value, NULL);
       ap_table_add(r->err_headers_out, header_name, negoauth_param);
    }
    if ((use_krb5pwd && conf->krb_method_k5pass) || conf->krb_delegate_basic) {
@@ -1340,7 +1454,7 @@ int kerb_authenticate_user(request_rec *r)
 
 #ifdef KRB5
    if (use_krb5 && conf->krb_method_gssapi &&
-       strcasecmp(auth_type, "Negotiate") == 0) {
+       strcasecmp(auth_type, MECH_NEGOTIATE) == 0) {
       ret = authenticate_user_gss(r, conf, auth_line, &negotiate_ret_value);
    } else if (use_krb5 && conf->krb_method_k5pass &&
              strcasecmp(auth_type, "Basic") == 0) {