/* Waf Service Provider Library for Shibboleth 
   Amit Thukral
*/

#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;

/*Forward Declaration*/
class ShibTargetWaf;
 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;
 
 
// per-request module structure
struct shib_request_config
{
    //SH_AP_TABLE* env;        // environment vars
#ifdef SHIB_DEFERRED_HEADERS
    //SH_AP_TABLE* hdr_out;    // headers to browser
#endif
#ifndef SHIB_APACHE_13
    ShibTargetWaf* stw;  // SP per-request structure wrapped around Apache's request
#endif
};

#if 0
class ShibTargetFCGIAuth : public AbstractSPRequest
{
    FCGX_Request* m_req;
    int m_port;
    string m_scheme,m_hostname;
    multimap<string,string> m_response_headers;
public:
    map<string,string> m_request_headers;

    ShibTargetFCGIAuth(FCGX_Request* req, const char* scheme=nullptr, const char* hostname=nullptr, int port=0)
            : AbstractSPRequest(SHIBSP_LOGCAT".FastCGI"), m_req(req) {
        const char* server_name_str = hostname;
        if (!server_name_str || !*server_name_str)
            server_name_str = FCGX_GetParam("SERVER_NAME", req->envp);
        m_hostname = server_name_str;

        m_port = port;
        if (!m_port) {
            char* server_port_str = FCGX_GetParam("SERVER_PORT", req->envp);
            m_port = strtol(server_port_str, &server_port_str, 10);
            if (*server_port_str) {
                cerr << "can't parse SERVER_PORT (" << FCGX_GetParam("SERVER_PORT", req->envp) << ")" << endl;
                throw runtime_error("Unable to determine server port.");
            }
        }

        const char* server_scheme_str = scheme;
        if (!server_scheme_str || !*server_scheme_str)
            server_scheme_str = (m_port == 443 || m_port == 8443) ? "https" : "http";
        m_scheme = server_scheme_str;

        setRequestURI(FCGX_GetParam("REQUEST_URI", m_req->envp));
    }

    ~ShibTargetFCGIAuth() { }

    const char* getScheme() const {
        return m_scheme.c_str();
    }
 
};
#endif
struct shib_server_config
{
    char* szScheme;
};

// per-dir module configuration structure
struct shib_dir_config
{
    //SH_AP_TABLE* tSettings; // generic table of extensible settings

    // RM Configuration
#ifdef SHIB_APACHE_24
    int bRequestMapperAuthz;// support RequestMapper AccessControl plugins
#else
    char* szAuthGrpFile;    // Auth GroupFile name
	char* szAccessControl;	// path to "external" AccessControl plugin file
    int bRequireAll;        // all "known" require directives must match, otherwise OR logic
    int bAuthoritative;     // allow htaccess plugin to DECLINE when authz fails
    int bCompatWith24;      // support 2.4-reserved require logic for compatibility
#endif

    // Content Configuration
    char* szApplicationId;  // Shib applicationId value
    char* szRequireWith;    // require a session using a specific initiator?
    char* szRedirectToSSL;  // redirect non-SSL requests to SSL port
    int bOff;               // flat-out disable all Shib processing
    int bBasicHijack;       // activate for AuthType Basic?
    int bRequireSession;    // require a session?
    int bExportAssertion;   // export SAML assertion to the environment?
    int bUseEnvVars;        // use environment?
    int bUseHeaders;        // use headers?
    int bExpireRedirects;   // expire redirects?
};

