/* 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 . */ #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); }