#include <entity.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>


/* We are using popen so we have to kill all the special
 * chars in the command. */
static void normalize_resolve_cmd (gchar * resolve_cmd)
{
  char * str;

  str = resolve_cmd;

  while (*str != '\0')
    {
      if ('\\' == *str    ||
	  '\'' == *str    ||
	  '\"' == *str    ||
	  '*'  == *str    ||
	  '&'  == *str    ||
	  '#'  == *str    ||
	  '('  == *str    ||
	  ')'  == *str    ||
	  '|'  == *str    ||
	  '~'  == *str    ||
	  '\t' == *str    ||
	  '$'  == *str    ||
	  '>'  == *str    ||
	  '<'  == *str    ||
	  '{'  == *str    ||
	  '}'  == *str
         )
       {
         *str = '_';   /* Assume that want _. :) */
       }

      str++;
    }
}

static int
resolverrend_resolv_finished (gint fd, EIOCond cond, ENode * node)
{
    FILE *fp;			/* The file pointer to pclose on. */
    char buf[2048];		/* buffer for read(2) to fill. */
    int exit_val;		/* the exit value of the child. */
    int count;			/* bytes read. */
    int res;			/* pclose(3) return. (exit status) */
    gpointer tag;		/* the tag to remove from the mainloop. */
    gchar *function;		/* The function to call with the data. */


    fp = enode_get_kv(node, "resolver-fp");
    tag = enode_get_kv(node, "resolver-io-tag");


    count = read (fd, buf, 2047);

    if (-1 == count) {		/* Some sort of read error. */
        EDEBUG(("resolver", "error reading hostname lookup '%s'",
		strerror(errno) ));

        buf[0] = '\0';		/* Make sure it looks like an empty string. */
    } else {
	if (buf[count - 1] == '\n') {		/* Set the newline to nul. */
	    buf[count - 1] = '\0';
        } else {				/* Just set the end to nul. */
            buf[count] = '\0';
        }
    }


    entity_mainloop->io_remove (tag);
    enode_set_kv (node, "resolver-io-tag", 0);

    res = pclose(fp);
    exit_val = WEXITSTATUS(res);

    EDEBUG(("resolver", "child's exit value = %i", exit_val));
    if (exit_val == EXIT_FAILURE) {
        EDEBUG(("resolve", "child exited with failure status."));

        enode_attrib_str (node, "error", buf);

        enode_ref (node);
        /* Call the error function and then the action one with no result,
	 * so even if they don't have an onerror function they know the lookup
         * is finished. */
        function = enode_attrib_str (node, "onerror", NULL);
        enode_call_ignore_return (node, function, "s", buf);

	/* The user may have destroyed that node... */
	if (!ENODE_FLAG_ISSET (node, ENODE_DELETED)) {
            function = enode_attrib_str (node, "action", NULL);
            enode_call_ignore_return (node, function, "s", "");
	}
        enode_unref (node);

    } else {
        function = enode_attrib_str (node, "action", NULL);
        enode_call_ignore_return (node, function, "s", buf);
    }

    return FALSE;
}


static void
resolverrend_render (ENode * node)
{
    enode_attribs_sync (node);    
}

static void
resolverrend_destroy (ENode *node)
{
    gpointer tag;

    tag = enode_get_kv (node, "resolver-io-tag");

    if (tag) {
        entity_mainloop->io_remove (tag);
    }
}

static int
resolverrend_ip_attr_set (ENode * node, EBuf * attr, EBuf * value)
{
    gchar resolve_cmd[15 + PATH_MAX + 4];	/* IP addr + command. */
    FILE *fp;
    int fd;
    gpointer tag;
	
    enode_attrib_str (node, "error", "");

    snprintf (resolve_cmd, sizeof (resolve_cmd) - 1,
	RESOLVER_BIN " -h %s", value->str);

    /* Keep malicious code from being ran when we popen(3).
     * resolve_cmd is changed in place. */
    normalize_resolve_cmd (resolve_cmd);

    EDEBUG(("resolver", "running popen"));
    fp = popen(resolve_cmd, "r");

    if(!fp) {
        EDEBUG(("resolver", "error opening resolve command '%s'",
		resolve_cmd));
        return FALSE;
    }

    fd = fileno(fp);

    tag = entity_mainloop->io_add (fd, EIO_READ | EIO_ERROR,
		(void *) resolverrend_resolv_finished, node);

    enode_set_kv (node, "resolver-io-tag", tag);
    enode_set_kv (node, "resolver-fp", fp);

    return TRUE;
}

static int
resolverrend_hostname_attr_set (ENode * node, EBuf * attr, EBuf * value)
{
    gchar resolve_cmd[256 + PATH_MAX + 4];	/* hostname + command. */
    FILE *fp;
    int fd;
    gpointer tag;
	
    enode_attrib_str (node, "error", "");

    snprintf (resolve_cmd, sizeof (resolve_cmd) - 1,
	RESOLVER_BIN " -i %s", value->str);

    /* Keep malicious code from being ran when we popen(3).
     * resolve_cmd is changed in place. */
    normalize_resolve_cmd (resolve_cmd);

    EDEBUG(("resolver", "running popen"));
    fp = popen(resolve_cmd, "r");

    if(!fp) {
        EDEBUG(("resolver", "error opening resolve command '%s'\n",
		resolve_cmd));
        return FALSE;
    }

    fd = fileno(fp);

    tag = entity_mainloop->io_add (fd, EIO_READ | EIO_ERROR,
		(void *) resolverrend_resolv_finished, node);

    enode_set_kv (node, "resolver-io-tag", tag);
    enode_set_kv (node, "resolver-fp", fp);

    return TRUE;
}

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

    /* Register the resolver element */
    element = g_new0 (Element, 1);
    element->render_func = resolverrend_render;
    element->destroy_func = resolverrend_destroy;
    element->tag = "resolver";
    element_register (element);

    e_attr = g_new0 (ElementAttr, 1);
    e_attr->attribute = "ip";
    e_attr->description = "The ip address in \"dotted quad\" notation.  "
                          "ex. 208.153.12.155.";
    e_attr->value_desc = "string";
    e_attr->set_attr_func = resolverrend_ip_attr_set;
    element_register_attrib (element, e_attr);

    e_attr = g_new0 (ElementAttr, 1);
    e_attr->attribute = "hostname";
    e_attr->description = "The hostname to get the ip address for";
    e_attr->value_desc = "string";
    e_attr->set_attr_func = resolverrend_hostname_attr_set;
    element_register_attrib (element, e_attr);

    e_attr = g_new0 (ElementAttr, 1);
    e_attr->attribute = "error";
    e_attr->description = "The error string if no result was found";
    e_attr->value_desc = "string";
    element_register_attrib (element, e_attr);

    e_attr = g_new0 (ElementAttr, 1);
    e_attr->attribute = "action";
    e_attr->description = "Function to call when it is resolved";
    e_attr->value_desc = "function";
    e_attr->possible_values = "(resolver_node, result)";
    element_register_attrib (element, e_attr);

    e_attr = g_new0 (ElementAttr, 1);
    e_attr->attribute = "onerror";
    e_attr->description = "Function to call if an error occurs in the lookup";
    e_attr->value_desc = "function";
    e_attr->possible_values = "(resolver_node, error)";
    element_register_attrib (element, e_attr);
}



syntax highlighted by Code2HTML, v. 0.9.1