#include <string.h>

#include "csink.h"
#include "csinkinet.h"
#include "csinkrend.h"
#include "entity.h"
#include <signal.h>

#ifdef HAVE_OPENSSL
#include "csinkssl.h"
#endif				/* HAVE_OPENSSL */



static void
csinkrend_connect_cb (CSink * sink)
{
    EBuf * func;
    EBuf * remote_ip_address;
    ENode * node;


    EDEBUG (("csinkrend", "By some act of God, I connected!"));

    node = (ENode *) csink_get_user_data (sink);
    EDEBUG (("csinkrend", "sink = %p, node = %p", sink, node));


    /* Make the remote ip into dotted quads and put it in an EBuf */
    remote_ip_address = ebuf_new_with_str
        (inet_ntoa ( *(struct in_addr*) &(CSINK_INET (sink)->ip)) );

    enode_attrib (node, "__remote-ip", remote_ip_address);
    
    func = enode_attrib (node, "onconnect", NULL);

    if (ebuf_not_empty (func)) {
	EDEBUG (("csinkrend", "onconnect found for '%s'",
		enode_path (node)->str));
	enode_call_ignore_return (node, func->str, "");
    }
}


static void
csinkrend_accept_cb (CSink * sink)
{
    EBuf *func;
    EBuf *remote_ip_address;
    ENode *node;
    ENode *newnode;


    EDEBUG (("csinkrend", "By some freaky act of God, I was connected to!"));


    node = (ENode *) csink_get_user_data (sink);
    EDEBUG (("csinkrend", "sink = %p, node = %p", sink, node));

    func = enode_attrib (node, "onconnect", NULL);


    /* Make a new (child) node to hold the (new) csink. */
    newnode = enode_new_child (node, "csink", NULL);

    /* set important attribs */
    enode_set_kv (newnode, "csinkrend-csink", sink);
    /* copy response function attributes from parent */
    /* which are, onconnect, onclose, onnewdata, onerror */
    enode_attrib (newnode, "onconnect", 
	          ebuf_new_with_ebuf (enode_attrib (node, "onconnect", NULL)));
    enode_attrib (newnode, "onclose",
		  ebuf_new_with_ebuf (enode_attrib (node, "onclose", NULL)));
    enode_attrib (newnode, "onnewdata", 
		  ebuf_new_with_ebuf (enode_attrib (node, "onnewdata", NULL)));
    enode_attrib (newnode, "onerror", 
		  ebuf_new_with_ebuf (enode_attrib (node, "onerror", NULL)));

    csink_set_user_data (sink, newnode);

    /* Make the remote ip into dotted quads and put it in an EBuf */
    remote_ip_address = ebuf_new_with_str
        (inet_ntoa ( *(struct in_addr*) &(CSINK_INET (sink)->ip)) );

    EDEBUG (("csinkrend", "Connection found to be from %s",
	   remote_ip_address->str ));

    enode_attrib (newnode, "__remote-ip", remote_ip_address);

    if (ebuf_not_empty (func)) {
	EDEBUG (("csinkrend", "onconnect found for '%s'",
		enode_path (node)->str));
	enode_call_ignore_return (newnode, func->str, "");
    }
    else {
	EDEBUG (("csinkrend", "no onconnect found for '%s'",
		enode_path (node)->str));
    }
}


static void
csinkrend_onnewdata_cb (CSink * sink)
{
    gchar *function;
    EBuf *mesg;
    ENode *node;


    EDEBUG (("csinkrend", "some csink got data."));

    node = (ENode *) csink_get_user_data (sink);
    EDEBUG (("csinkrend", "sink = %p, node = %p", sink, node));
    function = enode_attrib_str (node, "onnewdata", NULL);

    /* Get all of the available messages. */
    EDEBUG(("csinkrend", "getting a message"));
    mesg = csink_read (sink);
    EDEBUG(("csinkrend", "getting a message 2"));
    while (mesg) {
        EDEBUG (("csinkrend", "got mesg: (len %i) %s", mesg->len, mesg->str));

        if (function) {
	    EDEBUG (("csinkrend", "func: %s", function));
	    enode_call_ignore_return (node, function, "ei", mesg, mesg->len);
        }

	ebuf_free (mesg);

        EDEBUG(("csinkrend", "getting another message"));
        mesg = csink_read (sink);
        EDEBUG(("csinkrend", "got another message"));
    }

    EDEBUG(("csinkrend", "leaving on_new_data"));
}


