Hopefully proper use of LIBADD to fix LDFLAG ordering problems.
[shibboleth/cpp-sp.git] / apache / mod_apache.cpp
index 13d817e..eddfb60 100644 (file)
 # define _CRT_SECURE_NO_DEPRECATE 1
 #endif
 
+#include <shibsp/exceptions.h>
 #include <shibsp/AbstractSPRequest.h>
 #include <shibsp/AccessControl.h>
-#include <shibsp/exceptions.h>
+#include <shibsp/GSSRequest.h>
 #include <shibsp/RequestMapper.h>
 #include <shibsp/SPConfig.h>
 #include <shibsp/ServiceProvider.h>
 #include <shibsp/SessionCache.h>
 #include <shibsp/attribute/Attribute.h>
+
 #include <xercesc/util/XMLUniDefs.hpp>
 #include <xercesc/util/regx/RegularExpression.hpp>
 #include <xmltooling/XMLToolingConfig.h>
 #include <xmltooling/util/NDC.h>
+#include <xmltooling/util/ParserPool.h>
 #include <xmltooling/util/Threads.h>
 #include <xmltooling/util/XMLConstants.h>
 #include <xmltooling/util/XMLHelper.h>
@@ -95,6 +98,9 @@ namespace {
     string g_unsetHeaderValue,g_spoofKey;
     bool g_checkSpoofing = true;
     bool g_catchAll = false;
+#ifndef SHIB_APACHE_13
+    char* g_szGSSContextKey = "mod_auth_gssapi:gss_ctx";
+#endif
     static const char* g_UserDataKey = "urn:mace:shibboleth:Apache:shib_check_user";
 }
 
@@ -155,6 +161,7 @@ struct shib_dir_config
     char* szApplicationId;  // Shib applicationId value
     char* szRequireWith;    // require a session using a specific initiator?
     char* szRedirectToSSL;  // redirect non-SSL requests to SSL port
+       char* szAccessControl;  // path to "external" AccessControl plugin file
     int bOff;               // flat-out disable all Shib processing
     int bBasicHijack;       // activate for AuthType Basic?
     int bRequireSession;    // require a session?
@@ -174,6 +181,7 @@ extern "C" void* create_shib_dir_config (SH_AP_POOL* p, char* d)
     dc->szApplicationId = nullptr;
     dc->szRequireWith = nullptr;
     dc->szRedirectToSSL = nullptr;
+       dc->szAccessControl = nullptr;
     dc->bOff = -1;
     dc->bBasicHijack = -1;
     dc->bRequireSession = -1;
@@ -229,6 +237,13 @@ extern "C" void* merge_shib_dir_config (SH_AP_POOL* p, void* base, void* sub)
     else
         dc->szRedirectToSSL=nullptr;
 
+       if (child->szAccessControl)
+        dc->szAccessControl=ap_pstrdup(p,child->szAccessControl);
+    else if (parent->szAccessControl)
+        dc->szAccessControl=ap_pstrdup(p,parent->szAccessControl);
+    else
+        dc->szAccessControl=nullptr;
+
     dc->bOff=((child->bOff==-1) ? parent->bOff : child->bOff);
     dc->bBasicHijack=((child->bBasicHijack==-1) ? parent->bBasicHijack : child->bBasicHijack);
     dc->bRequireSession=((child->bRequireSession==-1) ? parent->bRequireSession : child->bRequireSession);
@@ -294,10 +309,11 @@ extern "C" const char* shib_table_set(cmd_parms* parms, shib_dir_config* dc, con
     return nullptr;
 }
 
-/********************************************************************************/
-// Apache ShibTarget subclass(es) here.
 
 class ShibTargetApache : public AbstractSPRequest
+#if defined(HAVE_GSSAPI) && !defined(SHIB_APACHE_13)
+    , public GSSRequest
+#endif
 {
   bool m_handler;
   mutable string m_body;
@@ -369,6 +385,7 @@ public:
         (level == SPWarn ? APLOG_WARNING :
         (level == SPError ? APLOG_ERR : APLOG_CRIT))))|APLOG_NOERRNO,
         SH_AP_R(m_req),
+        "%s",
         msg.c_str()
         );
   }
@@ -521,7 +538,7 @@ public:
     return string(SH_AP_AUTH_TYPE(m_req) ? SH_AP_AUTH_TYPE(m_req) : "");
   }
   void setContentType(const char* type) {
-      m_req->content_type = ap_psprintf(m_req->pool, type);
+      m_req->content_type = ap_psprintf(m_req->pool, "%s", type);
   }
   void setResponseHeader(const char* name, const char* value) {
    HTTPResponse::setResponseHeader(name, value);
@@ -574,6 +591,13 @@ public:
   }
   long returnDecline(void) { return DECLINED; }
   long returnOK(void) { return OK; }
