/* info/policy communicating routines
 * Copyright (C) 2003       Michael Richardson <mcr@freeswan.org>
 * Copyright (C) 2004       Xelerance Corporation
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * 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 General Public License
 * for more details.
 */

#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <resolv.h>

#include <openswan.h>

#include "sysdep.h"
#include "constants.h"
#include "defs.h"
#include "id.h"
#include "connections.h"	/* needs id.h */
#include "foodgroups.h"
#include "whack.h"	/* needs connections.h */
#include "packet.h"
#include "demux.h"	/* needs packet.h */
#include "state.h"
#include "ipsec_doi.h"	/* needs demux.h and state.h */
#include "kernel.h"	/* needs connections.h */
#include "rcv_whack.h"
#include "log.h"
#include "keys.h"
#include "adns.h"	/* needs <resolv.h> */
#include "dnskey.h"	/* needs keys.h and adns.h */
#include "server.h"

#include "openswan/ipsec_policy.h"
#include "rcv_info.h"

/* global */
int info_fd = -1;

/** Lookup information about the hostpair, and set things like bandwidth
 * relative crypto strength, compression and credentials.
 * 
 * @param IPsec Policy Query
 * @return void
 */
static void
info_lookuphostpair(struct ipsec_policy_cmd_query *ipcq)
{
    struct connection *c;
    struct state *p1st, *p2st;


    /* default result: no crypto */
    ipcq->strength  = IPSEC_PRIVACY_NONE;
    ipcq->bandwidth = IPSEC_QOS_WIRESPEED;
    ipcq->credential_count = 0;

#ifdef DEBUG
    {
	char sstr[ADDRTOT_BUF], dstr[ADDRTOT_BUF];

	addrtot(&ipcq->query_local,  0, sstr, sizeof(sstr));
	addrtot(&ipcq->query_remote, 0, dstr, sizeof(dstr));
	DBG_log("info request for %s -> %s", sstr, dstr);
    }
#endif

    /* okay, look up what connection handles this ip pair */

    c = find_connection_for_clients(NULL,
				    &ipcq->query_local,
				    &ipcq->query_remote,
				    ipcq->proto);
    if (c == NULL)
    {
	/* try reversing it */
	c = find_connection_for_clients(NULL,
					&ipcq->query_remote,
					&ipcq->query_local,
					ipcq->proto);
	if (c != NULL)
	{
	    ip_address tmp;
	    /* If it is reversed, swap it */
	    tmp = ipcq->query_local;
	    ipcq->query_local = ipcq->query_remote;
	    ipcq->query_remote = tmp;
	}
    }
    
    if (c == NULL)
    {
#ifdef DEBUG
	DBG_log("no connection found");
#endif
	return;	/* no crypto */
    }

    if (c->newest_ipsec_sa == SOS_NOBODY)
    {
	ip_subnet us, them;

	DBG_log("connection %s found, no ipsec state, looking again", c->name);
	addrtosubnet(&ipcq->query_local, &us);
	addrtosubnet(&ipcq->query_remote, &them);
	c = find_client_connection(c, &us, &them, 0, 0, 0, 0);

	if (c == NULL)
	    return;	/* no crypto */
    }

    DBG_log("connection %s[%ld] with state %u"
	, c->name, c->instance_serial
	, (unsigned int)c->newest_ipsec_sa);

    if (c->newest_ipsec_sa == SOS_NOBODY)
	return;	/* no crypto */

    /* we found a connection, try to lookup the state */
    p2st = state_with_serialno(c->newest_ipsec_sa);

    p1st = find_phase1_state(c, ISAKMP_SA_ESTABLISHED_STATES);

    if (p1st == NULL || p2st == NULL)
    {
	DBG_log("connection %s[%ld] has missing states %s %s"
	    , c->name, c->instance_serial
	    , (p1st ? "phase1" : "")
	    , (p2st ? "phase1" : ""));
	return;	/* no crypto */
    }

    /* if we have AH present, then record minimal info */
    if (p2st->st_ah.present)
    {
	ipcq->strength = IPSEC_PRIVACY_INTEGRAL;
	ipcq->auth_detail = p2st->st_esp.attrs.transattrs.integ_hash;
    }

    if (p2st->st_esp.present)
    {
	/*
	 * XXX-mcr Please do not shout at me about relative strengths
	 *         here. I'm not a cryptographer. I just diddle bits.
	 */
	switch (p2st->st_esp.attrs.transattrs.encrypt)
	{
	case ESP_NULL:
	    /* actually, do not change it if we set it from AH */
	    break;

	case ESP_DES:
	case ESP_DES_IV64:
	case ESP_DES_IV32:
	case ESP_RC4:
	    ipcq->strength = IPSEC_PRIVACY_ROT13;
	    break;
	    
	case ESP_RC5:
	case ESP_IDEA:
	case ESP_CAST:
	case ESP_BLOWFISH:
	case ESP_3DES:
	    ipcq->strength = IPSEC_PRIVACY_PRIVATE;
	    ipcq->bandwidth = IPSEC_QOS_VOIP;
	    break;

	case ESP_3IDEA:
	    ipcq->strength = IPSEC_PRIVACY_STRONG;
	    ipcq->bandwidth = IPSEC_QOS_INTERACTIVE;
	    break;

	case ESP_AES:
	    ipcq->strength = IPSEC_PRIVACY_STRONG;
	    ipcq->bandwidth = IPSEC_QOS_FTP;
	    break;
	default:
		DBG_log("unsupported/unhandled ESP transform '%s' (%d)",
			enum_name(&esp_transformid_names, p2st->st_esp.attrs.transattrs.encrypt),
			p2st->st_esp.attrs.transattrs.encrypt);
	}
	ipcq->esp_detail = p2st->st_esp.attrs.transattrs.encrypt;
    }

    if (p2st->st_ipcomp.present)
	ipcq->comp_detail = p2st->st_esp.attrs.transattrs.encrypt;

    /* now! the credentails that were used */
    /* for the moment we only have 1 credential, the DNS name,
     * because the DNS servers do not return the chain of SIGs yet
     */

    if(!c->spd.this.key_from_DNS_on_demand)
    {
	/* the key didn't come from the DNS in some way,
	 * so it must have been loaded locally.
	 */
	ipcq->credential_count = 1;
	ipcq->credentials[0].ii_type   = c->spd.this.id.kind;
	ipcq->credentials[0].ii_format = CERT_RAW_RSA;
    }
	
#if 0
    switch (c->spd.id.kind)
    {
    case ID_IPV4_ADDR:
    }
    if (c->gw_info == NULL)
    {
	plog("rcv_info: connection %s had NULL gw_info.", c->name);
	return
    }
#endif

    ipcq->credential_count = 1;

    /* pull credentials out of gw_info */
    
    switch (p1st->st_peer_pubkey->dns_auth_level)
    {
    case DAL_UNSIGNED:
    case DAL_NOTSEC:
	/* these seem to be the same for this purpose */
	ipcq->credentials[0].ii_type   = p1st->st_peer_pubkey->id.kind;
	ipcq->credentials[0].ii_type   = CERT_NONE;
	idtoa(&p1st->st_peer_pubkey->id
	    , ipcq->credentials[0].ii_credential.ipsec_dns_signed.fqdn
	    , sizeof(ipcq->credentials[0].ii_credential.ipsec_dns_signed.fqdn));
	break;
	
    case DAL_SIGNED:
	ipcq->credentials[0].ii_type   = p1st->st_peer_pubkey->id.kind;
	ipcq->credentials[0].ii_format = CERT_DNS_SIGNED_KEY;
	idtoa(&p1st->st_peer_pubkey->id
	    , ipcq->credentials[0].ii_credential.ipsec_dns_signed.fqdn
	    , sizeof(ipcq->credentials[0].ii_credential.ipsec_dns_signed.fqdn));

	if (p1st->st_peer_pubkey->dns_sig != NULL)
	{
	    strncat(ipcq->credentials[0].ii_credential.ipsec_dns_signed.dns_sig
		, p1st->st_peer_pubkey->dns_sig
		, sizeof(ipcq->credentials[0].ii_credential.ipsec_dns_signed.dns_sig)
		- strlen(ipcq->credentials[0].ii_credential.ipsec_dns_signed.dns_sig)-1);
	}
	break;

    case DAL_LOCAL:
	ipcq->credentials[0].ii_type   = p1st->st_peer_pubkey->id.kind;
	ipcq->credentials[0].ii_format = CERT_RAW_RSA;
	idtoa(&p1st->st_peer_pubkey->id
	    , ipcq->credentials[0].ii_credential.ipsec_raw_key.id_name
	    , sizeof(ipcq->credentials[0].ii_credential.ipsec_raw_key.id_name));
	break;
    }
}

