/* This file is part of GNU Pies.
   Copyright (C) 2007-2025 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"
#include "prog.h"
#include "base64.h"

struct control control;

pies_identity_t identity;


struct ctlbuf
{
  char *base;
  size_t size;
  size_t level;
  size_t pos;
};

#define CTLBUFSIZE 16
#define EOT '\04'

static void
ctlbuf_init (struct ctlbuf *buf)
{
  buf->base = NULL;
  buf->size = 0;
  buf->level = 0;
  buf->pos = 0;
}

static void
ctlbuf_free (struct ctlbuf *buf)
{
  free (buf->base);
}

static void
ctlbuf_alloc (struct ctlbuf *buf, size_t s)
{
  size_t minsize = buf->level + s;
  while (minsize >= buf->size)
    {
      if (buf->size == 0)
	buf->size = CTLBUFSIZE;
      else
	buf->size *= CTLBUFSIZE;
      buf->base = grecs_realloc (buf->base, buf->size);
    }
}

static void
ctlbuf_write (struct ctlbuf *buf, char const *str, size_t n)
{
  ctlbuf_alloc (buf, n);
  memcpy (buf->base + buf->level, str, n);
  buf->level += n;
}

static char const *
ctlbuf_peek (struct ctlbuf *buf)
{
  return buf->base + buf->pos;
}

#define ctlbuf_rdsize(b) ((b)->level - (b)->pos)

static ssize_t
ctlbuf_read (struct ctlbuf *buf, char *str, size_t n)
{
  size_t size = ctlbuf_rdsize (buf);
  if (size < n)
    n = size;
  memcpy (str, buf->base + buf->pos, n);
  buf->pos += n;
  return n;
}

static ssize_t
ctlbuf_copy (struct ctlbuf *out, struct ctlbuf *in)
{
  size_t size = ctlbuf_rdsize (in);
  ctlbuf_alloc (out, size);
  ctlbuf_write (out, in->base + in->pos, size);
  in->pos += size;
  return size;
}

static void
ctlbuf_flush (struct ctlbuf *buf)
{
  buf->pos = buf->level = 0;
}

static void
ctlbuf_chomp (struct ctlbuf *buf)
{
  if (buf->base)
    {
      while (buf->level > 0
	     && strchr(" \t\r\n", buf->base[buf->level-1]))
	--buf->level;
    }
  ctlbuf_write (buf, "", 1);
}

#define ISWS(c) ((c) == ' ' || (c) == '\t')

static char *
ctlbuf_split (struct ctlbuf *buf)
{
  ssize_t i;
  ssize_t hsize = -1;
  char *s;
  
  for (i = 0; buf->pos + i < buf->level; i++)
    if (buf->base[buf->pos + i] == ':')
      {
	hsize = i;
	break;
      }
  
  if (hsize == -1)
    return NULL;

  s = grecs_malloc (hsize + 1);
  ctlbuf_read (buf, s, hsize);
  s[hsize] = 0;

  do
    ++buf->pos;
  while (buf->pos < buf->level && ISWS (buf->base[buf->pos]));
  
  return s;
}      

#define CTL_END_STATE           0
#define CTL_INITIAL_STATE       0x01
#define CTL_USER_STATE          0x02
#define CTL_ADMIN_STATE         0x04
#define CTL_ACTION_STATE        0x08

#define CTL_AUTHENTICATED_STATE (CTL_USER_STATE|CTL_ADMIN_STATE)
#define CTL_ALL_STATES (CTL_INITIAL_STATE|CTL_AUTHENTICATED_STATE)

#define CRLF "\r\n"

struct http_header
{
  char *name;
  char *value;
};

static unsigned
http_header_hash (void *data, unsigned long n_buckets)
{
  const struct http_header *p = data;
  return grecs_hash_string_ci (p->name, n_buckets);
}

static int
http_header_compare (void const *data1, void const *data2)
{
  const struct http_header *p1 = data1;
  const struct http_header *p2 = data2;
  return strcasecmp (p1->name, p2->name);
}

static int
http_header_copy (void *a, void *b)
{
  struct http_header *dst = a;
  struct http_header *src = b;

  dst->name = grecs_strdup (src->name);
  dst->value = src->value ? grecs_strdup (src->value) : NULL;

  return 0;
}

static void
http_header_free (void *p)
{
  const struct http_header *h = p;
  grecs_free (h->name);
  grecs_free (h->value);
  free (p);
}

enum input_state
  {
    is_initial,
    is_headers,
    is_content,
    is_end
  };

struct input
{
  struct ctlbuf ibuf;
  enum input_state input_state;
  char *input_method;
  char *input_uri;
  char *input_proto;
  struct grecs_symtab *headers;
  size_t input_content_length;
};

static char const *
http_get_header (struct grecs_symtab *headers, char const *name)
{
  struct http_header key, *ret;
  key.name = (char*) name;
  ret = grecs_symtab_lookup_or_install (headers, &key, NULL);
  return ret ? ret->value : NULL;
}

static void
input_init (struct input *inp)
{
  ctlbuf_init (&inp->ibuf);
  inp->input_state = is_initial;
  inp->headers = grecs_symtab_create (sizeof (struct http_header),
				      http_header_hash,
				      http_header_compare,
				      http_header_copy,
				      NULL,
				      http_header_free);
  inp->input_method = NULL;
  inp->input_uri = NULL;
  inp->input_proto = NULL;
  inp->input_content_length = 0;
}

static void
input_destroy (struct input *inp)
{
  ctlbuf_free (&inp->ibuf);
  grecs_symtab_free (inp->headers);
  free (inp->input_method);
  free (inp->input_uri);
  free (inp->input_proto);
}

static void
input_reset (struct input *inp)
{
  inp->input_state = is_initial;
  grecs_symtab_clear (inp->headers); 

  free (inp->input_method);
  inp->input_method = NULL;

  free (inp->input_uri);
  inp->input_uri = NULL;

  free (inp->input_proto);
  inp->input_proto = NULL;

  inp->input_content_length = 0;
  ctlbuf_flush (&inp->ibuf);    
}

static int
input_append (struct input *inp, char c)
{
  switch (inp->input_state)
    {
    case is_initial:
      if (c == '\n')
	{
	  int rc;
	  char *reqline[3];

	  ctlbuf_chomp (&inp->ibuf);
	  rc = strsplit3 (inp->ibuf.base, reqline, 1);
	  if (rc == 0)
	    {
	      inp->input_method = reqline[0];
	      inp->input_uri = reqline[1];
	      inp->input_proto = reqline[2];
	    }
	  else if (rc == -1)
	    {
	      logmsg (LOG_ERR, _("can't parse input line: %s"),
		      strerror (errno));
	      return 500;
	    }
	  else 
	    {
	      logmsg (LOG_ERR, _("protocol error"));
	      return 400;
	    }
	  ctlbuf_flush (&inp->ibuf);
	  inp->input_state = is_headers;
	}
      else
	ctlbuf_write (&inp->ibuf, &c, 1);
      break;
	  
    case is_headers:
      if (c == '\n')
	{
	  ctlbuf_chomp (&inp->ibuf);
	  if (ctlbuf_rdsize (&inp->ibuf) > 1)
	    {
	      int install = 1;
	      size_t size;
	      struct http_header key, *ret;
	      
	      key.name = ctlbuf_split (&inp->ibuf);
	      if (!key.name)
		{
		  logmsg (LOG_ERR, _("protocol error"));
		  return 400;
		}
	      key.value = NULL;
	      ret = grecs_symtab_lookup_or_install (inp->headers,
						    &key, &install);
	      free (key.name);
	      if (!ret)
		{
		  logmsg (LOG_ERR, _("cannot install header: %s"),
			  strerror (errno));
		  return 500;
		}
	      if (!install)
		free (ret->value);
	      size = ctlbuf_rdsize (&inp->ibuf);
	      ret->value = grecs_malloc (size + 1);
	      ctlbuf_read (&inp->ibuf, ret->value, size);
	      ret->value[size] = 0;
	    }
	  else
	    {
	      char const *val = http_get_header (inp->headers,
						 "content-length");
	      if (val)
		{
		  char *end;
		  inp->input_content_length = strtoul (val, &end, 10);
		  if (*end)
		    {
		      logmsg (LOG_ERR, _("protocol error"));
		      return 400;
		    }
		  if (inp->input_content_length == 0)
		    {
		      inp->input_state = is_end;
		      return 200;
		    }
		  else
		    inp->input_state = is_content;
		}
	      else
		{
		  inp->input_state = is_end;
		  return 200;
		}
	    }
	  ctlbuf_flush (&inp->ibuf);
	}
      else
	ctlbuf_write (&inp->ibuf, &c, 1);
      break;
	  
    case is_content:
      ctlbuf_write (&inp->ibuf, &c, 1);
      if (ctlbuf_rdsize (&inp->ibuf) == inp->input_content_length)
	return 200;
      break;

    case is_end:
      break;
    }
  return 0;
}

struct output
{
  unsigned code;
  struct grecs_symtab *headers;
  struct json_value *reply;
};

static void
output_init (struct output *out)
{
  out->reply = NULL;
  out->headers = grecs_symtab_create(sizeof(struct http_header),
				     http_header_hash,
				     http_header_compare,
				     http_header_copy,
				     NULL,
				     http_header_free);
  out->reply = NULL;
}

static void
output_destroy (struct output *out)
{
  json_value_free (out->reply);
  grecs_symtab_free (out->headers);
}

static void
output_reset (struct output *out)
{
  json_value_free (out->reply);
  out->reply = NULL;
  grecs_symtab_clear (out->headers); 
}

static int
output_set_header (struct output *out, char const *name, char const *fmt, ...)
{
  int install = 1;
  struct http_header key, *ret;
  va_list ap;
  size_t len = 0;
  
  key.name = (char *) name;
  key.value = NULL;
  ret = grecs_symtab_lookup_or_install (out->headers, &key, &install);
  if (!ret)
    {
      logmsg (LOG_ERR, _("cannot install output header: %s"),
	      strerror (errno));
      return 1;
    }
  if (!install)
    free (ret->value);
  va_start (ap, fmt);
  ret->value = NULL;
  grecs_vasprintf (&ret->value, &len, fmt, ap);
  va_end (ap);
  return 0;
}

static struct json_value *
json_reply_create (void)
{
  return json_new_object ();
}

void
json_object_set_string (struct json_value *obj,
			char const *name, char const *fmt, ...)
{
  va_list ap;
  char *s = NULL;
  size_t l = 0;
  
  va_start (ap, fmt);
  grecs_vasprintf (&s, &l, fmt, ap);
  va_end (ap);
  json_object_set (obj, name, json_new_string (s));
  grecs_free (s);
}

void
json_object_set_number (struct json_value *obj, char const *name, long val)
{
  json_object_set (obj, name, json_new_long (val));
}

void
json_object_set_bool (struct json_value *obj, char const *name, int val)
{
  json_object_set (obj, name, json_new_bool (val));
}

static struct json_value *
json_error_reply_create (int http_code, const char *msg)
{
  struct json_value *val;
  
  val = json_reply_create ();
  json_object_set_string (val, "status", http_code < 300 ? "OK" : "ER");
  json_object_set_string (val, "message", "%s", msg);
  return val;
}

struct ctlio
{
  union pies_sockaddr_storage addr;
  socklen_t addrlen;
  struct input input;
  struct output output;
  int state;
  int action;
  int code;
  struct ctlbuf obuf;
};

static struct ctlio *
ctlio_create (void)
{
  struct ctlio *io;

  io = grecs_malloc (sizeof (*io));
  input_init (&io->input);
  output_init (&io->output);
  io->state = identity_provider_list ? CTL_INITIAL_STATE : CTL_ADMIN_STATE;
  io->action = ACTION_CONT;
  ctlbuf_init (&io->obuf);
  return io;
}

void
ctlio_destroy (struct ctlio *io)
{
  input_destroy (&io->input);
  output_destroy (&io->output);
  ctlbuf_free (&io->obuf);
  free (io);
}

static void
ctlio_reset (struct ctlio *io)
{
  input_reset (&io->input);
  output_reset (&io->output);
  if (identity)
    {
      pies_identity_destroy (identity);
      identity = NULL;
    }
  if (io->state != CTL_END_STATE && io->state != CTL_ACTION_STATE)
    io->state = identity_provider_list ? CTL_INITIAL_STATE : CTL_ADMIN_STATE;
}

static int
ctlio_end (int fd, struct ctlio *io)
{
  deregister_socket (fd);
  ctlio_destroy (io);
  return 1;
}

static char const *
http_text (int code)
{
  switch (code)
    {
    case 200:
      return "OK";

    case 400:
      return "Bad request";
      
    case 401:
      return "Unauthorized";
      
    case 403:
      return "Forbidden";
      
    case 404:
      return "Not found";

    case 405:
      return "Method Not Allowed";

    case 406:
      return "Not Acceptable";

    case 411:
      return "Length Required";

    case 415:
      return "Unsupported Media Type";

    case 500:
      return "Internal Server Error";

    case 501:
      return "Not Implemented";

    case 503:
      return "Service Unavailable";

    case 505:
      return "HTTP Version Not Supported";
    }
  return "";
}

static void
ctlio_reply (struct ctlio *io, int code, const char *fmt, ...)
{
  io->code = code;
  if (fmt)
    {
      va_list ap;
      char *str = NULL;
      size_t len = 0;
      
      va_start (ap, fmt);
      grecs_vasprintf (&str, &len, fmt, ap);
      va_end (ap);

      io->output.reply = json_error_reply_create (code, str);
      grecs_free (str);
    }
  else
    io->output.reply = json_error_reply_create (code, http_text (code));
}

static void
ctlio_print (struct ctlio *io, const char *text)
{
  ctlbuf_write (&io->obuf, text, strlen (text));
}

static void
ctlio_printf (struct ctlio *io, const char *fmt, ...)
{
  va_list ap;
  char *str = NULL;
  size_t len = 0;
  
  va_start (ap, fmt);
  grecs_vasprintf (&str, &len, fmt, ap);
  va_end (ap);
  ctlio_print (io, str);
  free (str);
}

static void
ctlio_eol (struct ctlio *io)
{
  ctlio_print (io, CRLF);
}

static int
format_header (void *sym, void *data)
{
  struct http_header *h = sym;
  struct ctlio *io = data;
  ctlio_print (io, h->name);
  ctlio_print (io, ": ");
  ctlio_print (io, h->value);
  ctlio_eol (io);
  return 0;
}

static void
json_writer (void *closure, char const *text, size_t len)
{
  struct ctlbuf *buf = closure;
  ctlbuf_write (buf, text, len);
}

static void
ctlio_adjust_format (struct ctlio *io, struct json_format *fmt)
{
  char const *val = http_get_header (io->input.headers, "X-Pies-Output");
  if (!val)
    return;
  if (strcasecmp (val, "pretty") == 0)
    fmt->indent = 2;
}
  
static void
ctlio_finalize_reply (struct ctlio *io)
{
  size_t size;
  char const *val;
  struct ctlbuf tmpbuf;

  val = http_get_header (io->input.headers, "connection");
  if (val)
    {
      if (strcasecmp (val, "keep-alive") == 0)
	/* nothing */;
      else if (strcasecmp (val, "close") == 0)
	io->state = CTL_END_STATE;
    }
      
  if (io->state == CTL_END_STATE || io->state == CTL_ACTION_STATE)
    output_set_header (&io->output, "Connection", "close");
  
  ctlbuf_init (&tmpbuf);
  if (io->output.reply)
    {
      struct json_format fmt = {
	.indent = 0,
	.precision = 0,
	.write = json_writer,
	.data = &tmpbuf
      };

      ctlio_adjust_format (io, &fmt);
      json_value_format (io->output.reply, &fmt, 0);
      size = ctlbuf_rdsize (&tmpbuf);
      if (size)
	{
	  output_set_header (&io->output, "Content-Length",
			     "%lu", (unsigned long) size);
	  output_set_header (&io->output, "Content-Type", "application/json");
	}
    }

  ctlio_printf (io, "HTTP/1.1 %3d %s", io->code, http_text (io->code));
  ctlio_eol (io);
  grecs_symtab_foreach (io->output.headers, format_header, io);
  ctlio_eol (io);
  
  ctlbuf_copy (&io->obuf, &tmpbuf);
  ctlbuf_free (&tmpbuf);
}

