/* Waf Service Provider Library for Shibboleth 
   Amit Thukral
*/
#define SHIBSP_LITE
#include <shibsp/exceptions.h>
#include <shibsp/AbstractSPRequest.h>
#include <shibsp/AccessControl.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>
#include <boost/lexical_cast.hpp>

#include <stdexcept>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
# include <sys/mman.h>
#endif
//#include <fcgio.h>
#include <wafsp.h>
//#include <mod_auth.h>


#define OK 1


using namespace shibsp;
using namespace xmltooling;
using namespace boost;
using namespace std;
using xercesc::RegularExpression;
using xercesc::XMLException;


static const XMLCh path[] =     UNICODE_LITERAL_4(p,a,t,h);
static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);

typedef enum {
    SHIB_RETURN_OK,
    SHIB_RETURN_KO,
    SHIB_RETURN_DONE
} shib_return_t;

 SPConfig* g_Config=nullptr;
 char *g_ServerName;
 char *g_szPrefix = "/home/product/code/firmware/current";
 //char* g_szSchemaDir = "/home/product/code/firmware/current/share/xml";

 //char *g_szPrefix = nullptr;
 char* g_szSchemaDir = nullptr;
 //char* g_szSHIBConfig = "/home/product/code/firmware/current/etc/shibboleth";
 char* g_szSHIBConfig = nullptr;
 string g_unsetHeaderValue,g_spoofKey;
 bool g_checkSpoofing = true;
 bool g_catchAll = false;
 
 
class ShibTargetWaf : public AbstractSPRequest
{
  mutable string m_body;
  mutable bool m_gotBody, m_firsttime;
  mutable vector<string> m_certs;
  set<string> m_allhttp;
  int m_port;
  string m_scheme,m_hostname;
  mutable string m_remote_addr,m_content_type,m_method;
  string defHostname;
  
public:
  bool m_handler;
  WafSpAgentLoginArg_t* m_req;
  //Samlsp_AgentApi_Authenticate();
  ShibTargetWaf(WafSpAgentLoginArg_t* req) : AbstractSPRequest(SHIBSP_LOGCAT".ISAPI"),
        m_gotBody(false), m_firsttime(true),
        m_handler(false){
	m_req = req;
   	//setRequestURI(req->mpProtUri);
   	setRequestURI("/secure/");
	defHostname = "saml62-sp.barracuda.com";

      	 //m_port = req->isSecurePort ? 443 : 80;
	 //m_scheme = req->isSecurePort ? "https" : "http";
      	 m_port = 443;
	 m_scheme = "https";
 	 //m_hostname = req->hostname;
	 m_hostname = "saml62-sp.barracuda.com";
	 /*we can get the hostname details like name,scheme, port,sslport etc. from xml file*/
      /*  if (site.m_name != m_hostname && site.m_aliases.find(m_hostname) == site.m_aliases.end())
           m_hostname = site.m_name;*/

     /*
	 if (!g_spoofKey.empty()) {
        GetHeader("ShibSpoofCheck:", var, 32, false);
        if (!var.empty() && g_spoofKey == (char*)var)
            m_firsttime = false;
    	 }
	*/

      if (!m_firsttime)
       log(SPDebug, "WAFSP running more than once");
	
  }
  virtual ~ShibTargetWaf() {
  }

  bool init(bool handler, bool check_user)
  {
	    return true;
  }
  const char* getScheme() const {
    return m_scheme.c_str();
  }
  const char* getHostname() const {
       //return ap_get_server_name_for_url(m_req);
	//if(m_hostname.empty())
		//m_hostname = defHostname;
      return m_hostname.c_str();
  }
  int getPort() const {
	return m_port;
  }
  const char* getMethod() const {
  	/*could be GET POST LOGIN etc */
  	 if (m_req->mpProtMethod)
	 	m_method = m_req->mpProtMethod;
    return m_method.c_str();
  }