static void
csinkrend_onerror_cb (CSink * sink)
{
    ENode *node;
    gchar *function;

    EDEBUG (("csinkrend", "some csink received an error."));

    node = (ENode *) csink_get_user_data (sink);
    EDEBUG (("csinkrend", "sink = %p, node = %p", sink, node));
    function = enode_attrib_str (node, "onerror", NULL);

    if (function)
	enode_call_ignore_return (node, function, "s", csink_errstr (sink));
}

static void
csinkrend_onclose_cb (CSink * sink)
{
    ENode *node;
    gchar *function;

    EDEBUG (("csinkrend", "some csink was closed."));

    node = (ENode *) csink_get_user_data (sink);
    EDEBUG (("csinkrend", "sink = %p, node = %p", sink, node));
    if(!node)
      EDEBUG(("csinkrend", "sink with NULL associated node!"));

    function = enode_attrib_str (node, "onclose", NULL);
    if (function)
	enode_call_ignore_return (node, function, "");
}

#ifdef HAVE_OPENSSL
static gint
csinkrend_oncertcheck_cb (CSinkSSL *sink, X509 *server_cert)
{
    ENode *node;
    EBuf *res;
    gchar *function;

    EDEBUG (("csinkrend", "some csinkssl asked for a cert check."));
  
    node = (ENode *) csink_get_user_data (CSINK(sink));
    EDEBUG (("csinkrend", "sink = %p, node = %p", sink, node));
    if(!node)
        EDEBUG(("csinkrend", "sink with NULL associated node!"));
  
    function = enode_attrib_str (node, "oncertcheck", NULL);
    if (function) {
        EDEBUG (("csinkrend", "calling the user's cert check func"));
        res = enode_call (node, function, "");
        if(res && !ebuf_equal_str(res, "ok")) {
	    EDEBUG (("csinkrend", "..user DENIED the cert."));
	    return FALSE;
	} 
    }

    /* accept it all by default. */
    EDEBUG (("csinkrend", "..user ACCEPTED the cert."));
    return TRUE;
}
#endif /* HAVE_OPENSSL */

static void
csinkrend_render (ENode * node)
{
    CSink * sink = NULL;

    EDEBUG (("csinkrend", "rendering %s", node->element->str));

#ifdef HAVE_OPENSSL
    if (g_str_equal (node->element->str, "csink-ssl") ) {
        sink = CSINK (csink_ssl_create (NULL) );
    } else {
        sink = CSINK (csink_inet_create (NULL) );
    }
#else
    sink = CSINK (csink_inet_create (NULL) );
#endif /* HAVE_OPENSSL */
    
    
    enode_set_kv (node, "csinkrend-csink", sink);
    enode_attribs_sync (node);
}


static void
csinkrend_sync_attribs (ENode * node)
{
    CSink *sink;
    EBuf *value;

    sink = enode_get_kv (node, "csinkrend-csink");

    if (!sink)
	return;

    value = enode_attrib (node, "remote-port", NULL);
    if (ebuf_not_empty (value)) {
	csink_inet_set_remote_port (CSINK_INET (sink),
				    erend_get_integer (value));
	EDEBUG (("csinkrend", "Setting remote-port to %d",
		 erend_get_integer (value)));
    }

    value = enode_attrib (node, "remote-host", NULL);
    if (ebuf_not_empty (value)) {
	csink_inet_set_remote_host (CSINK_INET (sink), value->str);
	EDEBUG (("csinkrend", "Setting remote-host to %s", value->str));
    }

    value = enode_attrib (node, "local-interface", NULL);
    if (ebuf_not_empty (value)) {
	csink_inet_set_local_interface (CSINK_INET (sink), value->str);
	EDEBUG (("csinkrend", "Setting local-interface to %s", value->str));
    }

    value = enode_attrib (node, "local-port", NULL);
    if (ebuf_not_empty (value)) {
	csink_inet_set_local_port (CSINK_INET (sink),
				   erend_get_integer (value));
	EDEBUG (("csinkrend", "Setting local-port to %d",
		 erend_get_integer (value)));
    }
}