struct auth_data
{
  union pies_sockaddr_storage addr;
  socklen_t addrlen;
  pies_identity_t id;
  struct component const *comp;
};
  
static int
try_auth (struct prog *prog, void *data)
{
  struct auth_data *auth = data;
  if (IS_COMPONENT (prog))
    {
      if ((prog->v.p.comp->adm_acl
	   && check_acl (prog->v.p.comp->adm_acl,
			 (struct sockaddr *)&auth->addr, auth->addrlen,
			 auth->id) == 0)
	  || (prog->v.p.comp->list_acl
	      && check_acl (prog->v.p.comp->list_acl,
			    (struct sockaddr *)&auth->addr, auth->addrlen,
			    auth->id) == 0))
	{
	  auth->comp = prog->v.p.comp;
	  return 1;
	}
    }
  return 0;
}

static int
do_auth (struct ctlio *io, char const *name, char const *pass)
{
  struct grecs_list_entry *ep;
  pies_identity_t id = pies_identity_create (name);
  int new_state = CTL_INITIAL_STATE;

  if (!id)
    {
      logmsg (LOG_AUTH, _("%s: can't authenticate: %s"),
	      name, strerror (errno));
      return -1;
    }
  
  for (ep = identity_provider_list->head; ep; ep = ep->next)
    {
      pies_identity_provider_t provider = ep->data;
      char const *pname = pies_identity_provider_name (provider);

      debug(1, (_("trying identity provider %s..."), pname));
      if (pies_authenticate (provider, id, pass) == 0)
	{
	  if (control.adm_acl
	      && check_acl (control.adm_acl,
			    (struct sockaddr *)&io->addr, io->addrlen,
			    id) == 0)
	    {
	      new_state = CTL_ADMIN_STATE;
	      logmsg (LOG_AUTH, _("%s granted admin access via %s"),
		      name, pname);
	    }
	  else if (control.usr_acl
		   && check_acl (control.usr_acl,
				 (struct sockaddr *)&io->addr, io->addrlen,
				 id) == 0)
	    {
	      new_state = CTL_USER_STATE;
	      logmsg (LOG_AUTH, _("%s authenticated via %s"),
		      name, pname);
	    }
	  else
	    {
	      struct auth_data ad = { io->addr, io->addrlen, id, NULL };
	      progman_foreach (try_auth, &ad);
	      if (ad.comp)
		{
		  new_state = CTL_USER_STATE;
		  logmsg (LOG_AUTH, _("%s authenticated via %s, component %s"),
			  name, pname, ad.comp->tag);
		}
	      else
		{
		  logmsg (LOG_AUTH, _("%s authenticated via %s, but failed ACL check"),
			  name, pname);
		}
	    }
	  break;
	}
    }

  if (new_state == CTL_INITIAL_STATE)
    {
      pies_identity_destroy (id);
      return 1;
    }

  identity = id;
  io->state = new_state;

  return 0;
}

