diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2015-12-22 21:58:02 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2015-12-25 09:51:38 +0200 |
commit | fcace3930fc35a1b7beea75dba0cd48a5b229b21 (patch) | |
tree | 00be6f5c1080290ba788318219026f0a0c943e20 /src | |
parent | d479bd2f63445524253e16d8575a0592c266908d (diff) | |
download | pies-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 (limited to 'src')
-rw-r--r-- | src/ctl.c | 1729 |
1 files changed, 984 insertions, 745 deletions
@@ -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; + |