static int
csinkrend_write_attr_set (ENode * node, EBuf * attr, EBuf * value)
{
    CSink *sink;
    EDEBUG (("csinkrend", "writing message: %s", value->str));


    sink = enode_get_kv (node, "csinkrend-csink");
    if (!sink) {
	EDEBUG (("csinkrend", "write_set, attempt to write to null sink"));
	return TRUE;
    }

    if (ebuf_not_empty (value))
	csink_write (sink, value);

    return TRUE;
}


#ifdef HAVE_OPENSSL
static int
csinkrend_ssl_cert_file_set (ENode * node, EBuf * attr, EBuf * value)
{
  CSinkSSL *sink;

  
  sink = enode_get_kv (node, "csinkrend-csink");
  if (!sink) {
    EDEBUG (("csinkrend", "ssl_cert_file_set, null sink"));
    return TRUE;
  }
  
  if (ebuf_not_empty (value)) 
    csink_ssl_set_certfile (sink, value->str);

  return TRUE;
}


static int
csinkrend_ssl_cert_dir_set (ENode * node, EBuf * attr, EBuf * value)
{
  CSinkSSL *sink;


  EDEBUG (("csinkrend", "in csinkrend_ssl_cert_dir_set"));
  
  sink = enode_get_kv (node, "csinkrend-csink");
  if (!sink) {
    EDEBUG (("csinkrend", "ssl_cert_dir_set, null sink"));
    return TRUE;
  }
  
  if (ebuf_not_empty (value)) 
    csink_ssl_set_certdir (sink, value->str);

  return TRUE;
}
#endif /* HAVE_OPENSSL */


static void
csinkrend_parent (ENode * parent_node, ENode * child_node)
{
    erend_short_curcuit_parent (parent_node, child_node);
}

static void
csinkrend_destroy (ENode *node)
{
    CSink *sink;
    EBuf * path;
  

    path = enode_path (node);
    sink = enode_get_kv (node, "csinkrend-csink");
    EDEBUG (("csinkrend", "in csinkrend_destroy sink = %p, %s", 
	     sink, path->str));
    ebuf_free (path);

    if (sink) {
        EDEBUG(("csinkrend", "in csinkrend_destroy, closing/freeing a sink"));
        enode_set_kv (node, "csinkrend-csink", NULL);
        csink_close (sink);
        csink_free (sink);
    }
}



static void
csinkrend_connect (ENode * node)
{
    CSink *sink;


    EDEBUG (("csinkrend", "connect, called..."));

    sink = enode_get_kv (node, "csinkrend-csink");
    EDEBUG (("csinkrend", "connect, sink = %p", sink));

    csinkrend_sync_attribs (node);

    csink_set_connect_func (sink, csinkrend_connect_cb);
    csink_set_new_data_func (sink, csinkrend_onnewdata_cb);
    csink_set_error_func (sink, csinkrend_onerror_cb);
    csink_set_close_func (sink, csinkrend_onclose_cb);
    csink_set_user_data (sink, (void *) node);

    EDEBUG (("csinkrend", "connect, calling open."));
    csink_open (sink);
    EDEBUG (("csinkrend", "returned from open."));

    if (csink_error (sink)) {
	EDEBUG (("csinkrend", "connect, csink error: %s", csink_errstr (sink)));
    } else if (CSINK_INET(sink)->socket.status & SOCKET_CONNECT_INPROGRESS) {
	EDEBUG (("csinkrend", "connect, waiting for a connection."));
	return;
    }

    EDEBUG (("csinkrend", "connect, returned from open."));
}