static int
ctlio_authenticate (struct ctlio *io)
{
  const char *val;

  val = http_get_header (io->input.headers, "Authorization");
  if (val)
    {
      size_t len = strlen (val);
      char *data = NULL;
      idx_t datalen = 0;
      if (len > 6 && memcmp (val, "Basic ", 6) == 0)
	{
	  if (base64_decode_alloc (val + 6, len - 6, &data, &datalen))
	    {
	      char *p = strchr (data, ':');
	      if (*p)
		{
		  int result;
		  char *user;
		  char *passwd;
		  size_t s = p - data;

		  user = grecs_malloc (s + 1);
		  memcpy (user, data, s);
		  user[s] = 0;

		  passwd = grecs_malloc (datalen - s);
		  memcpy (passwd, p + 1, datalen - s - 1);
		  passwd[datalen - s - 1] = 0;

		  free(data);
		  
		  result = do_auth (io, user, passwd);
		  free(user);
		  free(passwd);
		  
		  if (result == 0)
		    return 0;
		}
	    }
	}
      ctlio_reply (io, 403, NULL);
    }
  else
    {
      output_set_header (&io->output, "WWW-Authenticate",
			 "Basic realm=\"%s\"",
			 control.realm ? control.realm : "pies");
      ctlio_reply (io, 401, NULL);
    }
  return 1;
}

enum http_method
  {
    METH_GET,
    METH_POST,
    METH_DELETE,
    METH_PUT,
    METH_OPTIONS,
    METH_INVALID
  };

#define METH_MASK(n) (1<<(n))

#define METH_MASK_GET     METH_MASK(METH_GET)
#define METH_MASK_POST    METH_MASK(METH_POST)
#define METH_MASK_DELETE  METH_MASK(METH_DELETE)
#define METH_MASK_PUT     METH_MASK(METH_PUT)
#define METH_MASK_OPTIONS METH_MASK(METH_OPTIONS)

char *method_names[] = {
  [METH_GET]     = "GET",
  [METH_POST]    = "POST",
  [METH_DELETE]  = "DELETE",
  [METH_PUT]     = "PUT",
  [METH_OPTIONS] = "OPTIONS",
  NULL
};

static int
method_decode (char const *method)
{
  int i;

  for (i = 0; method_names[i]; i++)
    {
      if (strcmp (method_names[i], method) == 0)
	return i;
    }
  return METH_INVALID;
}

static void res_instance (struct ctlio *, enum http_method, char const *,
			  struct json_value *);
static void res_programs (struct ctlio *, enum http_method, char const *,
			  struct json_value *);
static void res_alive (struct ctlio *, enum http_method, char const *,
		       struct json_value *);
static void res_conf (struct ctlio *, enum http_method, char const *,
		      struct json_value *);

#if PIES_SYSVINIT_ENABLED
static void res_runlevel (struct ctlio *, enum http_method, char const *,
			  struct json_value *);
static void res_environ  (struct ctlio *, enum http_method, char const *,
			  struct json_value *);
static int pred_sysvinit (void);
#endif


struct ctlio_resource
{
  char const *uri;
  size_t uri_len;
  int states;
  int (*predicate) (void);
  void (*handler) (struct ctlio *, enum http_method,
		   char const *uri, struct json_value *);
};

static struct ctlio_resource restab[] = {
#define S(s) #s, (sizeof (#s)-1)
  { S(/instance), CTL_ADMIN_STATE, NULL, res_instance },
  { S(/conf),     CTL_ADMIN_STATE, NULL, res_conf },
  { S(/programs), CTL_ADMIN_STATE|CTL_USER_STATE, NULL,
    res_programs },
  { S(/alive),    CTL_ADMIN_STATE|CTL_USER_STATE, NULL,
    res_alive },
#if PIES_SYSVINIT_ENABLED
  { S(/runlevel), CTL_ADMIN_STATE, pred_sysvinit, res_runlevel },
  { S(/environ),  CTL_ADMIN_STATE, pred_sysvinit, res_environ },
#endif
  { NULL }
#undef S
};

static struct ctlio_resource *
find_resource (struct ctlio *io, const char *endpoint)
{
  struct ctlio_resource *p;
  size_t len = strcspn (endpoint, "?");

  if (len == 0)
    return NULL;
  
  if (endpoint[len-1] == '/')
    --len;
  
  for (p = restab; p->uri; p++)
    if (len >= p->uri_len && memcmp (p->uri, endpoint, p->uri_len) == 0
	&& (endpoint[p->uri_len] == 0
	    || endpoint[p->uri_len] == '/'
	    || endpoint[p->uri_len] == '?')
	&& (!p->predicate || p->predicate()))
      return p;
  return NULL;
}

