/* This file is part of GNU Pies.
   Copyright (C) 2016-2022 Sergey Poznyakoff

   GNU Pies 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 3, or (at your option)
   any later version.

   GNU Pies 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.

   You should have received a copy of the GNU General Public License
   along with GNU Pies.  If not, see <http://www.gnu.org/licenses/>. */

#include "pies.h"

char *meta1_queue_dir;

#define META1_QUEUE_DIR()					\
  (meta1_queue_dir ? meta1_queue_dir : "/var/spool/meta1")

static void
setflag (int *flag, char const *var, int bit)
{
  char *p = getenv (var);
  if (p && (*p - '0') > 0)
    *flag |= bit;
}

static enum grecs_tree_recurse_res
freeproc (enum grecs_tree_recurse_op op, struct grecs_node *node, void *data)
{
  switch (op)
    {
    case grecs_tree_recurse_set:
    case grecs_tree_recurse_post:
      grecs_node_unlink (node);
      grecs_node_free (node);
      break;
    case grecs_tree_recurse_pre:
      /* descend into the subtree */
      break;
    }
  return grecs_tree_recurse_ok;
}

int
grecs_subtree_free (struct grecs_node *node)
{
  if (!node)
    return 0;
  if (node->type != grecs_node_block)
    {
      errno = EINVAL;
      return 1;
    }
  grecs_tree_recurse (node, freeproc, node);
  return 0;
}


static int
xlat_start_action (struct grecs_node *node)
{
  if (node->type != grecs_node_stmt)
    return -1;
  if (strcmp (node->v.value->v.string, "wait") == 0)
    {
      node->v.value->v.string = grecs_strdup ("exec");
    }
  return 0;
}

enum substatement_status
  {
    substatement_success,
    substatement_error,
    substatement_not_found
  };

static struct grecs_node *
simple_find_node (struct grecs_node *node, char const *name)
{
  for (node = node->down; node; node = node->next)
    if (strcmp (node->ident, name) == 0)
      break;
  return node;
}

static enum substatement_status
substatement_find (struct grecs_node *node, char const *name, int type,
		   int req, grecs_value_t **ret)
{
  struct grecs_node *np;
  
  np = simple_find_node (node, name);
  if (!np)
    {
      if (req)
	grecs_error (&node->locus, 0, _("no \"%s\" statement in block"),
		     name);
      return substatement_not_found;
    }

  if (node->type != grecs_node_block)
    {
      grecs_error (&node->locus, 0, _("unexpected scalar statement"));
      return substatement_error;
    }
  
  if (grecs_assert_value_type (np->v.value, type, &np->v.value->locus))
    return substatement_error;

  *ret = np->v.value;
  
  return substatement_success;
}

static int
translate_url_param (struct grecs_node *node, char const *name,
		     struct grecs_txtacc *acc)
{
  struct grecs_value *np;
  
  switch (substatement_find (node, name, GRECS_TYPE_STRING, 0, &np))
    {
    case substatement_success:
      grecs_txtacc_grow_char (acc, ';');
      grecs_txtacc_grow_string (acc, name + 1);
      grecs_txtacc_grow_char (acc, '=');
      grecs_txtacc_grow_string (acc, np->v.string);
      break;

    case substatement_not_found:
      break;

    case substatement_error:
      return -1;
    }
  return 0;
}
    
static int
xlat_listen_socket_i (struct grecs_node *node, char const *type,
		      struct grecs_txtacc *acc)
{
  struct grecs_value *val, *vp;
  char *url;
  
  grecs_txtacc_grow_string (acc, type);
  grecs_txtacc_grow (acc, "://", 3);

  if (strcmp (type, "inet") == 0)
    {
      const char *addr;

      switch (substatement_find (node, "address", GRECS_TYPE_STRING, 0, &vp))
	{
	case substatement_success:
	  addr = vp->v.string;
	  break;

	case substatement_not_found:
	  addr = "0.0.0.0";
	  break;

	case substatement_error:
	  return -1;
	}

      grecs_txtacc_grow_string (acc, addr);
      grecs_txtacc_grow_char (acc, ':');

      if (substatement_find (node, "port", GRECS_TYPE_STRING, 1, &vp) !=
	  substatement_success)
	return -1;
      grecs_txtacc_grow_string (acc, vp->v.string);
    }
  else if (strcmp (type, "unix") == 0)
    {
      /*  listen_socket {
	         type=unix;
		 path = /tmp/socket;
		 umask = 077;
		 user = user;
                 group = group;
	   }
      */
      if (substatement_find (node, "path", GRECS_TYPE_STRING, 1, &vp) !=
	  substatement_success)
	return -1;
      
      grecs_txtacc_grow_string (acc, vp->v.string);
      if (translate_url_param (node, "user", acc))
	return -1;
      if (translate_url_param (node, "group", acc))
	return -1;
      if (translate_url_param (node, "umask", acc))
	return -1;
    }
  else
    {
      grecs_error (&node->locus, 0, _("unsupported type"));
      return -1;
    }

