aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2015-12-22 21:58:02 +0200
committerSergey Poznyakoff <gray@gnu.org>2015-12-25 09:51:38 +0200
commitfcace3930fc35a1b7beea75dba0cd48a5b229b21 (patch)
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.
-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
@@ -69,13 +69,13 @@ AC_FUNC_FORK
AC_CHECK_FUNCS([alarm dup2 gethostbyname memmove memset select setenv socket strchr strcspn strtol strtoul sysconf getdtablesize vsyslog])
# Gnulib
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
MF_PROCTITLE
# Gettext.
diff --git a/gnulib.modules b/gnulib.modules
index 1e01a93..2612e75 100644
--- a/gnulib.modules
+++ b/gnulib.modules
@@ -1,9 +1,10 @@
# List of gnulib modules needed for Pies.
# A module name per line. Empty lines and comments are ignored.
+base64
c-ctype
c-strcase
configmake
fprintftime
gettext
inttostr
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
@@ -15,12 +15,14 @@
along with GNU Pies. If not, see <http://www.gnu.org/licenses/>. */
#include "pies.h"
#include "prog.h"
#include "xvasprintf.h"
#include "identity.h"
+#include "base64.h"
+#include "json.h"
#define DEFAULT_CONTROL_URL "unix:///tmp/%s.ctl"
struct control control;
pies_identity_t identity;
@@ -68,12 +70,18 @@ ctlbuf_write (struct ctlbuf *buf, char const *str, size_t n)
{
ctlbuf_alloc (buf, n);
memcpy (buf->base + buf->level, str, n);
buf->level += n;
}
+static char const *
+ctlbuf_peek (struct ctlbuf *buf)
+{
+ return buf->base + buf->pos;
+}
+
#define ctlbuf_rdsize(b) ((b)->level - (b)->pos)
static ssize_t
ctlbuf_read (struct ctlbuf *buf, char *str, size_t n)
{
size_t size = ctlbuf_rdsize (buf);
@@ -81,12 +89,22 @@ ctlbuf_read (struct ctlbuf *buf, char *str, size_t n)
n = size;
memcpy (str, buf->base + buf->pos, n);
buf->pos += n;
return n;
}
+static ssize_t
+ctlbuf_copy (struct ctlbuf *out, struct ctlbuf *in)
+{
+ size_t size = ctlbuf_rdsize (in);
+ ctlbuf_alloc (out, size);
+ ctlbuf_write (out, in->base + in->pos, size);
+ in->pos += size;
+ return size;
+}
+
static void
ctlbuf_flush (struct ctlbuf *buf)
{
buf->pos = buf->level = 0;
}
@@ -98,135 +116,490 @@ ctlbuf_chomp (struct ctlbuf *buf)
while (buf->level > 0
&& strchr(" \t\r\n", buf->base[buf->level-1]))
--buf->level;
}
ctlbuf_write (buf, "", 1);
}
+
+#define ISWS(c) ((c) == ' ' || (c) == '\t')
+
+static char *
+ctlbuf_split (struct ctlbuf *buf)
+{
+ ssize_t i;
+ ssize_t hsize = -1;
+ char *s;
+
+ for (i = 0; buf->pos + i < buf->level; i++)
+ if (buf->base[buf->pos + i] == ':')
+ {
+ hsize = i;
+ break;
+ }
+
+ if (hsize == -1)
+ return NULL;
+
+ s = grecs_malloc (hsize + 1);
+ ctlbuf_read (buf, s, hsize);
+ s[hsize] = 0;
+
+ do
+ ++buf->pos;
+ while (buf->pos < buf->level && ISWS (buf->base[buf->pos]));
+
+ return s;
+}
#define CTL_END_STATE 0
#define CTL_INITIAL_STATE 0x01
#define CTL_USER_STATE 0x02
#define CTL_ADMIN_STATE 0x04
#define CTL_ACTION_STATE 0x08
#define CTL_AUTHENTICATED_STATE (CTL_USER_STATE|CTL_ADMIN_STATE)
#define CTL_ALL_STATES (CTL_INITIAL_STATE|CTL_AUTHENTICATED_STATE)
+
+#define CRLF "\r\n"
-struct 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 *
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)
{
deregister_socket (fd);
close (fd);
ctlio_destroy (io);
return 1;
}
+static char const *
+http_text (int code)
+{
+ switch (code)
+ {
+ case 200:
+ return "OK";
+
+ case 400:
+ return "Bad request";
+
+ case 401:
+ return "Unauthorized";
+
+ case 403:
+ return "Forbidden";
+
+ case 404:
+ return "Not found";
+
+ case 405:
+ return "Method Not Allowed";
+
+ case 406:
+ return "Not Acceptable";
+
+ case 411:
+ return "Length Required";
+
+ case 415:
+ return "Unsupported Media Type";
+
+ case 500:
+ return "Internal Server Error";
+
+ case 501:
+ return "Not Implemented";
+
+ case 503:
+ return "Service Unavailable";
+
+ case 505:
+ return "HTTP Version Not Supported";
+ }
+ return "";
+}
+
+static void
+ctlio_reply (struct ctlio *io, int code, const char *fmt, ...)
+{
+ io->code = code;
+ if (fmt)
+ {
+ va_list ap;
+ char *str;
+
+ 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)
{
ctlbuf_write (&io->obuf, text, strlen (text));
}
@@ -246,85 +619,85 @@ ctlio_printf (struct ctlio *io, const char *fmt, ...)
static void
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
{
union pies_sockaddr_storage addr;
socklen_t addrlen;
@@ -351,722 +724,281 @@ try_auth (struct prog *prog, void *data)
return 1;
}
}
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)
{
pies_identity_provider_t provider = ep->data;
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,
(struct sockaddr *)&io->addr, io->addrlen,
id) == 0)
{
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,
(struct sockaddr *)&io->addr, io->addrlen,
id) == 0)
{
new_state = CTL_USER_STATE;
logmsg (LOG_AUTH, "%s authenticated via %s",
- argv[1], pname);
+ name, pname);
}
else
{
struct auth_data ad = { io->addr, io->addrlen, id, NULL };
progman_foreach (try_auth, &ad);
if (ad.comp)
{
new_state = CTL_USER_STATE;
logmsg (LOG_AUTH, "%s authenticated via %s, component %s",
- 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;
}
}
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)
- {
-