static char *
json_extract (char *uri)
{
  char *p = strchr (uri, '?');
  if (p)
    {
      *p++ = 0;
      return p;
    }
  return NULL;
}

static size_t
delim_count (char const *str, int delim)
{
  size_t i = 0;
  for (; *str; str++)
    if (*str == delim)
      ++i;
  return i;
}

static int
check_accepted_media (struct ctlio *io, char const *type, char const *subtype)
{
  char const *val;
  struct wordsplit ws;
  int rc = 1;
  size_t i;
  
  val = http_get_header (io->input.headers, "Accept");
  if (!val)
    return 0;

  ws.ws_delim = ",";
  if (wordsplit (val, &ws,
		 WRDSF_NOCMD | WRDSF_NOVAR | WRDSF_DELIM | WRDSF_WS
		 | WRDSF_DQUOTE))
    {
      logmsg (LOG_ERR, "wordsplit: %s", wordsplit_strerror (&ws));
      ctlio_reply (io, 500, NULL);
      return -1;
    }

  for (i = 0; i < ws.ws_wordc; i++)
    {
      char *p = strchr (ws.ws_wordv[i], ';');
      if (p)
	{
	  while (p > ws.ws_wordv[i] && ISWS (p[-1]))
	    --p;
	  *p = 0;
	}

      p = strchr (ws.ws_wordv[i], '/');
      if (!p)
	continue;

      *p++ = 0;

      if ((strcmp (ws.ws_wordv[i], "*") == 0
	   || strcmp (ws.ws_wordv[i], type) == 0)
	  && (strcmp (p, "*") == 0
	      || strcmp (p, subtype) == 0))
	{
	  rc = 0;
	  break;
	}
    }

  wordsplit_free (&ws);

  if (rc)
    ctlio_reply (io, 406, NULL);
  
  return rc;
}

static void
ctlio_do_command (struct ctlio *io)
{
  const char *val;
  struct ctlio_resource *res;
  enum http_method method;
  struct json_value *json;
  char *uri = io->input.input_uri;
  char *json_query = NULL;
  
  if (strcmp (io->input.input_proto, "HTTP/1.1"))
    {
      ctlio_reply (io, 505, NULL);
      return;
    }

  val = http_get_header (io->input.headers, "Content-Type");
  if (val)
    {
      size_t len = strcspn (val, ";");
      if (strncmp (val, "application/json", len))
	{
	  ctlio_reply (io, 415, "Unsupported content type");
	  return;
	}
    }

  if (check_accepted_media (io, "application", "json"))
    return;

  method = method_decode (io->input.input_method);
  if (method == METH_INVALID)
    {
      ctlio_reply (io, 405, NULL);
      return;
    }
  
  res = find_resource (io, uri);
  if (!res)
    {
      ctlio_reply (io, 404, "resource not found");
      return;
    }
  uri += res->uri_len;
  if (uri[0])
    {
      json_query = json_extract (uri);
      if (delim_count (uri, '/') > 1)
	{
	  ctlio_reply (io, 404, "resource not found");
	  return;
	}
    }
  if (!uri[0])
    uri = NULL;
  
  if (!(io->state & res->states))
    {
      if (ctlio_authenticate (io))
	return;
      if (!(io->state & res->states))
	{
	  ctlio_reply (io, 403, NULL);
	  return;
	}
    }

  if (io->input.input_content_length)
    {
      char const *errmsg;
      if (json_query)
	{
	  ctlio_reply (io, 400, "JSON query supplied twice");
	  return;
	}
	    
      json = grecs_json_parse_string (ctlbuf_peek (&io->input.ibuf),
				      ctlbuf_rdsize (&io->input.ibuf),
				      &errmsg, NULL);
      if (!json)
	{
	  ctlio_reply (io, 400, "JSON error: %s", errmsg);
	  return;
	}
    }
  else if (json_query)
    {
      if (method == METH_POST)
	{
	  ctlio_reply (io, 400, NULL);
	  return;
	}
      else
	{
	  char const *errmsg;
	  json = grecs_json_parse_string (json_query, strlen (json_query),
					  &errmsg, NULL);
	  if (!json)
	    {
	      ctlio_reply (io, 400, "JSON error: %s", errmsg);
	      return;
	    }
	}
    }      
  else
    json = json_new_bool (1);
  
  res->handler (io, method, uri, json);
  json_value_free (json);
}

static int ctlrd (int fd, void *data);
static int ctlwr (int fd, void *data);

static int
ctlrd (int fd, void *data)
{
  ssize_t n;
  char c;
  struct ctlio *io = data;

  n = read (fd, &c, 1);
  //  logmsg (LOG_DEBUG, "%s called: %d,%d", __FUNCTION__, n,c);
  if (n == 1)
    {
      if (c == EOT)
	return ctlio_end (fd, io);
      else
	{
	  int rc = input_append (&io->input, c);
	  if (rc == 0)
	    return 0;

	  if (rc == 200)
	    ctlio_do_command (io);
	  else
	    ctlio_reply (io, rc, NULL);

	  ctlio_finalize_reply (io);

	  ctlio_reset (io);
	  
	  update_socket (fd, PIES_EVT_RD, NULL);
	  update_socket (fd, PIES_EVT_WR, ctlwr);
	}    
    }
  else 
    {
      if (n == -1)
	logmsg (LOG_ERR, _("error reading from control socket: %s"),
		strerror (errno));
      return ctlio_end (fd, io);
    }
  
  return 0;
}

static int
ctlwr (int fd, void *data)
{
  char c;
  struct ctlio *io = data;
  // logmsg (LOG_DEBUG, "%s called", __FUNCTION__);
  if (ctlbuf_read (&io->obuf, &c, 1))
    {
      ssize_t rc = write (fd, &c, 1);
      if (rc != 1)
	{
	  if (rc == 0)
	    logmsg (LOG_ERR, _("error writing to control socket"));
	  else
	    logmsg (LOG_ERR, _("error writing to control socket: %s"),
		    strerror (errno));
	  return ctlio_end (fd, io);
	}
    }
  else if (io->state == CTL_END_STATE)
    return ctlio_end (fd, io);
  else if (io->state == CTL_ACTION_STATE)
    {
      pies_schedule_action (io->action);
      return ctlio_end (fd, io);
    }
  else
    {
      update_socket (fd, PIES_EVT_WR, NULL);
      update_socket (fd, PIES_EVT_RD, ctlrd);
      ctlbuf_flush (&io->obuf);
    }
  return 0;
}

static int
ctl_accept (int socket, void *data)
{
  int fd;
  union pies_sockaddr_storage addr;
  socklen_t addrlen = sizeof addr;
  struct ctlio *io;
  
  fd = accept (socket, (struct sockaddr*) &addr, &addrlen);
  if (fd == -1)
    {
      logfuncall ("accept", NULL, errno);
      return 1;
    }
  
  if (debug_level >= 1)
    {
      char *s = sockaddr_to_astr ((struct sockaddr *)&addr, addrlen);
      logmsg (LOG_DEBUG, _("%s wants %s"), s, _("control socket"));
      free (s);
    }

  if (check_acl (control.conn_acl, (struct sockaddr *)&addr, addrlen, NULL))
    {
      close (fd);
      return 1;
    }
  /* FIXME: Check number of connections? */

  io = ctlio_create ();
  io->addr = addr;
  io->addrlen = addrlen;
  register_socket (fd, NULL, ctlwr, NULL, io, NULL);

  return 0;
}

int
ctl_open (void)
{
  int fd;

  if (!control.url)
    return 0;
  
  fd = create_socket (control.url, SOCK_STREAM, NULL, 077);
  if (fd == -1)
    {
      logmsg (LOG_CRIT, _("can't create control socket %s"),
	      control.url->string);
      return -1;
    }

  if (listen (fd, 8))
    {
      logfuncall ("listen", control.url->string, errno);
      return -1;
    }

  register_socket (fd, ctl_accept, NULL, NULL, NULL, NULL);
  
  return 0;
}

static void
idfmt_string (struct ctlio *io, char const *name, void *ptr)
{
  json_object_set_string (io->output.reply, name, "%s", (char*) ptr);
}