  grecs_txtacc_grow_char (acc, 0);
  url = grecs_txtacc_finish (acc, 1);

  val = grecs_malloc (sizeof(*val));
  val->type = GRECS_TYPE_STRING;
  val->locus = node->locus;
  val->v.string = url;
    
  grecs_subtree_free (node->down);

  node->type = grecs_node_stmt;
  node->v.value = val;
  return 0;
}

static int
xlat_listen_socket (struct grecs_node *node)
{
  int rc;
  struct grecs_value *val;
  struct grecs_txtacc *acc;
  
  if (node->type != grecs_node_block)
    return -1;

  if (substatement_find (node, "type", GRECS_TYPE_STRING, 1, &val) !=
      substatement_success)
    return -1;
  acc = grecs_txtacc_create ();
  rc = xlat_listen_socket_i (node, val->v.string, acc);
  grecs_txtacc_free (acc);
  return rc;
}


struct meta1_translator
{
  char const *old_name;
  char const *new_name;
  int (*translate) (struct grecs_node *);
};

static struct meta1_translator transtab[] = {
  { "start_action", "mode", xlat_start_action },
  { "listen_socket", "socket", xlat_listen_socket },
  { "pass_fd_socket", "pass-fd-socket" },
  { "user", "user" },
  { "path", "program" },
  { "arguments", "command" },
  { "restart_dependencies", "dependents", },
  { NULL }
};

static struct meta1_translator *
meta1_translator_lookup (const char *name)
{
  struct meta1_translator *p;

  for (p = transtab; p->old_name; p++)
    if (strcmp (p->old_name, name) == 0)
      return p;
  return NULL;
}

static int
meta1_translate_stmt (struct grecs_node *stmt, struct component *comp)
{
  struct meta1_translator *trans = meta1_translator_lookup (stmt->ident);
  struct grecs_keyword *kwp;
  
  if (!trans)
    return 0;
  if (trans->translate && trans->translate (stmt))
    return -1;
  if (stmt->type != grecs_node_stmt)
    return -1;
  
  kwp = find_component_keyword (trans->new_name);
  grecs_process_ident (kwp, stmt->v.value, comp, &stmt->locus);
  return 0;
}

static int
meta1_translate_node (struct grecs_node *node)
{
  struct component *comp;
  struct grecs_node *stmt;
  size_t len;
  int err = 0;

  if (node->type != grecs_node_block)
    return 0;
  comp = component_create (node->ident);
  for (stmt = node->down; stmt; stmt = stmt->next)
    {
      if (meta1_translate_stmt (stmt, comp))
	++err;
    }
  if (err)
    {
      component_free (comp);
      return -1;
    }
  
  comp->privs.allgroups = 1;
  comp->dir = grecs_strdup (META1_QUEUE_DIR ());
  comp->redir[RETR_ERR].type = redir_file;
  comp->redir[RETR_ERR].v.file = NULL;
  len = 0;
  grecs_asprintf (&comp->redir[RETR_ERR].v.file, &len,
		  "%s/%s.log", META1_QUEUE_DIR (), comp->tag);
  component_finish (comp, &node->locus);
  return 0;
}

static int
meta1_translate_node_list (struct grecs_node *node)
{
  int err = 0;
  while (node)
    {
      if (meta1_translate_node (node))
	++err;
      node = node->next;
    }
  return err;
}

int
meta1_config_parse (const char *name)
{
  struct grecs_node *subtree;
  int traceflags = 0;
  int rc;
  
  setflag (&traceflags, "META1_DEBUG_YACC", GRECS_TRACE_GRAM);
  setflag (&traceflags, "META1_DEBUG_LEX", GRECS_TRACE_LEX);
  
  grecs_error_count = 0;
  grecs_current_locus_point.file = grecs_install_text (name);
  grecs_current_locus_point.line = 1;
  grecs_current_locus_point.col = 0;
  subtree = grecs_meta1_parser (name, traceflags);

  if (!subtree)
    return -1;

  rc = meta1_translate_node_list (subtree->down);

  grecs_node_free (subtree);
  
  return rc;
}
