/**
 * SigFW
 * Open Source SS7/Diameter firewall
 * By Martin Kacer, Philippe Langlois
 * Copyright 2017, P1 Security S.A.S and individual contributors
 * 
 * See the AUTHORS in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */
package sigfw.connectorIDS;

import java.net.URI;
import java.net.URISyntaxException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;


/**
 * Connector to IDS module implementing REST.
 * 
 * @author Martin Kacer
 */
public class ConnectorIDSModuleRest implements ConnectorIDSModuleInterface {
    private static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(ConnectorIDSModuleRest.class);
    List<Client> serverList = new ArrayList();
    List<WebTarget> serverTargetsList = new ArrayList();
    HashMap<Integer, Integer> serverBackoffAttempts = new HashMap<Integer, Integer>();
    private static final int SERVER_BACKOFFATTEMPTS = 1000;
    Random randomGenerator = new Random();
    
    /**
     * Create jersey client. 
     * Used to do not validate certificates or for other SSL/TLS options.
     * 
     */
    protected static Client createClient() {
        TrustManager[] certs = new TrustManager[]{
            new X509TrustManager() {
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                }

                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                }
            }
        };
        SSLContext ctx = null;
        try {
            ctx = SSLContext.getInstance("TLS");
            ctx.init(null, certs, new SecureRandom());
        } catch (java.security.GeneralSecurityException ex) {
        }
        HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory());
        
        Client client = ClientBuilder.newBuilder()
        .sslContext(ctx)
        .hostnameVerifier(
            new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            })
        .build();
        
        return client;
    }
    
    /**
     * Add IDS server
     * 
     * @param url url address of IDS server
     * @param username username for IDS server
     * @param password password for IDS server
     * @return true if successful
     */
    public boolean addServer(String url, String username, String password) {
        Client client = createClient();
        serverList.add(client);

        HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic(username, password);
        client.register(feature);
        
        //WebTarget target = client.target(url).path("ss7fw_api/1.0/eval_sccp_message_in_ids");
        WebTarget target = client.target(url);
        serverTargetsList.add(target);
        
        return true;
    }

    /**
     * Remove IDS server
     * 
     * @param url url address of IDS server
     * @return true if successful
     */
    public boolean removeServer(String url) {
        for (int i = 0; i < serverTargetsList.size(); i++) {
            try {
                if (serverTargetsList.get(i).getUri().getHost().equals(new URI(url).getHost()) ) {
                    serverTargetsList.remove(i);
                    Client client = serverList.get(i);
                    if (client != null) {
                        client.close();
                    }
                    serverList.remove(i);
                    return true;
                }
            } catch (URISyntaxException ex) {
                Logger.getLogger(ConnectorIDSModuleRest.class.getName()).log(Level.SEVERE, null, ex);
                return false;
            }
        }
        return false;
    }

    /**
     * Evaluate SCCP message towards IDS server.
     * 
     * @param sccp_raw SCCP hex raw payload of message
     * @return true if message is valid and false if message should be filtered
     */
    public boolean evalSCCPMessage(String sccp_raw) {
        int attempts = serverList.size();
        
        Response response = null;
        String output = "1";
        
        int i = randomGenerator.nextInt(serverList.size());
        
        do {
            if (serverBackoffAttempts.get(i) == null || serverBackoffAttempts.get(i).intValue() <= 0) {
                try {
                    WebTarget webResourceWithQueryParam = serverTargetsList.get(i).matrixParam("sccp_raw", sccp_raw);
                    response = webResourceWithQueryParam.request("text/plain").get();
                    if (response.getStatus() == 200) {
                        output = response.readEntity(String.class);
                        logger.debug("evalSCCPMessage " + webResourceWithQueryParam + " Response: " + output);
                        break;
                    } else {
                        logger.warn("Connection failed for IDS API: HTTP error code : " + response.getStatus() + " for " + serverTargetsList.get(i));
                    }
                } catch (Exception e) {
                    serverBackoffAttempts.put(i, SERVER_BACKOFFATTEMPTS);
                    logger.warn("Connection failed for IDS API: " + serverTargetsList.get(i) + " " + e.toString());
                }
            } else {
                if (serverBackoffAttempts.get(i) != null) {
                    serverBackoffAttempts.put(i, serverBackoffAttempts.get(i).intValue() - 1);
                }
                i = randomGenerator.nextInt(serverList.size());
            }
            attempts--;
        } while (attempts > 0);

        
        return output.equals("1");
    }
    
    /**
     * Evaluate Diameter message towards IDS server.
     * 
     * @param diameter_raw Diameter hex raw payload of message
     * @return true if message is valid and false if message should be filtered
     */
    public boolean evalDiameterMessage(String diameter_raw) {
        int attempts = serverList.size();
        
        Response response = null;
        String output = "1";
        
        int i = randomGenerator.nextInt(serverList.size());
        
        do {
            if (serverBackoffAttempts.get(i) == null || serverBackoffAttempts.get(i).intValue() <= 0) {
                try {
                    WebTarget webResourceWithQueryParam = serverTargetsList.get(i).matrixParam("diameter_raw", diameter_raw);
                    response = webResourceWithQueryParam.request("text/plain").get();
                    if (response.getStatus() == 200) {
                        output = response.readEntity(String.class);
                        logger.debug("evalDiameterMessage " + webResourceWithQueryParam + " Response: " + output);
                        break;
                    } else {
                        logger.warn("Connection failed for IDS API: HTTP error code : " + response.getStatus() + " for " + serverTargetsList.get(i));
                    }
                } catch (Exception e) {
                    serverBackoffAttempts.put(i, SERVER_BACKOFFATTEMPTS);
                    logger.warn("Connection failed for IDS API: " + serverTargetsList.get(i) + " " + e.toString());
                }
            } else {
                if (serverBackoffAttempts.get(i) != null) {
                    serverBackoffAttempts.put(i, serverBackoffAttempts.get(i).intValue() - 1);
                }
                i = randomGenerator.nextInt(serverList.size());
            }
            attempts--;
        } while (attempts > 0);

        
        return output.equals("1");
    }
    
}