static void
idfmt_string_ptr (struct ctlio *io, char const *name, void *ptr)
{
  char **str = ptr;
  json_object_set_string (io->output.reply, name, "%s", *str);
}

static void
idfmt_pid (struct ctlio *io, char const *name, void *ptr)
{
  json_object_set_number (io->output.reply, name, getpid ());
}

static void
idfmt_argv (struct ctlio *io, char const *name, void *ptr)
{
  struct json_value *ar = json_new_array ();
  size_t i;

  for (i = 0; i < pies_master_argc; i++)
    json_array_append (ar, json_new_string (pies_master_argv[i]));
  json_object_set (io->output.reply, name, ar);
}

static void
idfmt_binary (struct ctlio *io, char const *name, void *ptr)
{
  json_object_set_string (io->output.reply, name, "%s", pies_master_argv[0]);
}

static void
res_instance (struct ctlio *io, enum http_method meth,
	      char const *uri, struct json_value *req)
{
  static struct idparam {
    char *name;
    void (*fmt) (struct ctlio *, char const *, void *);
    void *data;
  } idparam[] = {
    { "package",  idfmt_string, PACKAGE_NAME },
    { "version",  idfmt_string, PACKAGE_VERSION },
    { "binary",   idfmt_binary, NULL },
    { "PID",      idfmt_pid, NULL },
    { "instance", idfmt_string_ptr, &instance },
    { "argv",     idfmt_argv, NULL },
    { NULL }
  };
  struct idparam *p;
  
  io->output.reply = json_reply_create ();
  if (uri)
    {
      ++uri; /* skip leading / */
      if (meth == METH_GET)
	{
	  for (p = idparam; p->name; p++)
	    if (strcmp (p->name, uri) == 0)
	      break;
	  if (p->name)
	    {
	      p->fmt (io, p->name, p->data);
	      io->code = 200;
	    }	
	  else
	    ctlio_reply (io, 404, NULL);
	}
      else if (strcmp (uri, "PID") == 0 || strcmp (uri, "instance") == 0)
	{
	  if (meth == METH_DELETE)
	    {
	      json_object_set_string (io->output.reply, "status", "OK");
	      io->action = ACTION_STOP;
	      io->state = CTL_ACTION_STATE;
	      io->code = 200;
	    }
	  else if (meth == METH_POST || meth == METH_PUT)
	    {
	      json_object_set_string (io->output.reply, "status", "OK");
	      io->action = ACTION_RESTART;
	      io->state = CTL_ACTION_STATE;
	      io->code = 201;
	    }
	  else
	    ctlio_reply (io, 405, NULL);
	}
      else
	ctlio_reply (io, 405, NULL);
    }
  else if (meth != METH_GET)
    ctlio_reply (io, 405, NULL);
  else
    {
      for (p = idparam; p->name; p++)
	p->fmt (io, p->name, p->data);
      io->code = 200;
    }
}

struct eval_env
{
  struct ctlio *io;
  int allowed_state;
  struct pcond_node *cond;
  int (*fun) (struct json_value *, struct prog *);
  struct json_value *result;
  size_t total_count;
  size_t success_count;
  size_t noperm_count;
};

static int
auth_prog (struct prog *prog, struct ctlio *io)
{
  if (io->state == CTL_ADMIN_STATE)
    return CTL_ADMIN_STATE|CTL_USER_STATE;
  switch (prog->type)
    {
    case TYPE_COMPONENT:
      if (prog->v.p.comp->adm_acl
	  && check_acl (prog->v.p.comp->adm_acl,
			(struct sockaddr *)&io->addr, io->addrlen,
			identity) == 0)
	return CTL_ADMIN_STATE;
      if (check_acl (prog->v.p.comp->list_acl,
		     (struct sockaddr *)&io->addr, io->addrlen,
		     identity) == 0)
	return CTL_USER_STATE;
    default:
      break;
    }
  return 0;
}

static char * const pies_type_str[] = {
  [TYPE_COMPONENT]   = "component", 
  [TYPE_COMMAND]     = "command"
};

static char * const pies_comp_mode_str[] = {
  [pies_comp_exec]         = "exec",        
  [pies_comp_accept]       = "accept",       
  [pies_comp_inetd]        = "inetd",        
  [pies_comp_pass_fd]      = "pass_fd",
  [pies_comp_startup]      = "startup",
  [pies_comp_shutdown]     = "shutdown",
  [pies_comp_wait]         = "wait",          
  [pies_comp_once]         = "once",         
  [pies_comp_boot]         = "boot",         
  [pies_comp_bootwait]     = "bootwait",     
  [pies_comp_powerfail]    = "powerfail",    
  [pies_comp_powerwait]    = "powerwait",    
  [pies_comp_powerokwait]  = "powerokwait",  
  [pies_comp_ctrlaltdel]   = "ctrlaltdel",        
  [pies_comp_ondemand]     = "ondemand",     
  [pies_comp_sysinit]      = "sysinit",      
  [pies_comp_powerfailnow] = "powerfailnow", 
  [pies_comp_kbrequest]    = "kbrequest"
};

static char * const pies_status_str[] = {
  [status_stopped]  = "stopped",
  [status_running]  = "running", 
  [status_listener] = "listener",
  [status_sleeping] = "sleeping",
  [status_stopping] = "stopping",
  [status_finished] = "finished"
};

static void
format_idx (struct json_value *obj, const char *name, unsigned i,
	    char * const *a, size_t s)
{
  if (i < s && a[i])
    json_object_set (obj, name, json_new_string (a[i]));
}

#define FORMAT_IDX(io,n,a,i)				\
  format_idx (io, n, i, a, sizeof(a)/sizeof(a[0]))

enum pcond_type
  {
    pcond_false,
    pcond_true,
    pcond_component,
    pcond_type,
    pcond_mode,
    pcond_active,
    pcond_status,
    pcond_not,
    pcond_and,
    pcond_or
  };

struct pcond_node
{
  enum pcond_type type;
  union
  {
    char *tag;
    enum pies_comp_mode mode;
    enum prog_status status;
    enum prog_type type;
    struct 
    {
      size_t c;
      struct pcond_node **v;
    } arg;
  } v;
};

static struct pcond_node *
pcond_node_alloc (enum pcond_type t)
{
  struct pcond_node *pn;

  pn = grecs_zalloc (sizeof (*pn));
  pn->type = t;
  return pn;
}

static void
pcond_node_free (struct pcond_node *node)
{
  if (!node)
    return;
  switch (node->type)
    {
    case pcond_not:
    case pcond_and:
    case pcond_or:
      {
	size_t i;
	
	for (i = 0; i < node->v.arg.c; i++)
	  pcond_node_free (node->v.arg.v[i]);
      }
      grecs_free (node->v.arg.v);
      break;
      
    default:
      break;
    }
  grecs_free (node);
}

static int
pcond_eval (struct pcond_node *node, struct prog *p)
{
  size_t i;
  
  if (!node)
    return 0;
  switch (node->type)
    {
    case pcond_true:
      return 1;

    case pcond_false:
      return 0;
      
    case pcond_component:
      return strcmp (prog_tag (p), node->v.tag) == 0;

    case pcond_type:
      return p->type == node->v.type;
      
    case pcond_mode:
      return IS_COMPONENT (p) && p->v.p.comp->mode == node->v.mode;

    case pcond_active:
      return p->active;
      
    case pcond_status:
      return IS_COMPONENT (p) && p->v.p.status == node->v.status;
      
    case pcond_not:
      return !pcond_eval (node->v.arg.v[0], p);
      
    case pcond_and:
      for (i = 0; i < node->v.arg.c; i++)
	if (!pcond_eval (node->v.arg.v[i], p))
	  return 0;
      return 1;
	  
    case pcond_or:
      for (i = 0; i < node->v.arg.c; i++)
	if (pcond_eval (node->v.arg.v[i], p))
	  return 1;
      return 0;

    default:
      abort ();
    }
}

static int json_to_pcond (struct ctlio *io, struct json_value *val,
			  struct pcond_node **pnode);

