summaryrefslogtreecommitdiffabout
authorSergey Poznyakoff <gray@gnu.org>2015-12-22 19:58:02 (GMT)
committer Sergey Poznyakoff <gray@gnu.org>2015-12-25 07:51:38 (GMT)
commitfcace3930fc35a1b7beea75dba0cd48a5b229b21 (patch) (side-by-side diff)
tree00be6f5c1080290ba788318219026f0a0c943e20
parentd479bd2f63445524253e16d8575a0592c266908d (diff)
downloadpies-fcace3930fc35a1b7beea75dba0cd48a5b229b21.tar.gz
pies-fcace3930fc35a1b7beea75dba0cd48a5b229b21.tar.bz2
REST control protocol.
* configure.ac (GRECS_SETUP): Add json * gnulib.modules: Add base64. * grecs: Upgrade. * src/ctl.c: Rewrite as REST.
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--configure.ac2
-rw-r--r--gnulib.modules1
m---------grecs0
-rw-r--r--src/ctl.c1729
4 files changed, 986 insertions, 746 deletions
diff --git a/configure.ac b/configure.ac
index 8fb1eac..1e5ba2a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -72,7 +72,7 @@ AC_CHECK_FUNCS([alarm dup2 gethostbyname memmove memset select setenv socket str
gl_INIT
# Grecs
-GRECS_SETUP([grecs],[tests git2chg getopt])
+GRECS_SETUP([grecs],[tests git2chg getopt json])
GRECS_HOST_PROJECT_INCLUDES='-I$(top_srcdir)/gnu -I$(top_builddir)/gnu'
# Test for setproctitle
diff --git a/gnulib.modules b/gnulib.modules
index 1e01a93..2612e75 100644
--- a/gnulib.modules
+++ b/gnulib.modules
@@ -1,6 +1,7 @@
# List of gnulib modules needed for Pies.
# A module name per line. Empty lines and comments are ignored.
+base64
c-ctype
c-strcase
configmake
diff --git a/grecs b/grecs
-Subproject 88e00fd054cc07cb9ede045c10ebf41795e144a
+Subproject 8312f45f48ed9d995a15ee6707257f4c8946528
diff --git a/src/ctl.c b/src/ctl.c
index a640d1f..737da2b 100644
--- a/src/ctl.c
+++ b/src/ctl.c
@@ -18,6 +18,8 @@
#include "prog.h"
#include "xvasprintf.h"
#include "identity.h"
+#include "base64.h"
+#include "json.h"
#define DEFAULT_CONTROL_URL "unix:///tmp/%s.ctl"
@@ -71,6 +73,12 @@ ctlbuf_write (struct ctlbuf *buf, char const *str, size_t 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
@@ -84,6 +92,16 @@ ctlbuf_read (struct ctlbuf *buf, char *str, size_t 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)
{
@@ -101,6 +119,36 @@ ctlbuf_chomp (struct ctlbuf *buf)
}
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
@@ -110,80 +158,330 @@ ctlbuf_chomp (struct ctlbuf *buf)
#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 ctlio;
+struct http_header
+{
+ char *name;
+ char *value;
+};
-static void cmd_auth (struct ctlio *, size_t, char **);
-static void cmd_quit (struct ctlio *, size_t, char **);
-static void cmd_noop (struct ctlio *, size_t, char **);
-static void cmd_help (struct ctlio *, size_t, char **);
-static void cmd_id (struct ctlio *, size_t, char **);
-static void cmd_list (struct ctlio *, size_t, char **);
-static void cmd_start (struct ctlio *, size_t, char **);
-static void cmd_stop (struct ctlio *, size_t, char **);
-static void cmd_restart (struct ctlio *, size_t, char **);
-static void cmd_reboot (struct ctlio *, size_t, char **);
-static void cmd_shutdown (struct ctlio *, size_t, char **);
+static unsigned
+http_header_hash (void *data, unsigned long n_buckets)
+{
+ const struct http_header *p = data;
+ return grecs_hash_string (p->name, n_buckets);
+}
-struct ctlio_command
+static int
+http_header_compare (void const *data1, void const *data2)
{
- char *verb;
- char *descr;
- int states;
- int minargs;
- size_t maxargs;
- void (*handler) (struct ctlio *, size_t, char **);
+ 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;
+
+ struct wordsplit ws;
+ int wsflags;
};
-static struct ctlio_command cmdtab[] = {
- { "auth", "authenticate",
- CTL_INITIAL_STATE, 3, 3, cmd_auth },
- { "noop", "no operation",
- CTL_ALL_STATES, 1, 1, cmd_noop },
- { "id", "identify the instance",
- CTL_ADMIN_STATE, 1, 1, cmd_id },
- { "quit", "quit the session",
- CTL_ALL_STATES, 1, 1, cmd_quit },
- { "help", "display help",
- CTL_ALL_STATES, 1, 1, cmd_help },
- { "list", "list components",
- CTL_AUTHENTICATED_STATE, 1, 0, cmd_list },
- { "stop", "stop component",
- CTL_AUTHENTICATED_STATE, 2, 2, cmd_stop },
- { "start", "start component",
- CTL_AUTHENTICATED_STATE, 2, 2, cmd_start },
- { "restart", "restart component",
- CTL_AUTHENTICATED_STATE, 2, 2, cmd_restart },
- { "reboot", "restart pies",
- CTL_ADMIN_STATE, 1, 1, cmd_reboot },
- { "shutdown", "stop pies",
- CTL_ADMIN_STATE, 1, 1, cmd_shutdown },
- { NULL }
+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;
+
+ inp->ws.ws_delim = " \t()";
+ inp->wsflags = WRDSF_DELIM |
+ WRDSF_NOVAR | WRDSF_NOCMD |
+ WRDSF_QUOTE | WRDSF_RETURN_DELIMS |
+ WRDSF_WS;
+}
+
+static void
+input_destroy (struct input *inp)
+{
+ ctlbuf_free (&inp->ibuf);
+ grecs_symtab_free (inp->headers);
+ if (inp->wsflags & WRDSF_REUSE)
+ wordsplit_free (&inp->ws);
+}
+
+static void
+input_reset (struct input *inp)
+{
+ inp->input_state = is_initial;
+ grecs_symtab_clear (inp->headers);
+ inp->input_method = NULL;
+ inp->input_uri = NULL;
+ 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;
+
+ ctlbuf_chomp (&inp->ibuf);
+ rc = wordsplit (inp->ibuf.base, &inp->ws, inp->wsflags);
+ inp->wsflags |= WRDSF_REUSE;
+ if (rc)
+ {
+ logmsg (LOG_ERR, _("can't parse input line: %s"),
+ wordsplit_strerror (&inp->ws));
+ return 500;
+ }
+ if (inp->ws.ws_wordc == 3)
+ {
+ inp->input_method = inp->ws.ws_wordv[0];
+ inp->input_uri = inp->ws.ws_wordv[1];
+ inp->input_proto = inp->ws.ws_wordv[2];
+ }
+ 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;
+ }
+ 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;
+ }
+ return 0;
+}
+
+struct output
+{
+ unsigned code;
+ struct grecs_symtab *headers;
+ struct json_value *reply;
};
-static struct ctlio_command *
-ctlio_command_find (char const *verb)
+static void
+output_init (struct output *out)
{
- struct ctlio_command *cp;
+ 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;
+}
- for (cp = cmdtab; cp->verb; cp++)
- if (strcasecmp (cp->verb, verb) == 0)
- return cp;
- return 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;
+
+ 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 = xvasprintf (fmt, ap);
+ va_end (ap);
+ return 0;
}
-#define CRLF "\r\n"
+static struct json_value *
+json_reply_create (void)
+{
+ return json_new_object ();
+}
+
+static void
+json_object_set_string (struct json_value *obj,
+ char const *name, char const *fmt, ...)
+{
+ va_list ap;
+ struct json_value *val;
+
+ val = json_value_create (json_string);
+ va_start (ap, fmt);
+ val->v.s = xvasprintf (fmt, ap);
+ va_end (ap);
+ json_object_set (obj, name, val);
+}
+static struct json_value *
+json_error_reply_create (const char *msg)
+{
+ struct json_value *val;
+
+ val = json_reply_create ();
+ json_object_set_string (val, "error_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;
- struct ctlbuf ibuf;
+ int code;
struct ctlbuf obuf;
- struct wordsplit ws;
- int wsflags;
};
static struct ctlio *
@@ -192,29 +490,37 @@ ctlio_create (void)
struct ctlio *io;
io = xmalloc (sizeof (*io));
- io->state = identity_provider_list
- ? CTL_INITIAL_STATE : CTL_ADMIN_STATE;
+ 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->ibuf);
ctlbuf_init (&io->obuf);
- io->ws.ws_delim = " \t()";
- io->wsflags = WRDSF_DELIM |
- WRDSF_NOVAR | WRDSF_NOCMD |
- WRDSF_QUOTE | WRDSF_RETURN_DELIMS |
- WRDSF_WS;
return io;
}
void
ctlio_destroy (struct ctlio *io)
{
- ctlbuf_free (&io->ibuf);
+ input_destroy (&io->input);
+ output_destroy (&io->output);
ctlbuf_free (&io->obuf);
- if (io->wsflags & WRDSF_REUSE)
- wordsplit_free (&io->ws);
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)
{
@@ -224,6 +530,73 @@ ctlio_end (int fd, struct ctlio *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;
+
+ va_start (ap, fmt);
+ str = xvasprintf (fmt, ap);
+ va_end (ap);
+
+ io->output.reply = json_error_reply_create (str);
+ free (str);
+ }
+ else
+ io->output.reply = json_error_reply_create (http_text (code));
+}
+
static void
ctlio_print (struct ctlio *io, const char *text)
{
@@ -249,79 +622,79 @@ ctlio_eol (struct ctlio *io)
ctlio_print (io, CRLF);
}
-static void
-ctlio_eot (struct ctlio *io)
+static int
+format_header (void *sym, void *data)
{
- ctlio_print (io, "." CRLF);
+ 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
-ctlio_reply (struct ctlio *io, const char *code, const char *fmt, ...)
+json_writer (void *closure, char const *text, size_t len)
{
- ctlio_print (io, code);
- if (fmt)
- {
- va_list ap;
- char *str;
-
- va_start (ap, fmt);
- str = xvasprintf (fmt, ap);
- va_end (ap);
- ctlio_print (io, " ");
- ctlio_print (io, str);
- free (str);
- }
- ctlio_eol (io);
+ struct ctlbuf *buf = closure;
+ ctlbuf_write (buf, text, len);
}
static void
-ctlio_do_command (struct ctlio *io)
+ctlio_finalize_reply (struct ctlio *io)
{
- int rc;
- struct ctlio_command *cmd;
-
- ctlbuf_chomp (&io->ibuf);
- ctlbuf_flush (&io->obuf);
- rc = wordsplit (io->ibuf.base, &io->ws, io->wsflags);
- io->wsflags |= WRDSF_REUSE;
- if (rc)
- {
- logmsg (LOG_ERR, _("can't parse input line: %s"),
- wordsplit_strerror (&io->ws));
- ctlio_reply (io, "550", "parse error");
- return;
- }
+ size_t size;
+ char const *val;
+ struct ctlbuf tmpbuf;
- cmd = ctlio_command_find (io->ws.ws_wordv[0]);
- if (!cmd)
- {
- ctlio_reply (io, "500", "unknown command");
- return;
- }
- if (!(cmd->states & io->state))
+ if (io->state & (CTL_INITIAL_STATE|CTL_AUTHENTICATED_STATE))
{
- ctlio_reply (io, "510", "permission denied");
- return;
- }
- if (cmd->minargs && io->ws.ws_wordc < cmd->minargs)
- {
- ctlio_reply (io, "500", "too few arguments");
- return;
+ if (io->code / 100 == 2)
+ {
+ 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;
+ //FIXME: else?
+ }
+ }
+ else
+ io->state = CTL_END_STATE;
}
- if (cmd->maxargs && io->ws.ws_wordc > cmd->maxargs)
+
+ 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)
{
- ctlio_reply (io, "500", "too many arguments");
- return;
+ struct json_format fmt = {
+ .indent = 0,
+ .precision = -1,
+ .write = json_writer,
+ .data = &tmpbuf
+ };
+ json_format_value (io->output.reply, &fmt);
+ 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");
+ }
}
- cmd->handler (io, io->ws.ws_wordc, io->ws.ws_wordv);
-}
-static void
-ctlio_initial_reply (struct ctlio *io)
-{
- ctlio_printf (io, "220 %s", instance);
- //FIXME: auth mechanisms
+ ctlio_printf (io, "HTTP/1.1 %3d %s", io->code, http_text (io->code));
+ ctlio_eol (io);
+ grecs_symtab_enumerate (io->output.headers, format_header, io);
ctlio_eol (io);
+
+ ctlbuf_copy (&io->obuf, &tmpbuf);
+ ctlbuf_free (&tmpbuf);
}
struct auth_data
@@ -354,11 +727,11 @@ try_auth (struct prog *prog, void *data)
return 0;
}
-static void
-cmd_auth (struct ctlio *io, size_t argc, char **argv)
+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 (argv[1]);
+ pies_identity_t id = pies_identity_create (name);
int new_state = CTL_INITIAL_STATE;
for (ep = identity_provider_list->head; ep; ep = ep->next)
@@ -367,7 +740,7 @@ cmd_auth (struct ctlio *io, size_t argc, char **argv)
char const *pname = pies_identity_provider_name (provider);
debug(1, ("trying %s...", pname));
- if (pies_authenticate (provider, id, argv[2]) == 0)
+ if (pies_authenticate (provider, id, pass) == 0)
{
if (control.adm_acl
&& check_acl (control.adm_acl,
@@ -376,7 +749,7 @@ cmd_auth (struct ctlio *io, size_t argc, char **argv)
{
new_state = CTL_ADMIN_STATE;
logmsg (LOG_AUTH, "%s granted admin access via %s",
- argv[1], pname);
+ name, pname);
}
else if (control.usr_acl
&& check_acl (control.usr_acl,
@@ -385,7 +758,7 @@ cmd_auth (struct ctlio *io, size_t argc, char **argv)
{
new_state = CTL_USER_STATE;
logmsg (LOG_AUTH, "%s authenticated via %s",
- argv[1], pname);
+ name, pname);
}
else
{
@@ -395,12 +768,12 @@ cmd_auth (struct ctlio *io, size_t argc, char **argv)
{
new_state = CTL_USER_STATE;
logmsg (LOG_AUTH, "%s authenticated via %s, component %s",
- argv[1], pname, ad.comp->tag);
+ name, pname, ad.comp->tag);
}
else
{
logmsg (LOG_AUTH, "%s authenticated via %s, but failed ACL check",
- argv[1], pname);
+ name, pname);
}
}
break;
@@ -410,660 +783,219 @@ cmd_auth (struct ctlio *io, size_t argc, char **argv)
if (new_state == CTL_INITIAL_STATE)
{
pies_identity_destroy (id);
- ctlio_reply (io, "531", "access denied");
- }
- else
- {
- ctlio_reply (io, "230", "authentication successful");
- identity = id;
- io->state = new_state;
+ return 1;
}
-}
-static void
-cmd_noop (struct ctlio *io, size_t argc, char **argv)
-{
- ctlio_reply (io, "220", "%s attending", instance);
-}
+ identity = id;
+ io->state = new_state;
-static void
-cmd_quit (struct ctlio *io, size_t argc, char **argv)
-{
- ctlio_reply (io, "221", "bye");
- io->state = CTL_END_STATE;
-}
-
-static void
-cmd_id (struct ctlio *io, size_t argc, char **argv)
-{
- ctlio_reply (io, "110", "instance identification follows");
- ctlio_printf (io, "Package: %s%s", PACKAGE_NAME, CRLF);
- ctlio_printf (io, "Version: %s%s", PACKAGE_VERSION, CRLF);
-#if HAVE_DECL_PROGRAM_INVOCATION_NAME
- ctlio_printf (io, "Binary: %s%s", program_invocation_name, CRLF);
-#endif
- ctlio_printf (io, "PID: %lu%s", (unsigned long) getpid (), CRLF);
- ctlio_printf (io, "Instance: %s%s", instance, CRLF);
- ctlio_eot (io);
+ return 0;
}
-static void
-cmd_help (struct ctlio *io, size_t argc, char **argv)
+static int
+ctlio_authenticate (struct ctlio *io)
{
- struct ctlio_command *cp;
+ const char *val;
- ctlio_reply (io, "113", "help text follows");
- for (cp = cmdtab; cp->verb; cp++)
+ val = http_get_header (io->input.headers, "Authorization");
+ if (val)
{
- if (cp->states & io->state)
+ size_t len = strlen (val);
+ char *data = NULL;
+ size_t datalen = 0;
+ if (len > 6 && memcmp (val, "Basic ", 6) == 0)
{
- ctlio_printf (io, "%-9s%s", cp->verb, cp->descr);
- ctlio_eol (io);
+ 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 = xmalloc (s + 1);
+ memcpy (user, data, s);
+ user[s] = 0;
+
+ passwd = xmalloc (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);
}
- ctlio_eot (io);
-}
-
-static char const *pies_type_str[] = {
- [TYPE_COMPONENT] = "component",
- [TYPE_REDIRECTOR] = "redirector",
- [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_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 *status_str[] = {
- [status_enabled] = "enabled",
- [status_disabled] = "disabled",
- [status_listener] = "listener",
- [status_sleeping] = "sleeping",
- [status_stopping] = "stopping",
- [status_finished] = "finished"
-};
-
-static void
-format_idx (struct ctlio *io, const char *header, unsigned i,
- char const **a, size_t s)
-{
- if (i < s && a[i])
- ctlio_printf (io, "%s: %s%s", header, a[i], CRLF);
-}
-
-#define FORMAT_IDX(io,h,a,i) \
- format_idx (io, h, i, a, sizeof(a)/sizeof(a[0]))
-
-static int
-term_to_idx (char const *str, char const **a, size_t s)
-{
- size_t i;
-
- for (i = 0; i < s; i++)
- if (strcasecmp (a[i], str) == 0)
- return i;
- return -1;
+ else
+ {
+ output_set_header (&io->output, "WWW-Authenticate",
+ "Basic realm=\"%s\"", "pies"); //FIXME: Configurable realm
+ ctlio_reply (io, 401, NULL);
+ }
+ return 1;
}
-
-#define TERM_TO_IDX(str,a) term_to_idx (str, a, sizeof (a) / sizeof ((a)[0]))
-/* Prog conditionals */
-enum pcond_type
+enum http_method
{
- pcond_true,
- pcond_component,
- pcond_type,
- pcond_mode,
- pcond_status,
- pcond_not,
- pcond_and,
- pcond_or
+ METH_GET,
+ METH_POST,
+ METH_DELETE,
+ METH_PUT,
+ METH_OPTIONS,
+ METH_INVALID
};
-struct pcond_node
-{
- enum pcond_type type;
- union
- {
- char *tag;
- enum pies_comp_mode mode;
- enum prog_status status;
- enum prog_type type;
- struct pcond_node *unary;
- struct pcond_node *binary[2];
- } v;
+#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 struct pcond_node *
-pcond_node_alloc (enum pcond_type t)
-{
- struct pcond_node *pn;
-
- pn = xmalloc (sizeof (*pn));
- pn->type = t;
- return pn;
-}
-
static int
-pcond_eval (struct pcond_node *node, struct prog *p)
+method_decode (char const *method)
{
- if (!node)
- return 0;
- switch (node->type)
- {
- case pcond_true:
- return 1;
-
- case pcond_component:
- return strcmp (p->tag, 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_status:
- return IS_COMPONENT (p) && p->v.p.status == node->v.status;
-
- case pcond_not:
- return !pcond_eval (node->v.unary, p);
-
- case pcond_and:
- if (!pcond_eval (node->v.binary[0], p))
- return 0;
- return pcond_eval (node->v.binary[1], p);
-
- case pcond_or:
- if (pcond_eval (node->v.binary[0], p))
- return 1;
- return pcond_eval (node->v.binary[1], p);
+ int i;
- default:
- abort ();
- }
-}
-
-static void
-pcond_free (struct pcond_node *node)
-{
- if (!node)
- return;
- switch (node->type)
+ for (i = 0; method_names[i]; i++)
{
- case pcond_true:
- break;
-
- case pcond_component:
- free (node->v.tag);
-
- case pcond_type:
- case pcond_mode:
- case pcond_status:
- break;
-
- case pcond_not:
- pcond_free (node->v.unary);
- break;
-
- case pcond_and:
- pcond_free (node->v.binary[0]);
- pcond_free (node->v.binary[1]);
- break;
-
- case pcond_or:
- pcond_free (node->v.binary[0]);
- pcond_free (node->v.binary[1]);
- break;
-
- default:
- abort ();
+ if (strcmp (method_names[i], method) == 0)
+ return i;
}
- free (node);
+ return METH_INVALID;
}
-
-struct pcond_parser_state
+
+static void res_instance (struct ctlio *, enum http_method, size_t, char **,
+ struct json_value *);
+static void res_programs (struct ctlio *, enum http_method, size_t, char **,
+ struct json_value *);
+
+struct ctlio_resource
{
- struct ctlio *io;
- size_t argc;
- char **argv;
+ char *uri;
+ int states;
+ void (*handler) (struct ctlio *, enum http_method,
+ size_t pathc, char **pathv,
+ struct json_value *);
};
-static int pcond_parse_binary (struct pcond_parser_state *state,
- struct pcond_node **ret);
+static struct ctlio_resource restab[] = {
+ { "instance", CTL_ADMIN_STATE, res_instance },
+ { "programs", CTL_ADMIN_STATE|CTL_USER_STATE, res_programs },
+ { NULL }
+};
-static int
-pcond_parse_unary (struct pcond_parser_state *state, struct pcond_node **ret)
+static struct ctlio_resource *
+find_resource (struct ctlio *io, const char *endpoint)
{
- char *term;
- struct pcond_node *pn;
- int rc, n;
+ struct ctlio_resource *p;
- if (state->argc == 0)
- {
- *ret = pcond_node_alloc (pcond_true);
- return 0;
- }
- --state->argc;
- term = *state->argv++;
-
- if (strcasecmp (term, "all") == 0)
- pn = pcond_node_alloc (pcond_true);
- else if (strcasecmp (term, "type") == 0)
- {
- if (!state->argc)
- {
- ctlio_reply (state->io, "551", "unfinished statement");
- return -1;
- }
- --state->argc;
- term = *state->argv++;
- n = TERM_TO_IDX (term, pies_type_str);
- if (n == -1)
- {
- ctlio_reply (state->io, "551", "undefined type: %s", term);
- return -1;
- }
- pn = pcond_node_alloc (pcond_type);
- pn->v.type = n;
- }
- else if (strcasecmp (term, "mode") == 0)
- {
- if (!state->argc)
- {
- ctlio_reply (state->io, "551", "unfinished statement");
- return -1;
- }
- --state->argc;
- term = *state->argv++;
- n = TERM_TO_IDX (term, pies_comp_mode_str);
- if (n == -1)
- {
- ctlio_reply (state->io, "551", "undefined mode: %s", term);
- return -1;
- }
- pn = pcond_node_alloc (pcond_mode);
- pn->v.mode = n;
- }
- else if (strcasecmp (term, "status") == 0)
- {
- if (!state->argc)
- {
- ctlio_reply (state->io, "551", "unfinished statement");
- return -1;
- }
- --state->argc;
- term = *state->argv++;
- n = TERM_TO_IDX (term, status_str);
- if (n == -1)
- {
- ctlio_reply (state->io, "551", "undefined status: %s", term);
- return -1;
- }
- pn = pcond_node_alloc (pcond_status);
- pn->v.status = n;
- }
- else if (strcasecmp (term, "component") == 0)
- {
- if (!state->argc)
- {
- ctlio_reply (state->io, "551", "unfinished statement");
- return -1;
- }
- --state->argc;
- term = *state->argv++;
- pn = pcond_node_alloc (pcond_component);
- pn->v.tag = xstrdup (term);
- }
- else if (strcasecmp (term, "not") == 0)
- {
- struct pcond_node *node;
- if (!state->argc)
- {
- ctlio_reply (state->io, "551", "unfinished statement");
- return -1;
- }
- if (pcond_parse_unary (state, &node) < 0)
- return -1;
- pn = pcond_node_alloc (pcond_not);
- pn->v.unary = node;
- }
- else if (term[0] == '(')
- {
- rc = pcond_parse_binary (state, &pn);
- if (rc == 0)
- {
- ctlio_reply (state->io, "551", "unbalanced parethesis");
- return -1;
- }
- else if (rc == -1)
- return rc;
-
- if (!state->argc || state->argv[0][0] != ')')
- {
- ctlio_reply (state->io, "551", "unbalanced parethesis");
- pcond_free (pn);
- return -1;
- }
- --state->argc;
- ++state->argv;
- }
- else
- {
- ctlio_reply (state->io, "551", "parse error near %s", term);
- return -1;
- }
-
- *ret = pn;
-
- return 1;
+ for (p = restab; p->uri; p++)
+ if (strcmp (p->uri, endpoint) == 0)
+ return p;
+ return NULL;
}
-static int
-pcond_parse_binary (struct pcond_parser_state *state, struct pcond_node **ret)
+static void
+ctlio_do_command (struct ctlio *io, struct wordsplit *uri)
{
- struct pcond_node *pn, *pleft, *pright;
- int rc;
- char const *term;
- int type;
+ const char *val;
+ struct ctlio_resource *res;
+ enum http_method method;
+ struct json_value *json;
- rc = pcond_parse_unary (state, &pleft);
- if (rc < 0)
- return rc;
- if (state->argc == 0)
+ if (strcmp (io->input.input_proto, "HTTP/1.1"))
{
- *ret = pleft;
- return 0;
+ ctlio_reply (io, 505, NULL);
+ return;
}
- if (state->argv[0][0] == ')')
+ val = http_get_header (io->input.headers, "Content-Type");
+ if (val)
{
- *ret = pleft;
- return 1;
+ size_t len = strcspn (val, ";");
+ if (strncmp (val, "application/json", len))
+ {
+ ctlio_reply (io, 415, "Unsupported content type");
+ return;
+ }
}
-
- --state->argc;
- term = *state->argv++;
-
- if (strcasecmp (term, "and") == 0)
- type = pcond_and;
- else if (strcasecmp (term, "or") == 0)
- type = pcond_or;
- else
+
+#if 0
+ FIXME
+ if (check_accept(io, "application/json"))
{
- pcond_free (pleft);
- ctlio_reply (state->io, "551", "expected 'and' or 'or', but found %s",
- term);
- return -1;
+ ctlio_reply (io, 406, NULL);
+ return;
}
+#endif
- if (state->argc == 0)
+ method = method_decode (io->input.input_method);
+ if (method == METH_INVALID)
{
- pcond_free (pleft);
- ctlio_reply (state->io, "551", "expected end of statement");
- return -1;
+ ctlio_reply (io, 405, NULL);
+ return;
}
- rc = pcond_parse_unary (state, &pright);
- if (rc < 0)
+ if (uri->ws_wordc < 1)
{
- pcond_free (pleft);
- return rc;
+ ctlio_reply (io, 404, NULL);
+ return;
}
-
- pn = pcond_node_alloc (type);
- pn->v.binary[0] = pleft;
- pn->v.binary[1] = pright;
-
- *ret = pn;
- return 1;
-}
-
-int
-pcond_parse (struct ctlio *io, size_t argc, char **argv,
- struct pcond_node **pret)
-{
- struct pcond_parser_state state = { io, argc, argv };
- int rc;
- struct pcond_node *pn;
- rc = pcond_parse_binary (&state, &pn);
- if (rc < 0)
- return rc;
- if (state.argc)
- {
- pcond_free (pn);
- ctlio_reply (io, "551", "trailing garbage at %s", state.argv[0]);
- return -1;
- }
-
- *pret = pn;
- return 0;
-}
-
-static int
-list_matches (struct ctlio *io, struct grecs_list *matches)
-{
- struct grecs_list_entry *ep;
- size_t i;
-
- for (ep = matches->head; ep; ep = ep->next)
- {
- struct prog *prog = ep->data;
-
- ctlio_reply (io, "151", "%s", prog->tag);
- FORMAT_IDX (io, "Type", pies_type_str, prog->type);
- switch (prog->type)
- {
- case TYPE_COMPONENT:
- FORMAT_IDX (io, "Mode", pies_comp_mode_str, prog->v.p.comp->mode);
- FORMAT_IDX (io, "Status", status_str, prog->v.p.status);
-
- if (prog->pid)
- ctlio_printf (io, "PID: %lu%s", (unsigned long) prog->pid, CRLF);
- else if (prog->v.p.status == status_listener
- && prog->v.p.comp->socket_url)
- ctlio_printf (io, "URL: %10s%s",
- prog->v.p.comp->socket_url->string,
- CRLF);
-
- if (prog->v.p.status == status_sleeping)
- {
- ctlio_printf (io, "Wakeup-Time: %lu%s",
- (unsigned long) (prog->v.p.timestamp + SLEEPTIME),
- CRLF);
- }
-
- ctlio_printf (io, "Command:");
- for (i = 0; i < prog->v.p.comp->argc; i++)
- ctlio_printf (io, " %s", quotearg (prog->v.p.comp->argv[i]));
- ctlio_eol (io);
- break;
-
- case TYPE_REDIRECTOR:
- ctlio_printf (io, "PID: %10lu%s", (unsigned long) prog->pid, CRLF);
- break;
-
- case TYPE_COMMAND:
- ctlio_printf (io, "Command: %s%s", prog->v.c.command, CRLF);
- }
- ctlio_eot (io);
- }
- return 0;
-}
-
-struct eval_env
-{
- struct ctlio *io;
- struct pcond_node *cond;
- struct grecs_list *list;
-};
-
-/* Authenticate access to PROG. Return 0, CTL_USER_STATE or
- CTL_ADMIN_STATE depending on whether and what kind of access is
- allowed for the current identity */
-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_REDIRECTOR:
- prog = prog->v.r.master;
- /* FALL THROUGH */
- 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 int
-selector (struct prog *prog, void *data)
-{
- struct eval_env *env = data;
-
- if (pcond_eval (env->cond, prog) && auth_prog (prog, env->io))
- grecs_list_append (env->list, prog);
- return 0;
-}
-
-static void
-cmd_list (struct ctlio *io, size_t argc, char **argv)
-{
- struct eval_env env;
- size_t size;
-
- env.io = io;
- if (pcond_parse (io, argc - 1, argv + 1, &env.cond))
- return;
- env.list = grecs_list_create ();
-
- progman_foreach (selector, &env);
- size = grecs_list_size (env.list);
- if (size)
+ res = find_resource (io, uri->ws_wordv[0]);
+ if (!res)
{
- ctlio_reply (io, "150", "%lu matches found; list follows",
- (unsigned long) size);
- list_matches (io, env.list);
- ctlio_reply (io, "250", "complete");
+ ctlio_reply (io, 404, "resource not found");
+ return;
}
- else
- ctlio_reply (io, "552", "No match");
-
- pcond_free (env.cond);
- grecs_list_free (env.list);
-}
-
-static void
-cmd_start (struct ctlio *io, size_t argc, char **argv)
-{
- char const *tag = argv[1];
- struct prog *prog = progman_locate (tag);
- if (auth_prog (prog, io) & CTL_ADMIN_STATE)
+
+ if (!(io->state & res->states))
{
- if (!prog)
- ctlio_reply (io, "552", "Component not found");
- else if (!IS_COMPONENT (prog))
- ctlio_reply (io, "553", "Not a component");
- else if (prog->v.p.status != status_disabled)
- ctlio_reply (io, "554", "Not stopped");
- else
+ if (ctlio_authenticate (io))
+ return;
+ if (!(io->state & res->states))
{
- prog->v.p.comp->flags &= CF_DISABLED;
- prog->v.p.status = status_enabled;
- kill (getpid (), SIGALRM);
- ctlio_reply (io, "250", "Complete");
+ ctlio_reply (io, 403, NULL);
+ return;
}
}
- else
- ctlio_reply (io, "531", "access denied");
-}
-static void
-cmd_stop (struct ctlio *io, size_t argc, char **argv)
-{
- char const *tag = argv[1];
- struct prog *prog = progman_locate (tag);
- if (auth_prog (prog, io) & CTL_ADMIN_STATE)
+ if (io->input.input_content_length)
{
- if (!prog)
- ctlio_reply (io, "552", "Component not found");
- else if (!IS_COMPONENT (prog))
- ctlio_reply (io, "553", "Not a component");
- else
+ json = json_parse_string (ctlbuf_peek (&io->input.ibuf),
+ ctlbuf_rdsize (&io->input.ibuf));
+ if (!json)
{
- progman_stop_component (prog);
- prog->v.p.comp->flags |= CF_DISABLED;
- ctlio_reply (io, "250", "Complete");
+ ctlio_reply (io, 400, NULL);
+ //FIXME: Reply json
}
}
else
- ctlio_reply (io, "531", "access denied");
-}
-
-static void
-cmd_restart (struct ctlio *io, size_t argc, char **argv)
-{
- char const *tag = argv[1];
- struct prog *prog = progman_locate (tag);
- if (auth_prog (prog, io) & CTL_ADMIN_STATE)
- {
- if (!prog)
- ctlio_reply (io, "552", "Component not found");
- else if (!(IS_COMPONENT (prog)
- && !(prog->v.p.comp->mode == pies_comp_inetd
- && prog->v.p.listener)))
- ctlio_reply (io, "553", "Not a component");
- else
- {
- progman_stop_component (prog);
- ctlio_reply (io, "250", "Complete");
- }
- }
- else
- ctlio_reply (io, "531", "access denied");
-}
-
-static void
-cmd_reboot (struct ctlio *io, size_t argc, char **argv)
-{
- io->action = ACTION_RESTART;
- io->state = CTL_ACTION_STATE;
- ctlio_reply (io, "221", "Rebooting, closing connection");
-}
-
-static void
-cmd_shutdown (struct ctlio *io, size_t argc, char **argv)
-{
- io->action = ACTION_STOP;
- io->state = CTL_ACTION_STATE;
- ctlio_reply (io, "221", "Shutting down, connection will be closed");
+ json = json_value_create (json_null);
+
+ res->handler (io, method, uri->ws_wordc, uri->ws_wordv, json);
+ json_value_free (json);
}
static int ctlrd (int fd, void *data);
@@ -1080,20 +1012,39 @@ ctlrd (int fd, void *data)
// logmsg (LOG_DEBUG, "%s called: %d,%d", __FUNCTION__, n,c);
if (n == 1)
{
- switch (c) {
- case '\n':
- ctlio_do_command (io);
- ctlbuf_flush (&io->ibuf);
- update_socket (fd, PIES_EVT_RD, NULL);
- update_socket (fd, PIES_EVT_WR, ctlwr);
- break;
-
- case EOT:
+ if (c == EOT)
return ctlio_end (fd, io);
+ else
+ {
+ int rc = input_append (&io->input, c);
+ if (rc == 0)
+ return 0;
- default:
- ctlbuf_write (&io->ibuf, &c, 1);
- }
+ if (rc == 200)
+ {
+ struct wordsplit ws;
+
+ ws.ws_delim = "/";
+ if (wordsplit (io->input.input_uri, &ws,
+ WRDSF_DELIM | WRDSF_SQUEEZE_DELIMS
+ | WRDSF_NOVAR | WRDSF_NOCMD))
+ ctlio_reply (io, 500, NULL);
+ else
+ {
+ ctlio_do_command (io, &ws);
+ wordsplit_free (&ws);
+ }
+ }
+ 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
{
@@ -1115,14 +1066,15 @@ ctlwr (int fd, void *data)
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);
- }
+ 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);
@@ -1172,7 +1124,6 @@ ctl_accept (int socket, void *data)
io = ctlio_create ();
io->addr = addr;
io->addrlen = addrlen;
- ctlio_initial_reply (io);
register_socket (fd, NULL, ctlwr, NULL, io);
return 0;
@@ -1212,4 +1163,292 @@ ctl_open ()
register_socket (fd, ctl_accept, NULL, NULL, NULL);
}
+
+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_string (io->output.reply, name, "%lu",
+ (unsigned long) getpid ());
+}
+
+static void
+res_instance (struct ctlio *io, enum http_method meth,
+ size_t pathc, char **pathv,
+ 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 },
+#if HAVE_DECL_PROGRAM_INVOCATION_NAME
+ { "binary", idfmt_string_ptr, &program_invocation_name },
+#endif
+ { "PID", idfmt_pid, NULL },
+ { "instance", idfmt_string_ptr, &instance },
+ { NULL }
+ };
+ struct idparam *p;
+
+ if (pathc > 2)
+ {
+ ctlio_reply (io, 404, NULL);
+ return;
+ }
+ io->output.reply = json_reply_create ();
+ if (pathc == 2)
+ {
+ if (meth == METH_GET)
+ {
+ for (p = idparam; p->name; p++)
+ if (strcmp (p->name, pathv[1]) == 0)
+ break;
+ if (p->name)
+ {
+ p->fmt (io, p->name, p->data);
+ io->code = 200;
+ }
+ else
+ ctlio_reply (io, 404, NULL);
+ }
+ else if (strcmp (pathv[1], "PID") == 0
+ || strcmp (pathv[1], "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;
+ struct json_value *json;
+};
+
+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_REDIRECTOR:
+ prog = prog->v.r.master;
+ /* FALL THROUGH */
+ 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 struct json_value *prog_serialize (struct prog *prog);
+
+static int
+selector (struct prog *prog, void *data)
+{
+ struct eval_env *env = data;
+
+ if (auth_prog (prog, env->io))
+ json_array_append (env->json, prog_serialize (prog));
+ return 0;
+}
+
+static char const *pies_type_str[] = {
+ [TYPE_COMPONENT] = "component",
+ [TYPE_REDIRECTOR] = "redirector",
+ [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_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 *status_str[] = {
+ [status_enabled] = "enabled",
+ [status_disabled] = "disabled",
+ [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]))
+
+static struct json_value *
+prog_serialize (struct prog *prog)
+{
+ struct json_value *ret = json_reply_create ();
+ struct json_value *v;
+ size_t i;
+
+ json_object_set_string (ret, "tag", prog->tag);
+ 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", status_str, prog->v.p.status);
+
+ if (prog->pid)
+ json_object_set_string (ret, "PID", "%lu", (unsigned long) prog->pid);
+ else if (prog->v.p.status == status_listener
+ && prog->v.p.comp->socket_url)
+ json_object_set_string (ret, "URL", "%s",
+ prog->v.p.comp->socket_url->string);
+
+ if (prog->v.p.status == status_sleeping)
+ {
+ json_object_set_string (ret, "wakeup-time", "%lu",
+ (unsigned long) (prog->v.p.timestamp + SLEEPTIME));
+ }
+
+ v = json_new_array ();
+ for (i = 0; i < prog->v.p.comp->argc; i++)
+ json_array_append (v, json_new_string (prog->v.p.comp->argv[i]));
+ json_object_set (ret, "argv", v);
+ break;
+
+ case TYPE_REDIRECTOR:
+ json_object_set_string (ret, "PID", "%lu", (unsigned long) prog->pid);
+ break;
+
+ case TYPE_COMMAND:
+ json_object_set_string (ret, "command", "%s", prog->v.c.command);
+ }
+ return ret;
+}
+
+static void
+res_programs (struct ctlio *io, enum http_method meth,
+ size_t pathc, char **pathv,
+ struct json_value *json)
+{
+ if (meth == METH_GET)
+ {
+ struct eval_env env;
+
+ env.io = io;
+ env.json = json_new_array ();
+ progman_foreach (selector, &env);
+ io->output.reply = env.json;
+ io->code = 200;
+ }
+ else if (meth == METH_DELETE || meth == METH_PUT)
+ {
+ if (pathc != 2)
+ ctlio_reply (io, 404, NULL);
+ else
+ {
+ struct prog *prog = progman_locate (pathv[1]);
+ if (!prog)
+ ctlio_reply (io, 404, NULL);
+ else if (auth_prog (prog, io) & CTL_ADMIN_STATE)
+ {
+ if (!IS_COMPONENT (prog))
+ ctlio_reply (io, 404, "Not a component");
+ else if (meth == METH_DELETE)
+ {
+ if (prog->v.p.comp->flags & CF_DISABLED)
+ ctlio_reply (io, 409, "already stopped");
+ else
+ {
+ progman_stop_component (prog);
+ prog->v.p.comp->flags |= CF_DISABLED;
+ ctlio_reply (io, 200, "Component stopped");
+ }
+ }
+ else
+ {
+ if (!(prog->v.p.comp->flags & CF_DISABLED))
+ ctlio_reply (io, 409, "already running");
+ else
+ {
+ prog->v.p.comp->flags &= CF_DISABLED;
+ prog->v.p.status = status_enabled;
+ kill (getpid (), SIGALRM);
+ ctlio_reply (io, 200, "Component started");
+ }
+ }
+ }
+ else
+ ctlio_reply (io, 403, NULL);
+ }
+ }
+ else
+ ctlio_reply (io, 405, NULL);
+}

Return to:

Send suggestions and report system problems to the System administrator.