#ifdef HAVE_OPENSSL
static void
csinkrend_ssl_connect (ENode * node)
{
    CSink * sink;

    EDEBUG (("csinkrend", "ssl connect, called..."));

    sink = enode_get_kv (node, "csinkrend-csink");

    csinkrend_sync_attribs (node);

    csink_set_connect_func (sink, csinkrend_connect_cb);
    csink_set_new_data_func (sink, csinkrend_onnewdata_cb);
    csink_set_error_func (sink, csinkrend_onerror_cb);
    csink_set_close_func (sink, csinkrend_onclose_cb);
    csink_ssl_set_certcheck_func (CSINK_SSL(sink), csinkrend_oncertcheck_cb);
    csink_set_user_data (sink, (void *) node);


    EDEBUG (("csinkrendssl", "connect, calling open."));
    csink_open (sink);
    EDEBUG (("csinkrendssl", "returned from open."));

    if (csink_error (sink)) {
	EDEBUG (("csinkrend", "ssl connect, csink error: %s",
		 csink_errstr (sink)));
    }
    else if (CSINK_INET(sink)->socket.status & SOCKET_CONNECT_INPROGRESS) {
	EDEBUG (("csinkrend", "ssl connect, waiting for a connection."));
	return;
    }

    EDEBUG (("csinkrend", "ssl connect, returned from open."));
}
#endif /* HAVE_OPENSSL */


static void
csinkrend_disconnect (ENode * node)
{
    CSink *sink;
    EBuf *func;

    EDEBUG (("csinkrend", "disconnecting"));

    sink = enode_get_kv (node, "csinkrend-csink");
    if (!sink) {
	EDEBUG (("csinkrend", "attempted disconnect from null sink"));
	return;
    }

    func = enode_attrib (node, "ondisconnect", NULL);
    if (ebuf_not_empty (func))
	enode_call_ignore_return (node, func->str, "n", node);

    csink_close (sink);
}

static void
csinkrend_listen (ENode * node)
{
    CSink *sink;


    EDEBUG (("csinkrend", "csink listening..."));

    sink = enode_get_kv (node, "csinkrend-csink");

    EDEBUG (("csinkrend", "syncing attributes.."));
    csinkrend_sync_attribs (node);

    csink_set_connect_func (sink, csinkrend_accept_cb);
    csink_set_new_data_func (sink, csinkrend_onnewdata_cb);
    csink_set_error_func (sink, csinkrend_onerror_cb);
    csink_set_close_func (sink, csinkrend_onclose_cb);
    csink_set_user_data (sink, (void *) node);


    EDEBUG (("csinkrend", "calling inet_listen.."));
    csink_socket_listen (CSINK_SOCKET (sink));

    if (csink_error (sink)) {
	EDEBUG (("csinkrend", "Csink error: %s", csink_errstr (sink)));
    } else if (CSINK_SOCKET(sink)->status & SOCKET_CONNECT_INPROGRESS) {
	EDEBUG (("csinkrend", "Please hold, we're waiting for a connection."));
	return;
    }
}

static int
csinkrend_action_attr_set (ENode * node, EBuf * attr, EBuf * value)
{
    EDEBUG (("csinkrend", "action set to '%s'", value->str));

    /* TODO: defer action until later with gtk_idle_add, and */

    if (ebuf_equal_str (value, "connect")) {
	EDEBUG (("csinkrend", "action_set, matched 'connect', dispatching."));
	csinkrend_connect (node);
	EDEBUG (("csinkrend", "action_set, returning."));
	return TRUE;
    }

    if (ebuf_equal_str (value, "disconnect")) {
	EDEBUG (("csinkrend", "action_set, matched 'disconnect', dispatching"));
	csinkrend_disconnect (node);
	EDEBUG (("csinkrend", "action_set, returning."));
	return TRUE;
    }

    if (ebuf_equal_str (value, "listen")) {
	EDEBUG (("csinkrend", "action_set,  matched 'listen', dispatching"));
	csinkrend_listen (node);
	EDEBUG (("csinkrend", "action_set, returning."));
	return TRUE;
    }

    EDEBUG (("csinkrend", "action_set, no match."));
    EDEBUG (("csinkrend", "action_set, returning."));
    return TRUE;
}