static int
pcond_conv_component (struct pcond_node *node, struct json_value *arg,
		      struct ctlio *io)
{
  if (arg->type != json_string)
    {
      ctlio_reply (io, 400, "component must be string");
      return -1;
    }
  node->v.tag = arg->s;
  return 0;
}

static int
pcond_conv_n (struct pcond_node *node, struct json_value *arg,
	      struct ctlio *io, char * const *ar, const char *what)
{
  int n;
  
  if (arg->type != json_string)
    {
      ctlio_reply (io, 400, "%s must be string", what);
      return -1;
    }
  n = array_index (ar, arg->s);
  if (n == -1)
    {
      ctlio_reply (io, 400, "bad %s", what);
      return -1;
    }
  return n;
}

static int
pcond_conv_type (struct pcond_node *node, struct json_value *arg,
		 struct ctlio *io)
{
  int n = pcond_conv_n (node, arg, io, pies_type_str, "type");
  if (n == -1)
    return -1;
  node->v.type = n;
  return 0;
}

static int
pcond_conv_mode (struct pcond_node *node, struct json_value *arg,
		 struct ctlio *io)
{
  int n = pcond_conv_n (node, arg, io, pies_type_str, "mode");
  if (n == -1)
    return -1;
  node->v.mode = n;
  return 0;
}

static int
pcond_conv_status (struct pcond_node *node, struct json_value *arg,
		   struct ctlio *io)
{
  int n = pcond_conv_n (node, arg, io, pies_status_str, "status");
  if (n == -1)
    return -1;
  node->v.status = n;
  return 0;
}

static int
pcond_conv_not (struct pcond_node *node, struct json_value *arg,
		struct ctlio *io)
{
  node->v.arg.v = grecs_calloc (1, sizeof (node->v.arg.v[0]));
  node->v.arg.c = 1;
  return json_to_pcond (io, arg, &node->v.arg.v[0]);
}

static int
pcond_conv_binary (struct pcond_node *node, struct json_value *arg,
		   struct ctlio *io)
{
  size_t i, n;
  
  if (arg->type != json_array)
    {
      ctlio_reply (io, 400, "arguments to binary opcode must be array");
      return -1;
    }

  n = json_array_length (arg);
  node->v.arg.v = grecs_calloc (n, sizeof (node->v.arg.v[0]));
  node->v.arg.c = n;
				
  for (i = 0; i < n; i++)
    {
      struct json_value *v;
      if (json_array_get (arg, i, &v) == 0)
	{
	  if (json_to_pcond (io, v, &node->v.arg.v[i]))
	    return -1;
	}
    }
  return 0;
}

struct pcond_conv {
  char *term;
  int (*handler) (struct pcond_node *, struct json_value *arg, struct ctlio *);
};

static struct pcond_conv pcond_conv[] = {
  [pcond_component] = { "component", pcond_conv_component },
  [pcond_type]      = { "type", pcond_conv_type },
  [pcond_mode]      = { "mode", pcond_conv_mode },
  [pcond_active]    = { "active", NULL },
  [pcond_status]    = { "status", pcond_conv_status },
  [pcond_not]       = { "not", pcond_conv_not },
  [pcond_and]       = { "and", pcond_conv_binary },
  [pcond_or]        = { "or", pcond_conv_binary },
};

static int max_type = sizeof (pcond_conv) / sizeof (pcond_conv[0]);

static int
pcond_conv_find (char const *name)
{
  int i;
  for (i = 0; i < max_type; i++)
    {
      if (pcond_conv[i].term && strcmp (pcond_conv[i].term, name) == 0)
	return i;
    }
  return -1;
}

static int
object_to_cond (struct ctlio *io, struct json_value *obj,
		struct pcond_node **pnode)
{
  struct json_value *op, *arg;
  struct pcond_node *node;
  int type;
  
  if (json_object_get (obj, "op", &op))
    {
      ctlio_reply (io, 400, "bad conditional: missing op");
      return -1;
    }
  
  if (op->type != json_string)
    {
      ctlio_reply (io, 400, "bad conditional: bad op type");
      return -1;
    }

  type = pcond_conv_find (op->s);
  if (type == -1)
    {
      ctlio_reply (io, 400, "bad conditional: unknown opcode");
      return -1;
    }

  if (json_object_get (obj, "arg", &arg) && pcond_conv[type].handler)
    {
      ctlio_reply (io, 400, "bad conditional: missing arg");
      return -1;
    }

  node = pcond_node_alloc (type);
  if (pcond_conv[type].handler && pcond_conv[type].handler (node, arg, io))
    {
      pcond_node_free (node);
      return -1;
    }
  *pnode = node;
  return 0;
}
  
static int
json_to_pcond (struct ctlio *io, struct json_value *val,
	       struct pcond_node **pnode)
{
  if (!val)
    *pnode = pcond_node_alloc (pcond_true);
  else
    switch (val->type)
      {
      case json_null:
	*pnode = NULL;
	break;
	
      case json_bool:
	*pnode = pcond_node_alloc (val->b ? pcond_true : pcond_false);
	break;
	
      case json_long:
      case json_double:
      case json_string:
      case json_array:
	ctlio_reply (io, 400, "bad conditional");
	return -1;
	
      case json_object:
	return object_to_cond (io, val, pnode);
      }
  return 0;
}

static int
selector (struct prog *prog, void *data)
{
  struct eval_env *env = data;

  if (pcond_eval (env->cond, prog))
    {
      if (auth_prog (prog, env->io) & env->allowed_state)
	{
	  struct json_value *status = json_new_object ();
	  json_object_set_string (status, "tag", prog_tag (prog));
	  if (env->fun (status, prog) == 0)
	    env->success_count++;
	  json_array_append (env->result, status);
	  env->total_count++;
	}
      else
	env->noperm_count++;
    }
  return 0;
}

/* Return true if PROG is active.  For TCPMUX progs, this takes into account
   the state of the master listener.
   FIXME: Ideally all TCPMUXen must form a list attached to their master
   listener.  When this is fixed, this function will disappear. */
static int
prog_active (struct prog *prog)
{
  if (prog->active)
    {
      if (ISCF_TCPMUX (prog->v.p.comp->flags))
	{
	  prog = progman_locate (prog->v.p.comp->tcpmux);
	  return prog && prog->active;
	}
    }
  return prog->active;
}

static struct json_value *
prog_serialize (struct json_value *ret, struct prog *prog)
{
  struct json_value *v;
  size_t i;
  
  FORMAT_IDX (ret, "type", pies_type_str, prog->type);
  switch (prog->type)
    {
    case TYPE_COMPONENT:
      FORMAT_IDX (ret, "mode", pies_comp_mode_str, prog->v.p.comp->mode);
      FORMAT_IDX (ret, "status", pies_status_str, prog->v.p.status);

      json_object_set_bool (ret, "active", prog_active (prog));
      
      if (prog->pid)
	json_object_set_number (ret, "PID", prog->pid);
      else if (prog->v.p.status == status_listener)
	{
	  if (prog->v.p.comp->socket_url)
	    json_object_set_string (ret, "URL", "%s",
				    prog->v.p.comp->socket_url->string);
	  if (prog->v.p.comp->service)
	    json_object_set_string (ret, "service", "%s",
				    prog->v.p.comp->service);
 	  if (ISCF_TCPMUX (prog->v.p.comp->flags) && prog->v.p.comp->tcpmux)
	    json_object_set_string (ret, "master", "%s",
				    prog->v.p.comp->tcpmux);
	}
      
      
      if (prog->v.p.comp->runlevels)
	json_object_set_string (ret, "runlevels", "%s",
				prog->v.p.comp->runlevels);
      
      if (prog->v.p.status == status_sleeping)
	json_object_set_number (ret, "wakeup-time", 
				prog->v.p.timestamp + SLEEPTIME);

      v = json_new_array ();
      for (i = 0; i < prog->v.p.argc; i++)
	json_array_append (v, json_new_string (prog->v.p.argv[i]));
      json_object_set (ret, "argv", v);
      break;
	  
    case TYPE_COMMAND:
      json_object_set_string (ret, "command", "%s", prog->v.c.command);
    }
  return ret;
}

