diff options
Diffstat (limited to 'src/ctl.c')
-rw-r--r-- | src/ctl.c | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/src/ctl.c b/src/ctl.c new file mode 100644 index 0000000..cfa0e13 --- /dev/null +++ b/src/ctl.c @@ -0,0 +1,459 @@ +/* This file is part of GNU Pies. + Copyright (C) 2007-2013 Sergey Poznyakoff + + GNU Pies is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + GNU Pies is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNU Pies. If not, see <http://www.gnu.org/licenses/>. */ + +#include "pies.h" +#include "prog.h" +#include "xvasprintf.h" + +#define DEFAULT_CONTROL_URL "unix:///tmp/%s.ctl" + +struct control control; + + +struct ctlbuf +{ + char *base; + size_t size; + size_t level; + size_t pos; +}; + +#define CTLBUFSIZE 16 +#define EOT '\04' + +static void +ctlbuf_init (struct ctlbuf *buf) +{ + buf->base = NULL; + buf->size = 0; + buf->level = 0; + buf->pos = 0; +} + +static void +ctlbuf_free (struct ctlbuf *buf) +{ + free (buf->base); +} + +static void +ctlbuf_alloc (struct ctlbuf *buf, size_t s) +{ + while (buf->level + s >= buf->size) + { + if (buf->size == 0) + buf->size = CTLBUFSIZE; + buf->base = x2realloc (buf->base, &buf->size); + } +} + +static void +ctlbuf_write (struct ctlbuf *buf, char const *str, size_t n) +{ + ctlbuf_alloc (buf, n); + memcpy (buf->base + buf->level, str, n); + buf->level += n; +} + +#define ctlbuf_rdsize(b) ((b)->level - (b)->pos) + +static ssize_t +ctlbuf_read (struct ctlbuf *buf, char *str, size_t n) +{ + size_t size = ctlbuf_rdsize (buf); + if (size < n) + n = size; + memcpy (str, buf->base + buf->pos, n); + buf->pos += n; + return n; +} + +static void +ctlbuf_flush (struct ctlbuf *buf) +{ + buf->pos = buf->level = 0; +} + +static void +ctlbuf_chomp (struct ctlbuf *buf) +{ + if (buf->base) + { + while (buf->level > 0 + && strchr(" \t\r\n", buf->base[buf->level-1])) + --buf->level; + } + ctlbuf_write (buf, "", 1); +} + +#define CTL_END_STATE 0 +#define CTL_INITIAL_STATE 0x01 +#define CTL_AUTHENTICATED_STATE 0x02 +#define CTL_ALL_STATES (CTL_INITIAL_STATE|CTL_AUTHENTICATED_STATE) + +struct ctlio; + +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 **); + +struct ctlio_command +{ + char *verb; + char *descr; + int states; + int minargs; + size_t maxargs; + void (*handler) (struct ctlio *, size_t, char **); +}; + +static struct ctlio_command cmdtab[] = { + { "noop", "no operation", + CTL_ALL_STATES, 1, 1, cmd_noop }, + { "id", "identify the instance", + CTL_AUTHENTICATED_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 }, + { NULL } +}; + +static struct ctlio_command * +ctlio_command_find (char const *verb) +{ + struct ctlio_command *cp; + + for (cp = cmdtab; cp->verb; cp++) + if (strcasecmp (cp->verb, verb) == 0) + return cp; + return NULL; +} + +#define CRLF "\r\n" + +struct ctlio +{ + int state; + struct ctlbuf ibuf; + struct ctlbuf obuf; + struct wordsplit ws; + int wsflags; +}; + +static struct ctlio * +ctlio_create (void) +{ + struct ctlio *io; + + io = xmalloc (sizeof (*io)); + io->state = CTL_INITIAL_STATE; + ctlbuf_init (&io->ibuf); + ctlbuf_init (&io->obuf); + io->wsflags = WRDSF_DEFFLAGS; + return io; +} + +void +ctlio_destroy (struct ctlio *io) +{ + ctlbuf_free (&io->ibuf); + ctlbuf_free (&io->obuf); + if (io->wsflags & WRDSF_REUSE) + wordsplit_free (&io->ws); + free (io); +} + +static int +ctlio_end (int fd, struct ctlio *io) +{ + deregister_socket (fd); + close (fd); + ctlio_destroy (io); + return 1; +} + +static void +ctlio_print (struct ctlio *io, const char *text) +{ + ctlbuf_write (&io->obuf, text, strlen (text)); +} + +static void +ctlio_printf (struct ctlio *io, const char *fmt, ...) +{ + va_list ap; + char *str; + + va_start (ap, fmt); + str = xvasprintf (fmt, ap); + va_end (ap); + ctlio_print (io, str); + free (str); +} + +static void +ctlio_eol (struct ctlio *io) +{ + ctlio_print (io, CRLF); +} + +static void +ctlio_eot (struct ctlio *io) +{ + ctlio_print (io, "." CRLF); +} + +static void +ctlio_reply (struct ctlio *io, const char *code, const char *fmt, ...) +{ + 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); +} + +static void +ctlio_do_command (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; + } + + 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", "command not valid in state"); + return; + } + if (cmd->minargs && io->ws.ws_wordc < cmd->minargs) + { + ctlio_reply (io, "500", "too few arguments"); + return; + } + if (cmd->maxargs && io->ws.ws_wordc > cmd->maxargs) + { + ctlio_reply (io, "500", "too many arguments"); + return; + } + 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); + ctlio_printf (io, " <%s>", "foobarbaz"); + //FIXME: auth mechanisms + ctlio_eol (io); +} + +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; +} + +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, "Instance:%s%s", instance, CRLF); + ctlio_eot (io); +} + +static void +cmd_help (struct ctlio *io, size_t argc, char **argv) +{ + struct ctlio_command *cp; + + ctlio_reply (io, "113", "help text follows"); + for (cp = cmdtab; cp->verb; cp++) + { + ctlio_printf (io, "%-9s%s", cp->verb, cp->descr); + ctlio_eol (io); + } + ctlio_eot (io); +} + + +static int ctlrd (int fd, void *data); +static int ctlwr (int fd, void *data); + +static int +ctlrd (int fd, void *data) +{ + ssize_t n; + char c; + struct ctlio *io = data; + + n = read (fd, &c, 1); + // logmsg (LOG_DEBUG, "%s called: %d,%d", __FUNCTION__, n,c); + if (n == 1) + { + 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: + return ctlio_end (fd, io); + + default: + ctlbuf_write (&io->ibuf, &c, 1); + } + } + else + { + if (n == -1) + logmsg (LOG_ERR, "error reading from control socket: %s", + strerror (errno)); + return ctlio_end (fd, io); + } + + return 0; +} + +static int +ctlwr (int fd, void *data) +{ + char c; + struct ctlio *io = data; + // logmsg (LOG_DEBUG, "%s called", __FUNCTION__); + if (ctlbuf_read (&io->obuf, &c, 1)) + write (fd, &c, 1); + else if (io->state == CTL_END_STATE) + return ctlio_end (fd, io); + else + { + update_socket (fd, PIES_EVT_WR, NULL); + update_socket (fd, PIES_EVT_RD, ctlrd); + ctlbuf_flush (&io->obuf); + } + return 0; +} + +static int +ctl_accept (int socket, void *data) +{ + int fd; + union pies_sockaddr_storage addr; + socklen_t addrlen = sizeof addr; + struct ctlio *io; + + fd = accept (socket, (struct sockaddr*) &addr, &addrlen); + if (fd == -1) + { + logmsg (LOG_ERR, _("accept failed: %s"), strerror (errno)); + return 1; + } + + if (debug_level >= 1) + { + char *s = sockaddr_to_astr ((struct sockaddr *)&addr, addrlen); + logmsg (LOG_DEBUG, _("%s wants %s"), s, "control socket"); + free (s); + } + + if (check_acl (control.acl, (struct sockaddr *)&addr, addrlen)) + { + close (fd); + return 1; + } + /* FIXME: Check number of connections? */ + + io = ctlio_create (); + ctlio_initial_reply (io); + register_socket (fd, NULL, ctlwr, NULL, io); + + return 0; +} + +void +ctl_open () +{ + int fd; + + if (!control.url) + { + char *str = xasprintf (DEFAULT_CONTROL_URL, instance); + if (pies_url_create (&control.url, str)) + { + logmsg (LOG_CRIT, _("%s: cannot create URL: %s"), + str, strerror (errno)); + } + free (str); + } + + fd = create_socket (control.url, SOCK_STREAM, NULL, 0600); + if (fd == -1) + { + logmsg (LOG_CRIT, _("can't create control socket %s"), control.url->string); + exit (EX_UNAVAILABLE); + } + + if (listen (fd, 8)) + { + logmsg (LOG_CRIT, "can't listen on control socket %s: %s", + strerror (errno)); + exit (EX_UNAVAILABLE); + } + + register_socket (fd, ctl_accept, NULL, NULL, NULL); +} + |