From fcace3930fc35a1b7beea75dba0cd48a5b229b21 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Tue, 22 Dec 2015 21:58:02 +0200 Subject: REST control protocol. * configure.ac (GRECS_SETUP): Add json * gnulib.modules: Add base64. * grecs: Upgrade. * src/ctl.c: Rewrite as REST. --- configure.ac | 2 +- gnulib.modules | 1 + grecs | 2 +- src/ctl.c | 1729 ++++++++++++++++++++++++++++++++------------------------ 4 files changed, 987 insertions(+), 747 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 index 88e00fd..8312f45 160000 --- a/grecs +++ b/grecs @@ -1 +1 @@ -Subproject commit 88e00fd054cc07cb9ede045c10ebf41795e144ad +Subproject commit 8312f45f48ed9d995a15ee6707257f4c8946528d 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 http_header +{ + char *name; + char *value; +}; -struct ctlio; +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); +} -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 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); +} -struct ctlio_command +static int +http_header_copy (void *a, void *b) { - char *verb; - char *descr; - int states; - int minargs; - size_t maxargs; - void (*handler) (struct ctlio *, size_t, char **); + 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) +{ + 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) { - struct ctlio_command *cp; + json_value_free (out->reply); + grecs_symtab_free (out->headers); +} - for (cp = cmdtab; cp->verb; cp++) - if (strcasecmp (cp->verb, verb) == 0) - return cp; - return NULL; +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)) - { - ctlio_reply (io, "510", "permission denied"); - return; - } - if (cmd->minargs && io->ws.ws_wordc < cmd->minargs) + if (io->state & (CTL_INITIAL_STATE|CTL_AUTHENTICATED_STATE)) { - 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); -} -static void -cmd_quit (struct ctlio *io, size_t argc, char **argv) -{ - ctlio_reply (io, "221", "bye"); - io->state = CTL_END_STATE; -} + identity = id; + io->state = new_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); + } + else + { + output_set_header (&io->output, "WWW-Authenticate", + "Basic realm=\"%s\"", "pies"); //FIXME: Configurable realm + ctlio_reply (io, 401, NULL); } - ctlio_eot (io); + return 1; } -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; -} - -#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); - - default: - abort (); - } -} + int i; -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; - - 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; + struct ctlio_resource *p; - 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) + res = find_resource (io, uri->ws_wordv[0]); + if (!res) { - 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) - { - ctlio_reply (io, "150", "%lu matches found; list follows", - (unsigned long) size); - list_matches (io, env.list); - ctlio_reply (io, "250", "complete"); - } - 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 (!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 - { - prog->v.p.comp->flags &= CF_DISABLED; - prog->v.p.status = status_enabled; - kill (getpid (), SIGALRM); - ctlio_reply (io, "250", "Complete"); - } + ctlio_reply (io, 404, "resource not found"); + 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->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 (ctlio_authenticate (io)) + return; + if (!(io->state & res->states)) { - progman_stop_component (prog); - prog->v.p.comp->flags |= CF_DISABLED; - ctlio_reply (io, "250", "Complete"); + ctlio_reply (io, 403, NULL); + return; } } - 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 (io->input.input_content_length) { - 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 + json = json_parse_string (ctlbuf_peek (&io->input.ibuf), + ctlbuf_rdsize (&io->input.ibuf)); + if (!json) { - progman_stop_component (prog); - ctlio_reply (io, "250", "Complete"); + ctlio_reply (io, 400, NULL); + //FIXME: Reply json } } 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); +} -- cgit v1.2.1