static int
fun_list (struct json_value *result, struct prog *prog)
{
  prog_serialize (result, prog);
  return 0;
}

static int
fun_stop (struct json_value *result, struct prog *prog)
{
  if (!prog->active)
    {      
      json_object_set_string (result, "status", "ER");
      json_object_set_string (result, "message", "already stopped");
    }
  else
    {
      prog->active = 0;
      progman_stop_component (&prog);
      json_object_set_string (result, "status", "OK");
    }
  return 0;
}

static int
fun_start (struct json_value *result, struct prog *prog)
{
  if (!IS_COMPONENT (prog))
    {
      json_object_set_string (result, "status", "ER");
      json_object_set_string (result, "message", "not a component");
    }
  else
    {
      switch (prog->v.p.status)
	{
	case status_stopped:
	  prog->v.p.comp->flags &= ~CF_DISABLED;
	  json_object_set_string (result, "status", "OK");
	  break;

	case status_sleeping:
	case status_finished:
	  prog->v.p.status = status_stopped;
	  prog->v.p.failcount = 0;
	  prog->v.p.timestamp = 0;
	  json_object_set_string (result, "status", "OK");
	  break;

	case status_listener:
	  if (prog_activate_listener (prog) == 0)
	    json_object_set_string (result, "status", "OK");
	  else
	    {
	      json_object_set_string (result, "status", "ER");
	      /* FIXME: detailed error message */
	      json_object_set_string (result, "message", "can't open socket");
	    }
	  break;
      
	default:
	  json_object_set_string (result, "status", "ER");
	  json_object_set_string (result, "message", "already running");
	  return 0;
	}
      prog->active = 1;
    }
  return 0;
}

static int
fun_restart (struct json_value *result, struct prog *prog)
{
  if (!IS_COMPONENT (prog))
    {
      json_object_set_string (result, "status", "ER");
      json_object_set_string (result, "message", "not a component");
    }
  else if (!prog->active)
    {
      json_object_set_string (result, "status", "ER");
      json_object_set_string (result, "message", "not active");
    }
  else
    {
      switch (prog->v.p.status)
	{
	case status_running:
	  progman_stop_component (&prog);
	  json_object_set_string (result, "status", "OK");
	  break;
	  
      	case status_listener:
	  json_object_set_string (result, "status", "ER");
	  json_object_set_string (result, "message", "not applicable");
	  break;
      
	default:
	  json_object_set_string (result, "status", "ER");
	  json_object_set_string (result, "message", "not running");
	}
    }
  return 0;
}

static void
select_and_run (struct ctlio *io, struct pcond_node *cond,
		enum http_method meth)
{
  struct eval_env env;
  int wakeup = 0;

  memset (&env, 0, sizeof (env));
  env.io = io;
  env.cond = cond;
  env.fun = NULL;
  env.result = NULL;

  switch (meth)
    {
    case METH_GET:
      env.allowed_state = CTL_USER_STATE | CTL_ADMIN_STATE;
      env.fun = fun_list;
      break;

    case METH_DELETE:
      env.allowed_state = CTL_ADMIN_STATE;
      env.fun = fun_stop;
      break;

    case METH_PUT:
      env.allowed_state = CTL_ADMIN_STATE;
      env.fun = fun_start;
      wakeup = 1;
      break;

    case METH_POST:
      env.allowed_state = CTL_ADMIN_STATE;
      env.fun = fun_restart;
      wakeup = 1;
      break;

    default:
      ctlio_reply (io, 405, NULL);
    }

  if (env.fun)
    {
      env.result = json_new_array ();
      progman_foreach (selector, &env);

      if (env.success_count == 0 && env.noperm_count)
	ctlio_reply (io, 403, NULL);
      else
	{
	  if (env.success_count && wakeup)
	    progman_wake_sleeping (1);
	  io->output.reply = env.result;
	  io->code = 200;
	}
    }
}

static void
res_programs_select (struct ctlio *io, enum http_method meth,
		     char const *uri, struct json_value *json)
{
  struct pcond_node *cond;

  if (json_to_pcond (io, json, &cond) == 0)
    {
      select_and_run (io, cond, meth);
      pcond_node_free (cond);
    }
}

static void
res_programs_component (struct ctlio *io, enum http_method meth,
			char const *uri, struct json_value *json)
{
  struct pcond_node *np, *node;

  node = pcond_node_alloc (pcond_type);
  node->v.type = TYPE_COMPONENT;

  np = pcond_node_alloc (pcond_and);
  np->v.arg.c = 2;
  np->v.arg.v = grecs_calloc (np->v.arg.c, sizeof (np->v.arg.v[0]));
  np->v.arg.v[0] = node;
  np->v.arg.v[1] = pcond_node_alloc (pcond_component);
  np->v.arg.v[1]->v.tag = (char*) uri + 1;
  node = np;
  
  select_and_run (io, node, meth);
  pcond_node_free (node);
}  

static void
res_programs (struct ctlio *io, enum http_method meth,
	      char const *uri, struct json_value *json)
{
  if (uri)
    res_programs_component (io, meth, uri, json);
  else
    res_programs_select (io, meth, uri, json);
}

static void
res_alive (struct ctlio *io, enum http_method meth, char const *uri,
	   struct json_value *json)
{
  struct prog *prog;
	
  if (!uri)
    {
      ctlio_reply (io, 403, NULL);
      return;
    }

  prog = progman_locate (uri + 1);

  if (prog && IS_COMPONENT (prog))
    {
      switch (prog->v.p.status)
	{
	case status_running:
	case status_listener:
	  /* FIXME: Presumably these three fall into that category too:
	     status_stopped   -- Component is stopped administratively
	     status_stopping  -- Component is being stopped
	     status_finished  -- A "once" or "startup" component has finished
	  */
	  ctlio_reply (io, 200, NULL);
	  break;

	case status_sleeping:
	  {
	    time_t t;
	    char buf[80];

	    t = time (NULL) - prog->v.p.timestamp;
	    if (t <= SLEEPTIME)
	      {
		snprintf (buf, sizeof buf, "%lu", SLEEPTIME - t);
		output_set_header (&io->output, "Retry-After", buf);
	      }
	  }
	  /* fall through */
	  
	default:	  
	  ctlio_reply (io, 503, NULL);
	  break;
	}
    }
  else
    ctlio_reply (io, 404, NULL);
}

#if PIES_SYSVINIT_ENABLED
static int
pred_sysvinit (void)
{
  return init_process;
}

static void
res_runlevel (struct ctlio *io, enum http_method meth,
	      char const *uri, struct json_value *json)
{
  if (uri)
    ctlio_reply (io, 404, NULL);
  else if (meth == METH_GET)
    {
      io->output.reply = json_reply_create ();
      io->code = 200;
      sysvinit_report (io->output.reply);
    }
  else if (meth == METH_PUT)
    {
      struct json_value *val;
      // { "runlevel": "3" }
      if (json_object_get (json, "runlevel", &val))
	ctlio_reply (io, 400, "missing runlevel request");
      else if (val->type != json_string)
	ctlio_reply (io, 400, "bad runlevel type");
      else
	{
	  io->output.reply = json_reply_create ();
	  io->code = 200;
	  if (strlen (val->s) == 1
	      && sysvinit_set_runlevel (val->s[0]) == 0)
	    {
	      json_object_set_string (io->output.reply, "status", "OK");
	    }
	  else
	    {
	      json_object_set_string (io->output.reply, "status", "ER");
	      json_object_set_string (io->output.reply, "message",
				      "invalid runlevel value");
	    }
	}
    }
  else
    ctlio_reply (io, 405, NULL);
}

/* GET /environ          -     List entire environment
 *   ["RUNLEVEL=3", "CONSOLE=/dev/tty", ...]  
 * GET /environ/NAME     -     Get value of variable NAME
 *   { "status":"OK", "value":"..." }
 *   { "status":"ER", "message":"..." }
 * DELETE /environ/NAME  -     Unset variable
 *   { "status":"OK" }
 *   { "status":"ER", "message":"..." }
 * PUT /environ/NAME=VALUE -   Set variable 
 *   { "status":"OK" }
 *   { "status":"ER", "message":"..." }
 */
