Better r.e. to prevent from substituing empty strings on some platforms
[mod_auth_kerb.cvs/.git] / src / mod_auth_kerb.c
index da5a1bc..13f7ad3 100644 (file)
@@ -50,7 +50,7 @@
 #include <stdio.h>
 #include <stdarg.h>
 
-#define MODAUTHKERB_VERSION "5.3"
+#define MODAUTHKERB_VERSION "5.4"
 
 #define MECH_NEGOTIATE "Negotiate"
 #define SERVICE_NAME "HTTP"
 #define apr_pool_cleanup_register      ap_register_cleanup
 #endif /* STANDARD20_MODULE_STUFF */
 
+#if AP_SERVER_MAJORVERSION_NUMBER == 2 && AP_SERVER_MINORVERSION_NUMBER== 2
+#define APACHE22
+#include "mod_auth.h"
+#endif
+
 #ifdef _WIN32
 #define vsnprintf _vsnprintf
 #define snprintf _snprintf
 #include <netdb.h> /* gethostbyname() */
 #endif /* KRB4 */
 
-#ifndef _WIN32
-/* should be HAVE_UNISTD_H instead */
+#if HAVE_UNISTD_H
 #include <unistd.h>
 #endif
 
@@ -299,7 +303,7 @@ mod_auth_kerb_rc_store(krb5_context context, krb5_rcache rcache,
 }
 
 /* And this is the operations vector for our replay cache */