  string getContentType() const {

    /*Could be HTTP Redirect, HTTP POST, HTTP ARTIFACT"*/	
	if(m_req->contentType)
  	    m_content_type =m_req->contentType;
	return (!m_content_type.empty() ? m_content_type.c_str() : "HTTP POST");
  }
  long getContentLength() const {
  	
      return m_req->mProtUriLen;
  }
  string getRemoteAddr() const {
    /*string ret = AbstractSPRequest::getRemoteAddr();
    if (!ret.empty())
        return ret;*/
        string clientIp = m_req->mClientIp;
        log(SPDebug, "getRemoteAddr called.");
    return clientIp;
  }
 const char* getQueryString() const { 
 	 log(SPDebug, "getQueryString called.");
 	return m_req->mpProtUri;
 }
  const char* getRequestBody() const {
  	log(SPDebug, "getRequestBody called");
 }
const char* getParameter(const char* name) const {
	log(SPDebug, "Filter shut down...");
      //return AbstractSPRequest::getParameter(name);
  }
  vector<const char*>::size_type getParameters(const char* name, vector<const char*>& values) const {
  	log(SPDebug, "getParameter called.");
      //return AbstractSPRequest::getParameters(name, values);
  }


#if 0
  void log(SPLogLevel level, const string& msg) const {
      AbstractSPRequest::log(level,msg);
      //if (level >= SPCrit)
          //LogEvent(nullptr, EVENTLOG_ERROR_TYPE, 2100, nullptr, msg.c_str());
  }
#endif

void clearHeader(const char* rawname, const char* cginame) {
	log(SPDebug, "clearHeader  called");
}
void setHeader(const char* name, const char* value) {
 	log(SPDebug, "setHeader called");

}
string getHeader(const char* name) const {
    //const char* hdr = ap_table_get(m_req->headers_in, name);
    //return string(hdr ? hdr : "");
    log(SPDebug, "getHeader called");
  }
  string getSecureHeader(const char* name) const {
    //if (m_dc->bUseEnvVars != 
    log(SPDebug, "getSecureHeader called.");
  }
   void setRemoteUser(const char* user) {

	log(SPDebug, "setRemoteUser called.");
	}
   string getRemoteUser() const { 
	log(SPDebug, "getRemoteUser  called");	

	}
   void setAuthType(const char* authtype) {
	log(SPDebug, "setAuthType called.");

	}
   string getAuthType() const { 
	log(SPDebug, "getAuthType shut down...");

	}
   void setContentType(const char* type) {
	log(SPDebug, "setContentType shut down...");

	}
   void setResponseHeader(const char* name, const char* value) {

	log(SPDebug, "setResponseHeader shut down...");
	}
   long sendResponse(istream& in, long status) {
	log(SPDebug, "sendResponse shut down...");
	}
   long sendRedirect(const char* url) {
	log(SPDebug, "sendRedirect shut down...");
	}
   const vector<string>& getClientCertificates() const 
   {
   	//static vector<string> mcerts;
    	//return m_certs;
   }
   long returnDecline(void) { 
     //return DECLINED; 
   }
   long returnOK(void) { 
    //return OK; 
   }
};


 int shib_child_init()
  {
	 
	g_Config=&SPConfig::getConfig();
    	g_Config->setFeatures(
        SPConfig::Listener |
        SPConfig::Caching |
        SPConfig::RequestMapping |
        SPConfig::InProcess |
        SPConfig::Logging |
        SPConfig::Handlers
        );

    if (!g_Config->init(g_szSchemaDir, g_szPrefix)) {
        //ap_log_error(APLOG_MARK, APLOG_CRIT|APLOG_NOERRNO, SH_AP_R(s), "child_init: mod_shib failed to initialize libraries");
        exit(1);
    }
    //g_Config->AccessControlManager.registerFactory(HT_ACCESS_CONTROL, &htAccessFactory);
    //g_Config->RequestMapperManager.registerFactory(NATIVE_REQUEST_MAPPER, &ApacheRequestMapFactory);


    // The config gets installed for all versions here due to the background thread/fork issues.
    try {
        if (!g_Config->instantiate(g_szSHIBConfig, true))
            throw runtime_error("unknown error");
    }
    catch (std::exception& ex) {
        //ap_log_error(APLOG_MARK, APLOG_CRIT|APLOG_NOERRNO, SH_AP_R(s), "child_init: mod_shib failed to load configuration: %s", ex.what());
        g_Config->term();
        exit(1);
    }
   
	    return true;
  }
// Performs authentication and enforce session requirements.
// Also does header/env export from session, and will dispatch
// SP handler requests if it detects a handler URL.
int shib_check_user(WafSpAgentLoginArg_t* req,
		    char *targetURL)
{

    try {

	string threadid("[");
    	threadid += lexical_cast<string>(getpid()) + "] shib_check_user";
    	xmltooling::NDC ndc(threadid.c_str());
	ShibTargetWaf* psta; 
	ServiceProvider* sp = g_Config->getServiceProvider();
    	xmltooling::Locker locker(sp);
    	const PropertySet* props = sp->getPropertySet("InProcess");
    	if (props) {
        	pair<bool,const char*> unsetValue = props->getString("unsetHeaderValue");
        	if (unsetValue.first)
            	  g_unsetHeaderValue = unsetValue.second;
        	pair<bool,bool> flag=props->getBool("checkSpoofing");
        	g_checkSpoofing = !flag.first || flag.second;
        	if (g_checkSpoofing) {
            	unsetValue=props->getString("spoofKey");
            	if (unsetValue.first)
                	g_spoofKey = unsetValue.second;
        	}
        flag=props->getBool("catchAll");
        g_catchAll = flag.first && flag.second;
    }
 
      //if (NULL == req->stw) {
       	  psta = new ShibTargetWaf(req);
	  req->stw = psta;
      //}

     	//Session* session = psta->getSession(false, true, false);
	 //Locker slocker(session, false);
        //if (session) {
            //psta.first->log(SPRequest::SPDebug, "accepting valid-user based on active session");
            //return D;
        //}
		  
	if (!psta->init(false, true)) {
            //return SERVER_ERROR;
        }
    	
	pair<bool,long> res = psta->getServiceProvider().doAuthentication(*psta, true);
	if (res.second == OK )
	{
            //    r->user = "";
	}
	// user auth was okay -- export the session data now
        res = psta->getServiceProvider().doExport(*psta);
        if (res.first) {
		return res.second;
        }
	return REDIRECT_TO_IDP;
    }
    catch (...) {
    }

}