+#if defined(HAVE_GSSAPI) && !defined(SHIB_APACHE_13)
+  gss_ctx_id_t getGSSContext() const {
+    gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
+    apr_pool_userdata_get((void**)&ctx, g_szGSSContextKey, m_req->pool);
+    return ctx;
+  }
+#endif
 };
 
 /********************************************************************************/
@@ -990,8 +1014,6 @@ AccessControl::aclresult_t htAccessControl::authorized(const SPRequest& request,
     if (!sta)
         throw ConfigurationException("Request wrapper object was not of correct type.");
 
-    // mod_auth clone
-
     int m=sta->m_req->method_number;
     bool method_restricted=false;
     const char *t, *w;
@@ -1000,6 +1022,43 @@ AccessControl::aclresult_t htAccessControl::authorized(const SPRequest& request,
     if (!reqs_arr)
         return shib_acl_indeterminate;  // should never happen
 
+       // Check for an "embedded" AccessControl plugin.
+       if (sta->m_dc->szAccessControl) {
+               aclresult_t result = shib_acl_false;
+               try {
+            ifstream aclfile(sta->m_dc->szAccessControl);
+            xercesc::DOMDocument* acldoc = XMLToolingConfig::getConfig().getParser().parse(aclfile);
+                   XercesJanitor<xercesc::DOMDocument> docjanitor(acldoc);
+                   static XMLCh _type[] = UNICODE_LITERAL_4(t,y,p,e);
+            string t(XMLHelper::getAttrString(acldoc ? acldoc->getDocumentElement() : nullptr, nullptr, _type));
+            if (t.empty())
+                throw ConfigurationException("Missing type attribute in AccessControl plugin configuration.");
+            auto_ptr<AccessControl> aclplugin(SPConfig::getConfig().AccessControlManager.newPlugin(t.c_str(), acldoc->getDocumentElement()));
+                       Locker acllock(aclplugin.get());
+                       result = aclplugin->authorized(request, session);
+               }
+               catch (exception& ex) {
+                       request.log(SPRequest::SPError, ex.what());
+               }
+
+        if (result == shib_acl_true && sta->m_dc->bRequireAll != 1) {
+            // If we're not insisting that all rules be met, then we're done.
+            request.log(SPRequest::SPDebug, "htaccess: embedded AccessControl plugin was successful, granting access");
+            return shib_acl_true;
+        }
+        else if (result != shib_acl_true && sta->m_dc->bRequireAll == 1) {
+            // If we're insisting that all rules be met, which is not something Apache really handles well,
+            // then we either return false or indeterminate based on the authoritative option, which defaults on.
+            if (sta->m_dc->bAuthoritative != 0) {
+                request.log(SPRequest::SPDebug, "htaccess: embedded AccessControl plugin was unsuccessful, denying access");
+                return shib_acl_false;
+            }
+
+            request.log(SPRequest::SPDebug, "htaccess: embedded AccessControl plugin was unsuccessful but not authoritative, leaving it up to Apache");
+            return shib_acl_indeterminate;
+        }
+       }
+
     require_line* reqs=(require_line*)reqs_arr->elts;
 
     for (int x=0; x<reqs_arr->nelts; x++) {
@@ -1028,17 +1087,18 @@ AccessControl::aclresult_t htAccessControl::authorized(const SPRequest& request,
             status = true;
         }
         else if (!strcmp(w,"user") && !remote_user.empty()) {
-            bool regexp=false,negate=false;
+            bool regexp = false;
             while (*t) {
-                w=ap_getword_conf(sta->m_req->pool,&t);
-                if (*w=='~') {
-                    regexp=true;
+                w = ap_getword_conf(sta->m_req->pool,&t);
+                if (*w == '~') {
+                    regexp = true;
                     continue;
                 }
-                else if (*w=='!') {
-                    negate=true;
-                    if (*(w+1)=='~')
-                        regexp=true;
+                else if (*w == '!') {
+                    // A negated rule presumes success unless a match is found.
+                    status = true;
+                    if (*(w+1) == '~')
+                        regexp = true;
                     continue;
                 }
 
@@ -1058,87 +1118,93 @@ AccessControl::aclresult_t htAccessControl::authorized(const SPRequest& request,
                             string("htaccess plugin caught exception while parsing regular expression (") + w + "): " + tmp.get());
                     }
                 }
-                else if (remote_user==w) {
+                else if (remote_user == w) {
                     match = true;
                 }
 
                 if (match) {
-                    // If we matched, then we're done with this rule either way and status is set to reflect the outcome.
-                    status = !negate;
+                    // If we matched, then we're done with this rule either way and we flip status to reflect the outcome.
+                    status = !status;
                     if (request.isPriorityEnabled(SPRequest::SPDebug))
                         request.log(SPRequest::SPDebug,
-                            string("htaccess: require user ") + (negate ? "rejecting (" : "accepting (") + remote_user + ")");
+                            string("htaccess: require user ") + (!status ? "rejecting (" : "accepting (") + remote_user + ")");
                     break;
                 }
             }
         }
         else if (!strcmp(w,"group")  && !remote_user.empty()) {
-            SH_AP_TABLE* grpstatus=nullptr;
+            SH_AP_TABLE* grpstatus = nullptr;
             if (sta->m_dc->szAuthGrpFile) {
                 if (request.isPriorityEnabled(SPRequest::SPDebug))
                     request.log(SPRequest::SPDebug,string("htaccess plugin using groups file: ") + sta->m_dc->szAuthGrpFile);
-                grpstatus=groups_for_user(sta->m_req,remote_user.c_str(),sta->m_dc->szAuthGrpFile);
+                grpstatus = groups_for_user(sta->m_req,remote_user.c_str(),sta->m_dc->szAuthGrpFile);
             }
 
-            bool negate=false;
             while (*t) {
-                w=ap_getword_conf(sta->m_req->pool,&t);
-                if (*w=='!') {
-                    negate=true;
+                w = ap_getword_conf(sta->m_req->pool,&t);
+                if (*w == '!') {
+                    // A negated rule presumes success unless a match is found.
+                    status = true;
                     continue;
                 }
 
                 if (grpstatus && ap_table_get(grpstatus,w)) {
-                    // If we matched, then we're done with this rule either way and status is set to reflect the outcome.
-                    status = !negate;
-                    request.log(SPRequest::SPDebug, string("htaccess: require group ") + (negate ? "rejecting (" : "accepting (") + w + ")");
+                    // If we matched, then we're done with this rule either way and we flip status to reflect the outcome.
+                    status = !status;
+                    request.log(SPRequest::SPDebug, string("htaccess: require group ") + (!status ? "rejecting (" : "accepting (") + w + ")");
                     break;
                 }
             }
         }
         else if (!strcmp(w,"authnContextClassRef") || !strcmp(w,"authnContextDeclRef")) {
             const char* ref = !strcmp(w,"authnContextClassRef") ? session->getAuthnContextClassRef() : session->getAuthnContextDeclRef();
-            bool regexp=false,negate=false;
-            while (ref && *t) {
-                w=ap_getword_conf(sta->m_req->pool,&t);
-                if (*w=='~') {
-                    regexp=true;
-                    continue;
-                }
-                else if (*w=='!') {
-                    negate=true;
-                    if (*(w+1)=='~')
+            if (ref && *ref) {
+                bool regexp = false;
+                while (ref && *t) {
+                    w = ap_getword_conf(sta->m_req->pool,&t);
+                    if (*w == '~') {
                         regexp=true;
-                    continue;
-                }
+                        continue;
+                    }
+                    else if (*w == '!') {
+                        // A negated rule presumes success unless a match is found.
+                        status = true;
+                        if (*(w+1)=='~')
+                            regexp = true;
+                        continue;
+                    }
 
-                // Figure out if there's a match.
-                bool match = false;
-                if (regexp) {
-                    try {
-                        // To do regex matching, we have to convert from UTF-8.
-                        RegularExpression re(w);
-                        match = re.matches(ref);
+                    // Figure out if there's a match.
+                    bool match = false;
+                    if (regexp) {
+                        try {
+                            // To do regex matching, we have to convert from UTF-8.
+                            RegularExpression re(w);
+                            match = re.matches(ref);
+                        }
+                        catch (XMLException& ex) {
+                            auto_ptr_char tmp(ex.getMessage());
+                            request.log(SPRequest::SPError,
+                                string("htaccess plugin caught exception while parsing regular expression (") + w + "): " + tmp.get());
+                        }
                     }
-                    catch (XMLException& ex) {
-                        auto_ptr_char tmp(ex.getMessage());
-                        request.log(SPRequest::SPError,
-                            string("htaccess plugin caught exception while parsing regular expression (") + w + "): " + tmp.get());
+                    else if (!strcmp(w,ref)) {
+                        match = true;
                     }
-                }
-                else if (!strcmp(w,ref)) {
-                    match = true;
-                }
 
-                if (match) {
-                    // If we matched, then we're done with this rule either way and status is set to reflect the outcome.
-                    status = !negate;
-                    if (request.isPriorityEnabled(SPRequest::SPDebug))
-                        request.log(SPRequest::SPDebug,
-                            string("htaccess: require authnContext ") + (negate ? "rejecting (" : "accepting (") + ref + ")");
-                    break;
+                    if (match) {
+                        // If we matched, then we're done with this rule either way and we flip status to reflect the outcome.
+                        status = !status;
+                        if (request.isPriorityEnabled(SPRequest::SPDebug))
+                            request.log(SPRequest::SPDebug,
+                                string("htaccess: require authnContext ") + (!status ? "rejecting (" : "accepting (") + ref + ")");
+                        break;
+                    }
                 }
             }
+            else if (request.isPriorityEnabled(SPRequest::SPDebug)) {
+                request.log(SPRequest::SPDebug, "htaccess: require authnContext rejecting session with no context associated");
+            }
         }
         else if (!session) {
             request.log(SPRequest::SPError, string("htaccess: require ") + w + " not given a valid session, are you using lazy sessions?");
@@ -1228,7 +1294,7 @@ AccessControl::aclresult_t htAccessControl::authorized(const SPRequest& request,
 // Initial look at a request - create the per-request structure
 static int shib_post_read(request_rec *r)
 {
-    shib_request_config* rc = init_request_config(r);
+    init_request_config(r);
     //ap_log_rerror(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,SH_AP_R(r), "shib_post_read");
     return DECLINED;
 }
@@ -1324,7 +1390,7 @@ extern "C" void shib_child_init(apr_pool_t* p, server_rec* s)
             throw runtime_error("unknown error");
     }
     catch (exception& ex) {
-        ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,SH_AP_R(s),ex.what());
+        ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,SH_AP_R(s),"%s",ex.what());
         ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,SH_AP_R(s),"shib_child_init() failed to load configuration");
         exit(1);
     }
@@ -1431,6 +1497,10 @@ static command_rec shire_cmds[] = {
   {"ShibRequestSetting", (config_fn_t)shib_table_set, nullptr,
    OR_AUTHCFG, TAKE2, "Set arbitrary Shibboleth request property for content"},
 
+  {"ShibAccessControl", (config_fn_t)ap_set_string_slot,
+   (void *) XtOffsetOf (shib_dir_config, szAccessControl),
+   OR_AUTHCFG, TAKE1, "Set arbitrary Shibboleth access control plugin for content"},
+
   {"ShibDisable", (config_fn_t)ap_set_flag_slot,
    (void *) XtOffsetOf (shib_dir_config, bOff),
    OR_AUTHCFG, FLAG, "Disable all Shib module activity here to save processing effort"},
@@ -1536,6 +1606,8 @@ static command_rec shib_cmds[] = {
         RSRC_CONF, "Path to shibboleth2.xml config file"),
     AP_INIT_TAKE1("ShibCatalogs", (config_fn_t)ap_set_global_string_slot, &g_szSchemaDir,
         RSRC_CONF, "Paths of XML schema catalogs"),
+    AP_INIT_TAKE1("ShibGSSKey", (config_fn_t)ap_set_global_string_slot, &g_szGSSContextKey,
+        RSRC_CONF, "Name of user data key containing GSS context established by GSS module"),
 
     AP_INIT_TAKE1("ShibURLScheme", (config_fn_t)shib_set_server_string_slot,
         (void *) offsetof (shib_server_config, szScheme),
@@ -1544,6 +1616,10 @@ static command_rec shib_cmds[] = {
     AP_INIT_TAKE2("ShibRequestSetting", (config_fn_t)shib_table_set, nullptr,
         OR_AUTHCFG, "Set arbitrary Shibboleth request property for content"),
 
+    AP_INIT_TAKE1("ShibAccessControl", (config_fn_t)ap_set_string_slot,
+        (void *) offsetof (shib_dir_config, szAccessControl),
+        OR_AUTHCFG, "Set arbitrary Shibboleth access control plugin for content"),
+
     AP_INIT_FLAG("ShibDisable", (config_fn_t)ap_set_flag_slot,
         (void *) offsetof (shib_dir_config, bOff),
         OR_AUTHCFG, "Disable all Shib module activity here to save processing effort"),