-const krb5_rc_ops_internal mod_auth_kerb_rc_ops = {
+static const krb5_rc_ops_internal mod_auth_kerb_rc_ops = {
   0,
   "dfl",
   krb5_rc_dfl_init,
@@ -330,7 +334,7 @@ static void *kerb_dir_create_config(MK_POOL *p, char *d)
        ((kerb_auth_config *)rec)->krb_ssl_preauthentication = 0;
 #endif
 #ifdef KRB5
-  ((kerb_auth_config *)rec)->krb5_do_auth_to_local = 0;
+        ((kerb_auth_config *)rec)->krb5_do_auth_to_local = 0;
        ((kerb_auth_config *)rec)->krb_method_k5pass = 1;
        ((kerb_auth_config *)rec)->krb_method_gssapi = 1;
 #endif
@@ -448,7 +452,7 @@ authenticate_user_krb4pwd(request_rec *r,
                          const char *auth_line)
 {
    int ret;
-   const char *sent_pw;
+   char *sent_pw;
    const char *sent_name;
    char *sent_instance;
    char tkt_file[32];
@@ -528,7 +532,7 @@ authenticate_user_krb4pwd(request_rec *r,
    user = apr_pstrcat(r->pool, user, "@", realm, NULL);
 
    MK_USER = user;
-   MK_AUTH_TYPE = "Basic";
+   MK_AUTH_TYPE = "Kerberos";
    apr_table_setn(r->subprocess_env, "KRBTKFILE", tkt_file_p);
 
    if (!conf->krb_save_credentials)
@@ -678,12 +682,16 @@ end:
 static krb5_error_code
 verify_krb5_user(request_rec *r, krb5_context context, krb5_principal principal,
                 const char *password, krb5_principal server,
-                krb5_keytab keytab, int krb_verify_kdc, krb5_ccache *ccache)
+                krb5_keytab keytab, int krb_verify_kdc,
+                const char *krb_service_name, krb5_ccache *ccache)
 {
    krb5_creds creds;
+   krb5_get_init_creds_opt options;
    krb5_error_code ret;
    krb5_ccache ret_ccache = NULL;
    char *name = NULL;
+   krb5_keytab_entry entry;
+   krb5_kt_cursor cursor;
 
    /* 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
@@ -698,9 +706,10 @@ verify_krb5_user(request_rec *r, krb5_context context, krb5_principal principal,
       free(name);
    }
 
+   krb5_get_init_creds_opt_init(&options);
    ret = krb5_get_init_creds_password(context, &creds, principal, 
                                      (char *)password, NULL,
-                                     NULL, 0, NULL, NULL);
+                                     NULL, 0, NULL, &options);
    if (ret) {
       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                 "krb5_get_init_creds_password() failed: %s",
@@ -719,12 +728,42 @@ verify_krb5_user(request_rec *r, krb5_context context, krb5_principal principal,
    }
    */
 
-   if (krb_verify_kdc &&
+   /*if (krb_verify_kdc &&
        (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));
        goto end;
+   }*/
+
+   if (krb_verify_kdc) {
+     if (krb_service_name && strcmp(krb_service_name,"Any") == 0) {
+       ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+       if(!ret) {
+         while((krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0){
+           if ((ret = verify_krb5_init_creds(r, context, &creds, entry.principal, keytab)) == 0) 
+             break;
+         }
+       }
+       if (ret) {
+         log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                   "failed to verify krb5 credentials: %s",
+                         krb5_get_err_text(context, ret));
+         krb5_kt_end_seq_get(context, keytab, &cursor);
+         krb5_kt_close(context, keytab);
+         goto end;
+       }
+       krb5_kt_end_seq_get(context, keytab, &cursor);
+       krb5_kt_close(context, keytab);
+     }
+     else {
+       if ((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));
+       goto end;
+       }
+     }
    }
 
 #ifdef HAVE_KRB5_CC_NEW_UNIQUE
@@ -893,7 +932,6 @@ store_krb5_creds(krb5_context kcontext,
    return OK;
 }
 
-
 static int
 authenticate_user_krb5pwd(request_rec *r,
                           kerb_auth_config *conf,
@@ -914,10 +952,6 @@ authenticate_user_krb5pwd(request_rec *r,
    int             all_principals_unkown;
    char            *p = NULL;
 
-   //temporary fix for KrbServiceName Any, use default SERVICE_NAME
-   if (conf->krb_service_name && strcmp(conf->krb_service_name,"Any") == 0)
-      snprintf(conf->krb_service_name, 5,"%s",SERVICE_NAME);
-
    code = krb5_init_context(&kcontext);
    if (code) {
       log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
@@ -998,7 +1032,7 @@ authenticate_user_krb5pwd(request_rec *r,
       }
 
       code = verify_krb5_user(r, kcontext, client, sent_pw,
-                             server, keytab, conf->krb_verify_kdc, &ccache);
+                             server, keytab, conf->krb_verify_kdc, conf->krb_service_name, &ccache);
       if (!conf->krb_authoritative && code) {
         /* if we're not authoritative, we allow authentication to pass on
          * to another modules if (and only if) the user is not known to us */
@@ -1032,7 +1066,7 @@ authenticate_user_krb5pwd(request_rec *r,
       goto end;
    }
    MK_USER = apr_pstrdup (r->pool, name);
-   MK_AUTH_TYPE = "Basic";
+   MK_AUTH_TYPE = "Kerberos";
    free(name);
 
    if (conf->krb_save_credentials)
@@ -1188,7 +1222,7 @@ get_gss_creds(request_rec *r,
    token.length = strlen(buf) + 1;
 
    major_status = gss_import_name(&minor_status, &token,
-                                 (have_server_princ) ? GSS_KRB5_NT_PRINCIPAL_NAME : GSS_C_NT_HOSTBASED_SERVICE,
+                                 (have_server_princ) ? (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME : (gss_OID) GSS_C_NT_HOSTBASED_SERVICE,
                                  &server_name);
    memset(&token, 0, sizeof(token));
    if (GSS_ERROR(major_status)) {
@@ -1251,6 +1285,7 @@ get_gss_creds(request_rec *r,
    return 0;
 }
 
+#ifndef GSSAPI_SUPPORTS_SPNEGO
 static int
 cmp_gss_type(gss_buffer_t token, gss_OID oid)
 {
@@ -1277,6 +1312,7 @@ cmp_gss_type(gss_buffer_t token, gss_OID oid)
 
    return memcmp(p, oid->elements, oid->length);
 }
+#endif
 
 static int
 authenticate_user_gss(request_rec *r, kerb_auth_config *conf,
@@ -1353,10 +1389,10 @@ authenticate_user_gss(request_rec *r, kerb_auth_config *conf,
                        gss_accept_sec_context_spnego : gss_accept_sec_context;
 #endif
 
-  log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Verifying client data using %s",
+  log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Verifying client data using KRB5 GSS-API %s",
             (accept_sec_token == gss_accept_sec_context)
-              ? "KRB5 GSS-API"
-              : "SPNEGO GSS-API");
+              ? ""
+              : "with our SPNEGO lib");
 
   major_status = accept_sec_token(&minor_status,
                                  &context,
@@ -1371,7 +1407,7 @@ authenticate_user_gss(request_rec *r, kerb_auth_config *conf,
                                  &delegated_cred);
   log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
             "Client %s us their credential",
-            (ret_flags & GSS_C_DELEG_FLAG) ? "sent" : "didn't send");
+            (ret_flags & GSS_C_DELEG_FLAG) ? "delegated" : "didn't delegate");
   if (output_token.length) {
      char *token = NULL;
      size_t len;
@@ -1409,15 +1445,15 @@ authenticate_user_gss(request_rec *r, kerb_auth_config *conf,
      goto end;
   }
 
-#if 0
-  /* This is a _Kerberos_ module so multiple authentication rounds aren't
-   * supported. If we wanted a generic GSS authentication we would have to do
-   * some magic with exporting context etc. */
+  /* Multiple authentication rounds aren't supported.  If we wanted a generic
+   * GSS authentication we would have to do some magic with exporting context
+   * etc. */
   if (major_status & GSS_S_CONTINUE_NEEDED) {
+     log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                "Multi-iteration authentication isn't supported");
      ret = HTTP_UNAUTHORIZED;
      goto end;
   }
-#endif
 
   major_status = gss_display_name(&minor_status, client_name, &output_token, NULL);
   gss_release_name(&minor_status, &client_name); 
@@ -1520,16 +1556,16 @@ do_krb5_an_to_ln(request_rec *r) {
 #endif /* KRB5 */
 
 static krb5_conn_data *
-already_succeeded(request_rec *r, char *auth_line)
+already_authorized(request_rec *r, char *auth_line)
 {
    krb5_conn_data *conn_data;
-   const char keyname[1024];
-
+   char keyname[1024];
+   
    snprintf(keyname, sizeof(keyname) - 1,
        "mod_auth_kerb::connection::%s::%ld", r->connection->remote_ip, 
        r->connection->id);
 
-   if (apr_pool_userdata_get(&conn_data, keyname, r->connection->pool) != 0)
+   if (apr_pool_userdata_get((void**)&conn_data, keyname, r->connection->pool) != 0)
        return NULL;
 
    if(conn_data) {
@@ -1542,6 +1578,24 @@ already_succeeded(request_rec *r, char *auth_line)
 }
 
 static void
+save_authorized(request_rec *r, char *auth_line, const char *auth_type, int ret) {
+    krb5_conn_data *prevauth;
+    prevauth = (krb5_conn_data *)apr_palloc(r->connection->pool, sizeof(krb5_conn_data));
+    
+    char keyname[1024];
+    
+    prevauth->user = apr_pstrdup(r->connection->pool, MK_USER);
+    prevauth->authline = apr_pstrdup(r->connection->pool, auth_line);
+    prevauth->mech = apr_pstrdup(r->connection->pool, auth_type);
+    prevauth->last_return = ret;
+    
+    snprintf(keyname, sizeof(keyname) - 1,
+             "mod_auth_kerb::connection::%s::%ld",
+             r->connection->remote_ip, r->connection->id);
+    apr_pool_userdata_set(prevauth, keyname, NULL, r->connection->pool);
+}
+
+static void
 set_kerb_auth_headers(request_rec *r, const kerb_auth_config *conf,
                      int use_krb4, int use_krb5pwd, char *negotiate_ret_value)
 {
@@ -1578,57 +1632,22 @@ set_kerb_auth_headers(request_rec *r, const kerb_auth_config *conf,
 }
 
 static int
-kerb_authenticate_user(request_rec *r)
+authenticate_user(request_rec *r, char *auth_line, const char *type, int use_krb4, int use_krb5)
 {
-   kerb_auth_config *conf = 
-      (kerb_auth_config *) ap_get_module_config(r->per_dir_config,
-                                               &auth_kerb_module);
-   krb5_conn_data *prevauth = NULL;
-   const char *auth_type = NULL;
-   const char *auth_line = NULL;
-   const char *type = NULL;
-   int use_krb5 = 0, use_krb4 = 0;
    int ret;
-   static int last_return = HTTP_UNAUTHORIZED;
+   krb5_conn_data *prevauth = NULL;
+   kerb_auth_config *conf =
+      (kerb_auth_config *) ap_get_module_config(r->per_dir_config,
+                                                &auth_kerb_module);
    char *negotiate_ret_value = NULL;
-   char keyname[1024];
-
-   /* get the type specified in .htaccess */
-   type = ap_auth_type(r);
-
-   log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
-             "kerb_authenticate_user entered with user %s and auth_type %s",
-             (MK_USER)?MK_USER:"(NULL)",type?type:"(NULL)");
-
-   if (type && strcasecmp(type, "Kerberos") == 0)
-      use_krb5 = use_krb4 = 1;
-   else if(type && strcasecmp(type, "KerberosV5") == 0)
-      use_krb5 = 1;
-   else if(type && strcasecmp(type, "KerberosV4") == 0)
-      use_krb4 = 1;
-   else
-      return DECLINED;
-
-#if 0
-   if (conf->krb_ssl_preauthentication) {
-      const char *ssl_client_verify = ssl_var_lookup(r->pool, r->server,
-               r->connection, r, "SSL_CLIENT_VERIFY");
-
-      if (ssl_client_verify && strcmp(ssl_client_verify, "SUCCESS") == 0)
-        return OK;
-   }
-#endif
-
-   /* get what the user sent us in the HTTP header */
-   auth_line = MK_TABLE_GET(r->headers_in, (r->proxyreq == PROXYREQ_PROXY)
-                                           ? "Proxy-Authorization"
-                                           : "Authorization");
+   const char *auth_type = NULL;
+   
    if (!auth_line) {
       set_kerb_auth_headers(r, conf, use_krb4, use_krb5, 
                            (use_krb5) ? "\0" : NULL);
       return HTTP_UNAUTHORIZED;
    }
-   auth_type = ap_getword_white(r->pool, &auth_line);
+   auth_type = ap_getword_white(r->pool, (const char **)&auth_line);
 
    /* If we are delegating Basic to other modules, DECLINE the request */
    if (conf->krb_delegate_basic &&
@@ -1640,60 +1659,108 @@ kerb_authenticate_user(request_rec *r)
 #endif
        (strcasecmp(auth_type, "Basic") == 0))
        return DECLINED;
-
-   if ( (prevauth = already_succeeded(r, auth_line)) == NULL) {
-     ret = HTTP_UNAUTHORIZED;
+   if ((prevauth = already_authorized(r, auth_line)) == NULL) {
+      ret = HTTP_UNAUTHORIZED;
 
 #ifdef KRB5
    if (use_krb5 && conf->krb_method_gssapi &&
        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) {
+   } else if (use_krb5 && (conf->krb_method_k5pass || strcasecmp(type, "Basic"))){
        ret = authenticate_user_krb5pwd(r, conf, auth_line);
    }
 #endif
 
 #ifdef KRB4
-   if (ret == HTTP_UNAUTHORIZED && use_krb4 && conf->krb_method_k4pass &&
-       strcasecmp(auth_type, "Basic") == 0)
+   if (ret == HTTP_UNAUTHORIZED && use_krb4 && (conf->krb_method_k4pass || strcasecmp(type, "Basic")))
       ret = authenticate_user_krb4pwd(r, conf, auth_line);
 #endif
 
-   if (ret == HTTP_UNAUTHORIZED)
-      set_kerb_auth_headers(r, conf, use_krb4, use_krb5, negotiate_ret_value);
+      if (ret == HTTP_UNAUTHORIZED)
+        set_kerb_auth_headers(r, conf, use_krb4, use_krb5, negotiate_ret_value);
 
    } else {
-       ret = prevauth->last_return;
-       MK_USER = prevauth->user;
-       MK_AUTH_TYPE = prevauth->mech;
+      ret = prevauth->last_return;
+      MK_USER = prevauth->user;
+      MK_AUTH_TYPE = prevauth->mech;
    }
 
    /*
     * save who was auth'd, if it's not already stashed.
     */
-     if(!prevauth) {
-       prevauth = (krb5_conn_data *) apr_pcalloc(r->connection->pool, sizeof(krb5_conn_data));
-       prevauth->user = apr_pstrdup(r->connection->pool, MK_USER);
-       prevauth->authline = apr_pstrdup(r->connection->pool, auth_line);
-       prevauth->mech = apr_pstrdup(r->connection->pool, auth_type);
-       prevauth->last_return = ret;
-       snprintf(keyname, sizeof(keyname) - 1,
-           "mod_auth_kerb::connection::%s::%ld", 
-          r->connection->remote_ip, r->connection->id);
-       apr_pool_userdata_set(prevauth, keyname, NULL, r->connection->pool);
+   if(!prevauth) {
+      save_authorized(r, auth_line, auth_type, ret);
    }
 
-     if (ret == OK && conf->krb5_do_auth_to_local)
-       ret = do_krb5_an_to_ln(r);
+   if (ret == OK && conf->krb5_do_auth_to_local) {
+      ret = do_krb5_an_to_ln(r);
+   }
+   return ret;
+}
+
+static authn_status authn_krb_password(request_rec *r, const char *user,
+                                       const char *password)
+{
+   char *auth_line = NULL;
+   int ret;
+   const char *type = NULL;
    
-   /* XXX log_debug: if ret==OK, log(user XY authenticated) */
+   type = ap_auth_type(r);
+   auth_line = ap_pbase64encode (r->pool, apr_psprintf(r->pool, "%s:%s", user, password));
+   auth_line = apr_psprintf(r->pool, "Basic %s", auth_line);
+
+   ret = authenticate_user(r, auth_line, type, 1, 1);
+   
+   if (ret == OK) return AUTH_GRANTED;
+   else return AUTH_USER_NOT_FOUND;
+}
+
+static int
+kerb_authenticate_user(request_rec *r)
+{
+   kerb_auth_config *conf = 
+      (kerb_auth_config *) ap_get_module_config(r->per_dir_config,
+                                               &auth_kerb_module);
+   char *auth_line = NULL;
+   int ret, use_krb4 = 0, use_krb5 = 0;
+   const char *type = NULL;
+   
+   /* get the type specified in .htaccess */
+   type = ap_auth_type(r);
+
+   log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+             "kerb_authenticate_user entered with user %s and auth_type %s",
+             (MK_USER)?MK_USER:"(NULL)",type?type:"(NULL)");
+
+   if (type && strcasecmp(type, "Kerberos") == 0)
+      use_krb5 = use_krb4 = 1;
+   else if(type && strcasecmp(type, "KerberosV5") == 0)
+      use_krb5 = 1;
+   else if(type && strcasecmp(type, "KerberosV4") == 0)
+      use_krb4 = 1;
+   else
+      return DECLINED;
+
+#if 0
+   if (conf->krb_ssl_preauthentication) {
+      const char *ssl_client_verify = ssl_var_lookup(r->pool, r->server,
+               r->connection, r, "SSL_CLIENT_VERIFY");
+
+      if (ssl_client_verify && strcmp(ssl_client_verify, "SUCCESS") == 0)
+        return OK;
+   }
+#endif
+
+   /* get what the user sent us in the HTTP header */
+   auth_line = (char *)MK_TABLE_GET(r->headers_in, (r->proxyreq == PROXYREQ_PROXY)
+                                           ? "Proxy-Authorization"
+                                           : "Authorization");
+   ret = authenticate_user(r, auth_line, type, use_krb4, use_krb5);
 
-   last_return = ret;
    return ret;
 }
 
-int
+static int
 have_rcache_type(const char *type)
 {
    krb5_error_code ret;
@@ -1776,6 +1843,14 @@ kerb_init_handler(apr_pool_t *p, apr_pool_t *plog,
 static void
 kerb_register_hooks(apr_pool_t *p)
 {
+#ifdef APACHE22
+   static const authn_provider authn_krb_provider = {
+      &authn_krb_password,
+      NULL
+   };
+
+   ap_register_provider(p, AUTHN_PROVIDER_GROUP, "kerberos", "0", &authn_krb_provider);
+#endif
    ap_hook_post_config(kerb_init_handler, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_check_user_id(kerb_authenticate_user, NULL, NULL, APR_HOOK_MIDDLE);
 }