class ShibTargetWaf : public AbstractSPRequest
{
  mutable string m_body;
  mutable bool m_gotBody, m_firsttime;
  mutable vector<string> m_certs;
  set<string> m_allhttp;

public:
  bool m_handler;
  WafSpAgentLoginArg_t* m_req;
  shib_dir_config* m_dc;
  shib_server_config* m_sc;
  shib_request_config* m_rc;
  //Samlsp_AgentApi_Authenticate();
  ShibTargetWaf(WafSpAgentLoginArg_t* req) : AbstractSPRequest(SHIBSP_LOGCAT".Apache"),
        m_gotBody(false), m_firsttime(true),
        m_handler(false), m_dc(nullptr), m_sc(nullptr), m_rc(nullptr) {
	m_req = req;
   	//setRequestURI(req->mpProtUri);
   	setRequestURI("/secure/");
  }
  virtual ~ShibTargetWaf() {
  }
#if 0  
 void shib_child_init()
  {
       //g_ServerName = "fsweb.cuda.barracuda.com"; 
	 
	g_Config=&SPConfig::getConfig();
    	g_Config->setFeatures(
        SPConfig::Listener |
        SPConfig::Caching |
        SPConfig::RequestMapping |
        SPConfig::InProcess |
        SPConfig::Logging |
        SPConfig::Handlers
        );

   /*
	    //setRequestURI(m_req->mpProtUri);
    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);
    }

     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;
    }

	    return true;
  }
#endif
  
  bool init(bool handler, bool check_user)
  {
	    return true;
  }
  const char* getScheme() const {
  	const char* scheme = "HTTPS";
    return scheme;
  }
  const char* getHostname() const {
  	const char* hostname = "fsweb.waf.cuda.com";
      //return ap_get_server_name_for_url(m_req);
      return hostname;
  }
  int getPort() const {
   int port=443;
//    return ap_get_server_port(m_req);
return port;
  }
  const char* getMethod() const {
    //return m_req->method;
  }

  string getContentType() const {
    //const char* type = ap_table_get(m_req->headers_in, "Content-Type");
    //return type ? type : "";
  }
  long getContentLength() const {
      //return m_gotBody ? m_body.length() : m_req->remaining;
  }
  string getRemoteAddr() const {
    //string ret = AbstractSPRequest::getRemoteAddr();
    //if (!ret.empty())
      //  return ret;
    //return m_req->useragent_ip;
  }
 const char* getQueryString() const { //return m_req->args;
 }
  const char* getRequestBody() const {
 }
const char* getParameter(const char* name) const {
      //return AbstractSPRequest::getParameter(name);
  }
  vector<const char*>::size_type getParameters(const char* name, vector<const char*>& values) const {
      //return AbstractSPRequest::getParameters(name, values);
  }

void clearHeader(const char* rawname, const char* cginame) {}
void setHeader(const char* name, const char* value) {}
string getHeader(const char* name) const {
    //const char* hdr = ap_table_get(m_req->headers_in, name);
    //return string(hdr ? hdr : "");
  }
  string getSecureHeader(const char* name) const {
    //if (m_dc->bUseEnvVars != 
  }
   void setRemoteUser(const char* user) {}
   string getRemoteUser() const { }
   void setAuthType(const char* authtype) {}
   string getAuthType() const { }
   void setContentType(const char* type) {}
   void setResponseHeader(const char* name, const char* value) {}
   long sendResponse(istream& in, long status) {}
   long sendRedirect(const char* url) {}
   const vector<string>& getClientCertificates() const 
   {
   	//static vector<string> mcerts;
    	return m_certs;
   }
   long returnDecline(void) { 
     //return DECLINED; 
   }
   long returnOK(void) { 
    //return OK; 
   }
};
 bool shib_child_init()
  {
       //g_ServerName = "fsweb.cuda.barracuda.com"; 
	 
	g_Config=&SPConfig::getConfig();
    	g_Config->setFeatures(
        SPConfig::Listener |
        SPConfig::Caching |
        SPConfig::RequestMapping |
        SPConfig::InProcess |
        SPConfig::Logging |
        SPConfig::Handlers
        );

   /*
	    //setRequestURI(m_req->mpProtUri);
    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);
    }

   /*  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;
    }
    */

	    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)
{
	//return 1;
    try {

	shib_child_init();

	 string threadid("[");
    	 threadid += lexical_cast<string>(getpid()) + "] shib_check_user";
    	 xmltooling::NDC ndc(threadid.c_str());

      if (!req || (NULL == req->stw)) {
       // req->stw = new ShibTargetWaf(req);
    	}

        //ShibTargetWaf stw(req);
	//ShibTargetWaf* psta = &stw;
        req->stw = new ShibTargetWaf(req);
	ShibTargetWaf* psta = (ShibTargetWaf *)req->stw;

	if (!psta->init(false, true)) {
           // ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, SH_AP_R(r), "shib_check_user unable to initialize SP request object");
            //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 OK;
    }
    catch (...) {
    }

}