static void
env_reply (struct ctlio *io, int ok, int rc)
{
  switch (rc)
    {
    case 0:
      io->code = ok;
      io->output.reply = json_reply_create ();
      json_object_set_string (io->output.reply, "status", "OK");
      break;

    case 1:
      ctlio_reply (io, 403, NULL);
      break;
      
    case -1:
      ctlio_reply (io, 404, NULL);
    }
}

static void
res_environ (struct ctlio *io, enum http_method meth,
	     char const *uri, struct json_value *json)
{
  if (meth == METH_GET)
    {
      if (uri && uri[1])
	{
	  char *value;
	  
	  if (sysvinit_envlocate (uri + 1, &value) == -1)
	    ctlio_reply (io, 404, NULL);
	  else
	    {
	      env_reply (io, 200, 0);
	      json_object_set_string (io->output.reply, "value", "%s", value);
	    }
	}
      else
	{
	  size_t i;
      
	  io->output.reply = json_new_array ();
	  io->code = 200;
	  for (i = 0; sysvinit_environ_hint[i]; i++)
	    {
	      json_array_append (io->output.reply,
				 json_new_string (sysvinit_environ_hint[i]));
	    }
	}
    }
  else if (meth == METH_DELETE)
    {
      if (!(uri && uri[0]))
	ctlio_reply (io, 400, NULL);
      else
	env_reply (io, 200, sysvinit_envupdate (uri + 1));
    }
  else if (meth == METH_PUT)
    {
      if (!(uri && uri[0]))
	ctlio_reply (io, 400, NULL);
      else
	env_reply (io, 201, sysvinit_envupdate (uri + 1));
    }
  else
    ctlio_reply (io, 405, NULL);
    
}
#endif

/* GET /conf/runtime     -     List configuration
 * PUT /conf/runtime     -     Reload configuration
 * POST /conf/runtime    -     Post new configuration
 *      
 * GET /conf/files       -     List configuration files
 * DELETE /conf/files    -     Delete some or all configuration files
 *                             from the list
 * POST /conf/files      -     Install new list configuration file
 */

static void
conf_list_files (struct ctlio *io)
{
  io->output.reply = json_new_array ();
  io->code = 200;
  config_file_list_serialize (io->output.reply);
}

static void
conf_add_file (struct ctlio *io, struct json_value *json)
{
  struct json_value *v;
  struct config_syntax *synt;

  if (json->type != json_object)
    {
      ctlio_reply (io, 400, NULL);
      return;
    }
  if (json_object_get (json, "syntax", &v) || v->type != json_string)
    {
      ctlio_reply (io, 400, NULL);
      return;
    }
  synt = str_to_config_syntax (v->s);
  if (!synt)
    {
      ctlio_reply (io, 400, NULL);
      return;
    }
  if (json_object_get (json, "file", &v) || v->type != json_string)
    {
      ctlio_reply (io, 400, NULL);
      return;
    }

  config_file_add (synt, v->s);
  ctlio_reply (io, 201, NULL);
}

static void
conf_delete_files (struct ctlio *io, struct json_value *json)
{
  io->code = 200;
  io->output.reply = json_reply_create ();

  if ((json->type == json_bool && json->b == 1)
      || (json->type == json_array && json_array_length (json) == 0))
    {
      config_file_remove_all ();
      json_object_set_string (io->output.reply, "status", "OK");
      json_object_set_string (io->output.reply, "message",
			      "file list cleared");
    }
  else if (json->type == json_array)
    {
      size_t i, n, fails = 0;
      struct json_value *reply;

      n = json_array_length (json);

      /* Check request */
      for (i = 0; i < n; i++)
	{
	  struct json_value *val;
	  json_array_get (json, i, &val);
	  if (val->type != json_string)
	    {
	      json_object_set_string (io->output.reply, "status", "ER");
	      json_object_set_string (io->output.reply, "message",
				      "malformed request");
	      return;
	    }
	}

      reply = json_new_array ();
      /* Process request */
      for (i = 0; i < n; i++)
	{
	  struct json_value *val;
	  int rc;
	  
	  json_array_get (json, i, &val);
	  
	  rc = config_file_remove (val->s);
	  if (rc)
	    ++fails;
	  json_array_append (reply, json_new_bool (!rc));
	}

      if (fails == n)
	{
	  json_object_set_string (io->output.reply, "status", "ER");
	  json_object_set_string (io->output.reply, "message",
				  "no matching files found");
	  json_value_free (reply);
	}
      else
	{
	  json_object_set_string (io->output.reply, "status", "OK");
	  if (fails)
	    {
	      json_object_set_string (io->output.reply, "message",
				      "some files not removed");
	      json_object_set (io->output.reply, "result", reply);
	    }
	  else
	    json_value_free (reply);
	}
    }
}

static void (*saved_diag_fun)(grecs_locus_t const *, int, int,
			      const char *msg);
static struct json_value *messages;

static void
reload_diag_fun (grecs_locus_t const *locus, int err, int errcode,
		 const char *text)
{
  struct json_value *msg = json_new_object ();

  if (locus)
    {
      struct json_value *ar = json_new_array ();
      json_array_append (ar, json_new_string (locus->beg.file));
      json_array_append (ar, json_new_string (locus->end.file));
      json_object_set (msg, "file", ar);

      ar = json_new_array ();
      json_array_append (ar, json_new_long (locus->beg.line));
      json_array_append (ar, json_new_long (locus->end.line));
      json_object_set (msg, "line", ar);

      ar = json_new_array ();
      json_array_append (ar, json_new_long (locus->beg.col));
      json_array_append (ar, json_new_long (locus->end.col));
      json_object_set (msg, "col", ar);

      json_object_set (msg, "error", json_new_bool (err));

      if (errcode)
	{
	  json_object_set (msg, "syserrno",
			   json_new_long (errno));
	  json_object_set (msg, "syserrstr",
			   json_new_string (strerror (errno)));
	}

      json_object_set (msg, "message", json_new_string (text));
      json_array_append (messages, msg);
    }
  
  saved_diag_fun (locus, err, errcode, text);
}

static void
conf_reload (struct ctlio *io)
{
  io->code = 200;
  io->output.reply = json_reply_create ();

  saved_diag_fun = grecs_print_diag_fun;
  grecs_print_diag_fun = reload_diag_fun;
  messages = json_new_array ();
  if (pies_reread_config ())
    {
      json_object_set_string (io->output.reply, "status", "ER");
      json_object_set_string (io->output.reply, "message",
			      "configuration syntax error");
    }
  else
    {
      pies_schedule_action (ACTION_COMMIT);
      json_object_set_string (io->output.reply, "status", "OK");
      json_object_set_string (io->output.reply, "message",
			      "reload successful");
    }
  json_object_set (io->output.reply, "parser_messages", messages);
  grecs_print_diag_fun = saved_diag_fun;
}

static void
res_conf (struct ctlio *io, enum http_method meth,
	  char const *uri, struct json_value *json)
{
  if (!uri)
    {
      ctlio_reply (io, 404, NULL);
      return;
    }
  
  ++uri; /* skip leading / */
  if (strcmp (uri, "files") == 0)
    {
      switch (meth)
	{
	case METH_GET:
	  conf_list_files (io);
	  break;
	  
	case METH_POST:
	  conf_add_file (io, json);
	  break;
	  
	case METH_DELETE:
	  conf_delete_files (io, json);
	  break;

	default:
	  ctlio_reply (io, 405, NULL);
	}
    }
  else if (strcmp (uri, "runtime") == 0)
    {
      switch (meth)
	{
	case METH_GET:
	case METH_POST:
	  ctlio_reply (io, 501, NULL);
	  break;

	case METH_PUT:
	  conf_reload (io);
	  break;
	  
	default:
	  ctlio_reply (io, 405, NULL);
	}
    }
  else 
    ctlio_reply (io, 404, NULL);
}

