https://issues.shibboleth.net/jira/browse/SSPCPP-255
[shibboleth/cpp-sp.git] / nsapi_shib / nsapi_shib.cpp
index c986ef0..ef97932 100644 (file)
@@ -1,6 +1,6 @@
 /*
- *  Copyright 2001-2005 Internet2
- * 
+ *  Copyright 2001-2009 Internet2
+ *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
@@ -65,8 +65,8 @@ using namespace shibtarget;
 namespace {
     ShibTargetConfig* g_Config=NULL;
     string g_ServerName;
-    string g_ServerScheme;
     string g_unsetHeaderValue;
+    set<string> g_allowedSchemes;
     bool g_checkSpoofing = false;
     bool g_catchAll = true;
 }
@@ -102,9 +102,6 @@ extern "C" NSAPI_PUBLIC int nsapi_shib_init(pblock* pb, Session* sn, Request* rq
             }
         }
     }
-    name=pblock_findval("server-scheme",pb);
-    if (name)
-        g_ServerScheme=name;
 
     log_error(LOG_INFORM,"nsapi_shib_init",sn,rq,"nsapi_shib loaded for host (%s)",g_ServerName.c_str());
 
@@ -150,14 +147,32 @@ extern "C" NSAPI_PUBLIC int nsapi_shib_init(pblock* pb, Session* sn, Request* rq
         Locker locker(conf);
         const IPropertySet* props=conf->getPropertySet("Local");
         if (props) {
-            pair<bool,const char*> unsetValue=props->getString("unsetHeaderValue");
-            if (unsetValue.first)
-                g_unsetHeaderValue = unsetValue.second;
+            pair<bool,const char*> str=props->getString("unsetHeaderValue");
+            if (str.first)
+                g_unsetHeaderValue = str.second;
+
+            str=props->getString("allowedSchemes");
+            if (str.first) {
+                string schemes=str.second;
+                unsigned int j=0;
+                for (unsigned int i=0;  i < schemes.length();  i++) {
+                    if (schemes.at(i)==' ') {
+                        g_allowedSchemes.insert(schemes.substr(j, i-j));
+                        j = i+1;
+                    }
+                }
+                g_allowedSchemes.insert(schemes.substr(j, schemes.length()-j));
+            }
+
             pair<bool,bool> flag=props->getBool("checkSpoofing");
             g_checkSpoofing = !flag.first || flag.second;
             flag=props->getBool("catchAll");
             g_catchAll = !flag.first || flag.second;
         }
+        if (g_allowedSchemes.empty()) {
+            g_allowedSchemes.insert("https");
+            g_allowedSchemes.insert("http");
+        }
     }
     catch (exception&) {
         g_Config=NULL;
@@ -172,13 +187,44 @@ extern "C" NSAPI_PUBLIC int nsapi_shib_init(pblock* pb, Session* sn, Request* rq
 
 class ShibTargetNSAPI : public ShibTarget
 {
+  void checkString(const string& s, const char* msg) {
+    string::const_iterator e = s.end();
+    for (string::const_iterator i=s.begin(); i!=e; ++i) {
+        if (iscntrl(*i))
+            throw FatalProfileException(msg);
+    }
+  }
+
 public:
   ShibTargetNSAPI(pblock* pb, Session* sn, Request* rq) : m_pb(pb), m_sn(sn), m_rq(rq), m_firsttime(true) {
-    // Get everything but hostname...
+
+      // To determine whether SSL is active or not, we're supposed to rely
+      // on the security_active macro. For iPlanet 4.x, this works.
+      // For Sun 7.x, it's useless and appears to be on or off based
+      // on whether ANY SSL support is enabled for a vhost. Sun 6.x is unknown.
+      // As a fix, there's a conf variable called $security that can be mapped
+      // into a function parameter: security_active="$security"
+      // We check for this parameter, and rely on the macro if it isn't set.
+      // This doubles as a scheme virtualizer for load balanced scenarios
+      // since you can set the parameter to 1 or 0 as needed.
+      const char* scheme;
+      const char* sa = pblock_findval("security_active", pb);
+      if (sa)
+          scheme = (*sa == '1') ? "https" : "http";
+      else if (security_active)
+          scheme = "https";
+      else
+          scheme = "http";
+
+      // A similar issue exists for the port. server_portnum is no longer
+      // working on at least Sun 7.x, and returns the first listener's port
+      // rather than whatever port is actually used for the request. Nice job, Sun.
+      sa = pblock_findval("server_portnum", pb);
+      int port = (sa && *sa) ? atoi(sa) : server_portnum;
+
+    // Get everything else but hostname...
     const char* uri=pblock_findval("uri", rq->reqpb);
     const char* qstr=pblock_findval("query", rq->reqpb);
-    int port=server_portnum;
-    const char* scheme=security_active ? "https" : "http";
     const char* host=NULL;
 
     string url;
@@ -186,7 +232,7 @@ public:
         url=uri;
     if (qstr)
         url=url + '?' + qstr;
-    
+
 #ifdef vs_is_default_vs
     // This is 6.0 or later, so we can distinguish requests to name-based vhosts.
     if (!vs_is_default_vs(request_get_vs(m_rq)))
@@ -200,17 +246,17 @@ public:
 
     char* content_type = "";
     request_header("content-type", &content_type, sn, rq);
-      
+
     const char* remote_ip = pblock_findval("ip", sn->client);
     const char* method = pblock_findval("method", rq->reqpb);
 
     init(scheme, host, port, url.c_str(), content_type, remote_ip, method);
-    
+
     // See if this is the first time we've run.
     method = pblock_findval("auth-type", rq->vars);
     if (method && !strcmp(method, "shibboleth"))
         m_firsttime = false;
-    if (!m_firsttime)
+    if (!m_firsttime || rq->orig_rq)
         log(LogLevelDebug, "nsapi_shib function running more than once");
   }
   ~ShibTargetNSAPI() {
@@ -231,7 +277,7 @@ public:
     string cookie = name + '=' + value;
     pblock_nvinsert("Set-Cookie", cookie.c_str(), m_rq->srvhdrs);
   }
-  virtual string getArgs(void) { 
+  virtual string getArgs(void) {
     const char *q = pblock_findval("query", m_rq->reqpb);
     return string(q ? q : "");
   }
@@ -246,7 +292,7 @@ public:
       string cgistr;
       while (cl && ch != IO_EOF) {
         ch=netbuf_getc(m_sn->inbuf);
-      
+
         // Check for error.
         if(ch==IO_ERROR)
           break;
@@ -259,7 +305,7 @@ public:
     }
   }
   virtual void clearHeader(const string &name) {
-    if (m_firsttime && g_checkSpoofing && m_allhttp.empty()) {
+    if (g_checkSpoofing && m_firsttime && !m_rq->orig_rq && m_allhttp.empty()) {
       // Populate the set of client-supplied headers for spoof checking.
       const pb_entry* entry;
       for (int i=0; i<m_rq->headers->hsize; ++i) {
@@ -277,13 +323,14 @@ public:
       }
     }
     if (name=="REMOTE_USER") {
-        if (m_firsttime && g_checkSpoofing && m_allhttp.count("HTTP_REMOTE_USER") > 0)
+        if (g_checkSpoofing && m_firsttime && !m_rq->orig_rq && m_allhttp.count("HTTP_REMOTE_USER") > 0)
             throw SAMLException("Attempt to spoof header ($1) was detected.", params(1, name.c_str()));
         param_free(pblock_remove("auth-user",m_rq->vars));
         param_free(pblock_remove("remote-user",m_rq->headers));
+        pblock_nvinsert("remote-user", g_unsetHeaderValue.c_str(), m_rq->headers);
     }
     else {
-        if (m_firsttime && g_checkSpoofing) {
+        if (g_checkSpoofing && m_firsttime && !m_rq->orig_rq) {
             // Map to the expected CGI variable name.
             string transformed("HTTP_");
             const char* pch = name.c_str();
@@ -315,6 +362,7 @@ public:
     return string(hdr ? hdr : "");
   }
   virtual void setRemoteUser(const string &user) {
+    param_free(pblock_remove("remote-user",m_rq->headers));
     pblock_nvinsert("remote-user", user.c_str(), m_rq->headers);
     pblock_nvinsert("auth-user", user.c_str(), m_rq->vars);
   }
@@ -329,12 +377,15 @@ public:
     const string& content_type="text/html",
     const saml::Iterator<header_t>& headers=EMPTY(header_t)
     ) {
+    checkString(content_type, "Detected control character in a response header.");
     param_free(pblock_remove("content-type", m_rq->srvhdrs));
     pblock_nvinsert("content-type", content_type.c_str(), m_rq->srvhdrs);
     pblock_nninsert("content-length", msg.length(), m_rq->srvhdrs);
     pblock_nvinsert("connection","close",m_rq->srvhdrs);
     while (headers.hasNext()) {
         const header_t& h=headers.next();
+        checkString(h.first, "Detected control character in a response header.");
+        checkString(h.second, "Detected control character in a response header.");
         pblock_nvinsert(h.first.c_str(), h.second.c_str(), m_rq->srvhdrs);
     }
     protocol_status(m_sn, m_rq, code, NULL);
@@ -343,6 +394,9 @@ public:
     return (void*)REQ_EXIT;
   }
   virtual void* sendRedirect(const string& url) {
+    checkString(url, "Detected control character in an attempted redirect.");
+    if (g_allowedSchemes.find(url.substr(0, url.find(':'))) == g_allowedSchemes.end())
+        throw FatalProfileException("Invalid scheme in attempted redirect.");
     param_free(pblock_remove("content-type", m_rq->srvhdrs));
     pblock_nninsert("content-length", 0, m_rq->srvhdrs);
     pblock_nvinsert("expires", "01-Jan-1997 12:00:00 GMT", m_rq->srvhdrs);
@@ -379,14 +433,14 @@ extern "C" NSAPI_PUBLIC int nsapi_shib(pblock* pb, Session* sn, Request* rq)
     ostringstream threadid;
     threadid << "[" << getpid() << "] nsapi_shib" << '\0';
     saml::NDC ndc(threadid.str().c_str());
-    
+
     try {
         ShibTargetNSAPI stn(pb, sn, rq);
-    
+
         // Check user authentication
         pair<bool,void*> res = stn.doCheckAuthN();
         if (res.first) return (int)res.second;
-    
+
         // user authN was okay -- export the assertions now
         param_free(pblock_remove("auth-user",rq->vars));
         // This seems to be required in order to eventually set
@@ -394,11 +448,11 @@ extern "C" NSAPI_PUBLIC int nsapi_shib(pblock* pb, Session* sn, Request* rq)
         pblock_nvinsert("auth-type","shibboleth",rq->vars);
         res = stn.doExportAssertions();
         if (res.first) return (int)res.second;
-    
+
         // Check the Authorization
         res = stn.doCheckAuthZ();
         if (res.first) return (int)res.second;
-    
+
         // this user is ok.
         return REQ_PROCEED;
     }
@@ -421,13 +475,13 @@ extern "C" NSAPI_PUBLIC int shib_handler(pblock* pb, Session* sn, Request* rq)
     ostringstream threadid;
     threadid << "[" << getpid() << "] shib_handler" << '\0';
     saml::NDC ndc(threadid.str().c_str());
-    
+
     try {
         ShibTargetNSAPI stn(pb, sn, rq);
-    
+
         pair<bool,void*> res = stn.doHandler();
         if (res.first) return (int)res.second;
-    
+
         return WriteClientError(sn, rq, FUNC, "Shibboleth handler did not do anything.");
     }
     catch (exception& e) {
@@ -450,7 +504,7 @@ public:
     void lock() { m_mapper->lock(); }
     void unlock() { m_stKey->setData(NULL); m_propsKey->setData(NULL); m_mapper->unlock(); }
     Settings getSettings(ShibTarget* st) const;
-    
+
     pair<bool,bool> getBool(const char* name, const char* ns=NULL) const;
     pair<bool,const char*> getString(const char* name, const char* ns=NULL) const;
     pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=NULL) const;