#ifdef HAVE_OPENSSL
static int
csinkrend_ssl_action_attr_set (ENode * node, EBuf * attr, EBuf * value)
{
    EDEBUG (("csinkrend", "ssl action set to '%s'", value->str));

    /* TODO: defer action until later with gtk_idle_add, and */

    if (ebuf_equal_str (value, "connect")) {
	EDEBUG (("csinkrend", "ssl action_set, matched 'connect'."));
	csinkrend_ssl_connect (node);
	EDEBUG (("csinkrend", "ssl action_set, returning."));
	return TRUE;
    }

    if (ebuf_equal_str (value, "disconnect")) {
      EDEBUG (("csinkrend", "ssl action_set, matched 'disconnect'"));
      csinkrend_disconnect (node);
      EDEBUG (("csinkrend", "ssl action_set, returning."));
      return TRUE;
    }
     
     
    if (ebuf_equal_str (value, "listen")) { 
 	EDEBUG (("csinkrend", "ssl action_set,  matched 'listen'")); 
 	csinkrend_listen (node);
 	EDEBUG (("csinkrend", "ssl action_set, returning.")); 
 	return TRUE; 
     } 

    EDEBUG (("csinkrend", "ssl action_set, no match."));
    EDEBUG (("csinkrend", "ssl action_set, returning."));
    return TRUE;
}
#endif /* HAVE_OPENSSL */


static void
register_inet_attribs (Element *element)
{
  ElementAttr *e_attr;

  EDEBUG (("csinkrend", "registering csink, inet actions"));


  e_attr = g_new0 (ElementAttr, 1);
  e_attr->attribute = "remote-host";
  e_attr->description = "Name of remote host or IP address to connect to.";
  e_attr->value_desc = "string";
  element_register_attrib (element, e_attr);

  e_attr = g_new0 (ElementAttr, 1);
  e_attr->attribute = "__remote-ip";
  e_attr->description = "Set to the ip address of the remote host.";
  e_attr->value_desc = "string";
  element_register_attrib (element, e_attr);

  e_attr = g_new0 (ElementAttr, 1);
  e_attr->attribute = "remote-port";
  e_attr->description = "Port of remote host.";
  e_attr->value_desc = "integer";
  element_register_attrib (element, e_attr);

  e_attr = g_new0 (ElementAttr, 1);
  e_attr->attribute = "local-interface";
  e_attr->description =
    "Specify local interface to bind on.  Not often used.";
  e_attr->value_desc = "string";
  element_register_attrib (element, e_attr);

  e_attr = g_new0 (ElementAttr, 1);
  e_attr->attribute = "local-port";
  e_attr->description =
    "Specify local port to bind to.  Useful mostly for listening.";
  e_attr->value_desc = "integer";
  element_register_attrib (element, e_attr);

  e_attr = g_new0 (ElementAttr, 1);
  e_attr->attribute = "onnewdata";
  e_attr->description = "Function to call with data read from the csink.";
  e_attr->value_desc = "function";
  e_attr->possible_values = "(csink_node, new_data, data_length)";
  element_register_attrib (element, e_attr);

  e_attr = g_new0 (ElementAttr, 1);
  e_attr->attribute = "onclose";
  e_attr->description = "Function to call when the connection closes.";
  e_attr->value_desc = "function";
  e_attr->possible_values = "(csink_node)";
  element_register_attrib (element, e_attr);

  e_attr = g_new0 (ElementAttr, 1);
  e_attr->attribute = "onconnect";
  e_attr->description = "Function to call when the connection completes.";
  e_attr->value_desc = "function";
  e_attr->possible_values = "(csink_node)";
  element_register_attrib (element, e_attr);

  e_attr = g_new0 (ElementAttr, 1);
  e_attr->attribute = "onerror";
  e_attr->description = "Function to call when an error occurs.";
  e_attr->value_desc = "function";
  e_attr->possible_values = "(csink_node, error_string)";
  element_register_attrib (element, e_attr);

  e_attr = g_new0 (ElementAttr, 1);
  e_attr->attribute = "action";
  e_attr->description =
    "Action wanted to perform (ie \"connect\", \"disconnect\".)";
  e_attr->value_desc = "choice";
  e_attr->possible_values = "connect,close,listen";
  e_attr->set_attr_func = csinkrend_action_attr_set;
  element_register_attrib (element, e_attr);

  /* This is _write, with the leading "_", so that data set to it does not get
   * saved if xml for the application is dumped.
   */
  e_attr = g_new0 (ElementAttr, 1);
  e_attr->attribute = "_write";
  e_attr->description = "Message wanted to be sent.";
  e_attr->value_desc = "string";
  e_attr->set_attr_func = csinkrend_write_attr_set;
  element_register_attrib (element, e_attr);
}