/**
 * Handle an info/policy request. 
 *
 * For now, we close the socket after answering the request.
 * @param infoctlfd File Descriptor for socket communication
 */
void
info_handle(int infoctlfd)
{
	struct sockaddr_un info_client_addr;
	unsigned int info_addr_len = sizeof(info_client_addr);
	/* Note: actual value in n should fit in int.  To print, cast to int. */
	int infofd;
	err_t err;
	struct ipsec_policy_cmd_query ipcq;
	unsigned long fcntl_arg;

	infofd = accept(infoctlfd, (struct sockaddr *)&info_client_addr
			, &info_addr_len);

	if (infofd < 0)
	{
	    log_errno((e, "accept() failed in info_handle()"));
	    return;
	}

	err = ipsec_policy_readmsg(infofd, (unsigned char *)&ipcq, sizeof(ipcq));

	if (err != NULL)
	{
	    log_errno((e, "readmsg said: %s", err));
	    close(infofd);
	    return;
	}

	/*
	 * set CLOEXEC it to suppress selenux avc denials on exec
	 */
	fcntl_arg = fcntl(infofd, F_GETFD);
	fcntl_arg |= FD_CLOEXEC;
	fcntl(infofd, F_SETFD, fcntl_arg);

	switch (ipcq.head.ipm_msg_type)
	{
	case IPSEC_CMD_QUERY_HOSTPAIR:
	    info_lookuphostpair(&ipcq);
	    if(write(infofd, &ipcq, ipcq.head.ipm_msg_len) == -1 ) {
            plog("info_handle: write error");
	    }
	    break;
	    
	default:
	    plog("got unimplemented msg type: %d", ipcq.head.ipm_msg_type);
	    break;
	}

	/* for now, close the socket */
	close(infofd);
}