void
rendcsink_init (int flags)
{
    Element *element;
    ElementAttr *e_attr;

    /* SIGPIPE may be sent to us when we try to write down a closed
     * socket. Lets ignore it. */
    signal (SIGPIPE, SIG_IGN);
    

    EDEBUG (("csinkrend", "registering csink"));

    if (flags & RENDERER_INIT)
	csink_init_funcs ((CSinkAddFDFunc) entity_mainloop_io_add,
			  (CSinkRemoveFDFunc) entity_mainloop_io_remove);

    if (!(flags & RENDERER_REGISTER))
	return;


    /* Register the "csink" element */
    element = g_new0 (Element, 1);
    element->render_func = csinkrend_render;
    element->parent_func = csinkrend_parent;
    element->destroy_func = csinkrend_destroy;
    element->tag = "csink";
    element_register (element);
    
    register_inet_attribs (element); /* define it's attributes */

#ifdef HAVE_OPENSSL
    /* Register the "csink-ssl" element */
    element = g_new0 (Element, 1);
    element->render_func = csinkrend_render;
    element->parent_func = csinkrend_parent;
    element->destroy_func = csinkrend_destroy;
    element->tag = "csink-ssl";
    element_register (element);


    register_inet_attribs (element); /* define it's base attributes */


    e_attr = g_new0 (ElementAttr, 1); /* override the action attribute */
    e_attr->attribute = "action";
    e_attr->description =
	"Action wanted to perform (ie \"connect\", \"close\".)";
    e_attr->value_desc = "choice";
    e_attr->possible_values = "connect,close,listen";
    e_attr->set_attr_func = csinkrend_ssl_action_attr_set;
    element_register_attrib (element, e_attr);

    e_attr = g_new0 (ElementAttr, 1); /* key file attribute */
    e_attr->attribute = "certfile";
    e_attr->description = "File containing the private key to use. "
			  "May contain the public cert.";
    e_attr->value_desc = "string";
    e_attr->set_attr_func = csinkrend_ssl_cert_file_set;
    element_register_attrib (element, e_attr);

    e_attr = g_new0 (ElementAttr, 1); /* key file attribute */
    e_attr->attribute = "certdir";
    e_attr->description = "Directory containing the public keys to use.";
    e_attr->value_desc = "string";
    e_attr->set_attr_func = csinkrend_ssl_cert_dir_set;
    element_register_attrib (element, e_attr);

    e_attr = g_new0 (ElementAttr, 1); /* cert check callback attrib */
    e_attr->attribute = "oncertcheck";
    e_attr->description = "Function to verify a certificate";
    e_attr->value_desc = "function";
    e_attr->possible_values = "(csink-ssl node, certificate)";
    element_register_attrib (element, e_attr);
#endif /* HAVE_OPENSSL */
}



syntax highlighted by Code2HTML, v. 0.9.1