aboutsummaryrefslogtreecommitdiff
path: root/src/piesctl.c
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2015-12-31 13:59:18 +0200
committerSergey Poznyakoff <gray@gnu.org.ua>2015-12-31 15:58:17 +0200
commit00e6c3c3ed06a258a02943fc49fa7c528025d747 (patch)
tree52530660be48fb5f4611bcc9393886bab8a70b72 /src/piesctl.c
parent7f204cc788de3e03a51087b1273deb5b59288cf2 (diff)
downloadpies-00e6c3c3ed06a258a02943fc49fa7c528025d747.tar.gz
pies-00e6c3c3ed06a258a02943fc49fa7c528025d747.tar.bz2
Command-line control interface.
* configure.ac (DEFAULT_CONTROL_URL): New subst variable. * grecs: Upgrade. * ident/pam.c (overwrite_and_free): Free ptr. * lib/Makefile.am: Add new sources. * src/addrfmt.c: Move to lib/addrfmt.c * lib/grecsasrt.c: New file. * lib/grecsasrt.h: New file. * lib/mkfilename.c: New file. * lib/netrc.c: New file. * lib/pp.c: New file. * lib/split3.c: New file. * src/url.c: Move from lib/url.c (pies_url_free_user, pies_url_free_passwd): New finctions. * lib/libpies.h (strsplit3): New proto. (pies_url_create, pies_url_destroy) (pies_url_get_arg, pies_url_copy) (pies_url_free_user, pies_url_free_passwd) (netrc_scan) (pp_add_option, pp_command_line, mkfilename) (sockaddr_to_str, sockaddr_to_astr): New protos * src/Makefile.am (bin_PROGRAMS): New program: piesctl (pies_SOURCES): Remove addrfmt.c and url.c. (noinst_HEADERS, BUILT_SOURCES): Add piesctl-cl.h * src/cmdline.opt: Use pp_* function family to build preprocessor command line. * src/ctl.c (http_header_hash): Use case-insensitive hashing. (ctlio_finalize_reply): Don't close connection after sending 401 response. (input): Remove ws and wsflags. All uses changed. (input_append): Use strsplit3 to parse the request line. * src/pies.c: Use pp_* function family to build preprocessor command line. Move assert_, mkfilename and _cb+_url functions into libpies. * src/pies.h (pies_sockaddr_storage): Move to libpies.h * src/piesctl.c: New file. * src/piesctl-cl.opt: New file.
Diffstat (limited to 'src/piesctl.c')
-rw-r--r--src/piesctl.c1067
1 files changed, 1067 insertions, 0 deletions
diff --git a/src/piesctl.c b/src/piesctl.c
new file mode 100644
index 0000000..0618caa
--- /dev/null
+++ b/src/piesctl.c
@@ -0,0 +1,1067 @@
+/* This file is part of GNU Pies.
+ Copyright (C) 2015 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 <config.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <netdb.h>
+#include <locale.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <configmake.h>
+#include <grecs.h>
+#include <grecs-locus.h>
+#include <json.h>
+#include <wordsplit.h>
+#include <sysexits.h>
+#include <termios.h>
+#include "base64.h"
+#include "progname.h"
+#include "libpies.h"
+#include "grecsasrt.h"
+
+struct pies_url *default_url; /* Control socket URL */
+struct pies_url *url;
+char const *instance = "pies";
+char *config_file;
+char default_config_file[] = SYSCONFDIR "/piesctl.conf";
+int preprocess_only;
+int verbose;
+
+static void config_help (void);
+
+#include "piesctl-cl.h"
+
+static struct grecs_keyword instance_keywords[] = {
+ {"socket",
+ N_("url: string"),
+ N_("Socket URL for that instance."),
+ grecs_type_string, GRECS_DFLT,
+ NULL, 0, conf_callback_url},
+ { NULL }
+};
+
+static int
+callback_instance (enum grecs_callback_command cmd,
+ grecs_locus_t *locus,
+ void *varptr,
+ grecs_value_t *value, void *cb_data)
+{
+ switch (cmd)
+ {
+ case grecs_callback_section_begin:
+ if (GRECS_VALUE_EMPTY_P (value))
+ {
+ grecs_error (locus, 0, _("missing tag"));
+ return 1;
+ }
+ if (assert_grecs_value_type (&value->locus, value, GRECS_TYPE_STRING))
+ return 1;
+ if (!url && strcmp (value->v.string, instance) == 0)
+ *(struct pies_url ***) cb_data = &url;
+ else
+ *(struct pies_url ***) cb_data = NULL;
+ break;
+
+ case grecs_callback_section_end:
+ break;
+
+ case grecs_callback_set_value:
+ grecs_error (locus, 0, _("invalid use of block statement"));
+ }
+ return 0;
+}
+
+struct grecs_keyword piesctl_keywords[] = {
+ {"socket",
+ N_("url: string"),
+ N_("Default socket URL."),
+ grecs_type_string, GRECS_DFLT,
+ &default_url, 0, conf_callback_url},
+ {"instance",
+ N_("name"),
+ N_("Configure connection to a pies instance"),
+ grecs_type_section, GRECS_DFLT,
+ NULL, 0,
+ callback_instance, NULL, instance_keywords },
+ { NULL }
+};
+
+static void
+parse_config ()
+{
+ char *file_name;
+ struct grecs_node *tree;
+
+ grecs_include_path_setup (DEFAULT_VERSION_INCLUDE_DIR,
+ DEFAULT_INCLUDE_DIR, NULL);
+ grecs_log_to_stderr = 1;
+ grecs_preprocessor = pp_command_line ();
+
+ if (config_file)
+ file_name = config_file;
+ else if (access (default_config_file, F_OK) == 0)
+ file_name = default_config_file;
+ else
+ file_name = NULL;
+
+ if (file_name)
+ {
+ if (preprocess_only)
+ exit (grecs_preproc_run (file_name, grecs_preprocessor)
+ ? EX_CONFIG : 0);
+ if (verbose)
+ printf ("%s: reading configuration from %s\n",
+ program_name, file_name);
+ tree = grecs_parse (file_name);
+ if (!tree)
+ exit (EX_CONFIG);
+ if (grecs_tree_process (tree, piesctl_keywords))
+ exit (EX_CONFIG);
+ if (!url && verbose)
+ printf ("%s: URL not found in %s\n",
+ program_name, file_name);
+ }
+
+ if (!url)
+ {
+ /* Try local instance configuration */
+ file_name = mkfilename (SYSCONFDIR, instance, ".conf");
+ if (access (file_name, F_OK) == 0)
+ {
+ struct grecs_node *node;
+
+ if (verbose)
+ printf ("%s: reading configuration from %s\n",
+ program_name, file_name);
+ if (preprocess_only)
+ exit (grecs_preproc_run (file_name, grecs_preprocessor)
+ ? EX_CONFIG : 0);
+ tree = grecs_parse (file_name);
+ node = grecs_find_node (tree, "/control/socket");
+ if (node)
+ {
+ assert_grecs_value_type (&node->locus,
+ node->v.value, GRECS_TYPE_STRING);
+ if (pies_url_create (&url, node->v.value->v.string))
+ {
+ grecs_error (&node->locus, 0, _("%s: cannot create URL: %s"),
+ node->v.value->v.string, strerror (errno));
+ exit (EX_CONFIG);
+ }
+ }
+ else if (verbose)
+ printf ("%s: URL not found in %s\n",
+ program_name, file_name);
+ }
+ free (file_name);
+ }
+
+ if (!url)
+ {
+ if (strcmp (instance, "pies") == 0)
+ {
+ if (verbose)
+ printf ("%s: falling back to default URL\n", program_name);
+ if (default_url)
+ url = default_url;
+ else
+ {
+ int rc;
+ size_t len = 0;
+ file_name = NULL;
+ grecs_asprintf (&file_name, &len, DEFAULT_CONTROL_URL, instance);
+ rc = pies_url_create (&url, file_name);
+ free (file_name);
+
+ if (rc)
+ {
+ grecs_error (NULL, 0, _("%s: cannot create URL: %s"),
+ DEFAULT_CONTROL_URL, strerror (errno));
+ exit (EX_SOFTWARE);
+ }
+ }
+ }
+ else
+ {
+ grecs_error (NULL, 0, _("socket name for instance %s not configured"),
+ instance);
+ exit (EX_CONFIG);
+ }
+ }
+ if (verbose)
+ printf ("%s: using URL %s\n", program_name, url->string);
+}
+
+static void
+config_help (void)
+{
+ static char docstring[] =
+ /* TRANSLATORS: do not translate words between ` and ' */
+ N_("Configuration file structure for piesctl.\n"
+ "For more information, use `info piesctl configuration'.");
+ grecs_print_docstring (docstring, 0, stdout);
+ grecs_print_statement_array (piesctl_keywords, 1, 0, stdout);
+}
+
+static void
+piesctl_diag(grecs_locus_t const *locus, int err, int errcode,
+ const char *msg)
+{
+ fflush (stdout);
+ fprintf (stderr, "%s: ", program_name);
+ if (locus)
+ {
+ YY_LOCATION_PRINT (stderr, *locus);
+ fputc(':', stderr);
+ fputc(' ', stderr);
+ }
+ if (!err)
+ fprintf (stderr, "warning: ");
+ fprintf (stderr, "%s", msg);
+ if (errcode)
+ fprintf (stderr, ": %s", strerror (errno));
+ fputc ('\n', stderr);
+}
+
+enum http_method
+ {
+ METH_GET,
+ METH_POST,
+ METH_DELETE,
+ METH_PUT,
+ METH_OPTIONS,
+ METH_INVALID
+ };
+
+char *method_names[] = {
+ [METH_GET] = "GET",
+ [METH_POST] = "POST",
+ [METH_DELETE] = "DELETE",
+ [METH_PUT] = "PUT",
+ [METH_OPTIONS] = "OPTIONS",
+ NULL
+};
+
+static char http_version[] = "HTTP/1.1";
+
+struct shttp_io
+{
+ int code;
+ struct grecs_symtab *headers;
+ unsigned long content_length;
+ char *content;
+};
+
+struct shttp_buf
+{
+ char *base;
+ size_t size;
+};
+
+struct shttp_connection
+{
+ FILE *fp;
+ struct pies_url *url;
+ struct shttp_io req;
+ struct shttp_io resp;
+ struct shttp_buf sendbuf;
+ struct shttp_buf readbuf;
+ char *b64auth;
+ char *status_line[3];
+ struct json_value *result;
+};
+
+struct http_header
+{
+ char *name;
+ char *value;
+};
+
+static unsigned
+http_header_hash (void *data, unsigned long n_buckets)
+{
+ const struct http_header *p = data;
+ return grecs_hash_string_ci (p->name, n_buckets);
+}
+
+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);
+}
+
+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);
+}
+
+static void
+shttp_io_init (struct shttp_io *io)
+{
+ io->code = 0;
+ io->content_length = 0;
+ grecs_free (io->content);
+ io->content = NULL;
+ grecs_symtab_clear (io->headers);
+}
+
+static int
+shttp_io_header_put (struct shttp_io *io, char const *name,
+ char const *fmt, ...)
+{
+ int install = 1;
+ struct http_header key, *ret;
+ va_list ap;
+ size_t len = 0;
+
+ if (!io->headers)
+ io->headers = grecs_symtab_create (sizeof (struct http_header),
+ http_header_hash,
+ http_header_compare,
+ http_header_copy,
+ NULL,
+ http_header_free);
+ key.name = (char *) name;
+ key.value = NULL;
+ ret = grecs_symtab_lookup_or_install (io->headers, &key, &install);
+ if (!ret)
+ {
+ grecs_error (NULL, 0, _("cannot install header %s: %s"),
+ name, strerror (errno));
+ return 1;
+ }
+ if (!install)
+ grecs_free (ret->value);
+ va_start (ap, fmt);
+ grecs_vasprintf (&ret->value, &len, fmt, ap);
+ va_end (ap);
+ return 0;
+}
+
+static char *
+shttp_io_header_get (struct shttp_io *io, char const *name)
+{
+ struct http_header key, *ret;
+
+ if (!io->headers)
+ return NULL;
+ key.name = (char*) name;
+ ret = grecs_symtab_lookup_or_install (io->headers, &key, NULL);
+ return ret ? ret->value : NULL;
+}
+
+static void
+shttp_io_free (struct shttp_io *io)
+{
+ grecs_symtab_free (io->headers);
+ grecs_free (io->content);
+}
+
+static struct shttp_connection *
+shttp_connect (struct pies_url *url)
+{
+ int fd;
+ union pies_sockaddr_storage addr;
+ socklen_t socklen;
+ int flags;
+ struct shttp_connection *conn;
+ FILE *fp;
+
+ if (strcmp (url->scheme, "unix") == 0
+ || strcmp (url->scheme, "file") == 0
+ || strcmp (url->scheme, "socket") == 0)
+ {
+ struct stat st;
+
+ if (url->port)
+ {
+ grecs_error (NULL, 0, _("%s: invalid connection type: "
+ "port is meaningless for UNIX sockets"),
+ url->string);
+ return NULL;
+ }
+
+ if (strlen (url->path) > sizeof addr.s_un.sun_path)
+ {
+ errno = EINVAL;
+ grecs_error (NULL, 0,
+ _("%s: UNIX socket name too long"),
+ url->path);
+ return NULL;
+ }
+
+ addr.s.sa_family = PF_UNIX;
+ socklen = sizeof (addr.s_un);
+ strcpy (addr.s_un.sun_path, url->path);
+
+ if (stat (url->path, &st))
+ {
+ if (errno != ENOENT)
+ {
+ grecs_error (NULL, 0,
+ _("%s: cannot stat socket: %s"),
+ url->path,
+ strerror (errno));
+ return NULL;
+ }
+ }
+ else
+ {
+ if (!S_ISSOCK (st.st_mode))
+ {
+ grecs_error (NULL, 0,
+ _("%s: not a socket"),
+ url->path);
+ return NULL;
+ }
+ }
+ }
+ else if (strcmp (url->scheme, "inet") == 0)
+ {
+ short pnum;
+ struct hostent *hp;
+
+ addr.s_in.sin_family = PF_INET;
+ socklen = sizeof (addr.s_in);
+
+ pnum = url->port ? url->port : 8080;
+
+ hp = gethostbyname (url->host);
+ if (!hp)
+ {
+ grecs_error (NULL, 0, _("%s: unknown host name"), url->string);
+ return NULL;
+ }
+
+ addr.s_in.sin_family = hp->h_addrtype;
+ switch (hp->h_addrtype)
+ {
+ case AF_INET:
+ memmove (&addr.s_in.sin_addr, hp->h_addr, 4);
+ addr.s_in.sin_port = htons (pnum);
+ break;
+
+ default:
+ grecs_error (NULL, 0,
+ _("%s: invalid connection type: "
+ "unsupported address family"),
+ url->string);
+ return NULL;
+ }
+ }
+ else
+ {
+ grecs_error (NULL, 0, _("%s: unsupported protocol"),
+ url->string);
+ return NULL;
+ }
+
+ fd = socket (addr.s.sa_family, SOCK_STREAM, 0);
+ if (fd == -1)
+ {
+ grecs_error (NULL, 0, _("%s: unable to create new socket: %s"),
+ url->string, strerror(errno));
+ return NULL;
+ }
+
+ if ((flags = fcntl (fd, F_GETFD, 0)) == -1
+ || fcntl (fd, F_SETFD, flags | FD_CLOEXEC) == -1)
+ grecs_error (NULL, 0, _("%s: cannot set close-on-exec: %s"),
+ url->string, strerror (errno));
+
+ // FIXME: Bind to local address
+
+ if (connect (fd, &addr.s, socklen))
+ {
+ grecs_error (NULL, 0, _("%s: cannot connect: %s"),
+ url->string, strerror (errno));
+ close (fd);
+ return NULL;
+ }
+
+ fp = fdopen (fd, "w+");
+ if (!fp)
+ {
+ grecs_error (NULL, 0, "fdopen: %s", strerror (errno));
+ close (fd);
+ return NULL;
+ }
+
+ conn = grecs_zalloc (sizeof (*conn));
+ conn->fp = fp;
+ if (pies_url_copy (&conn->url, url))
+ grecs_alloc_die ();
+
+ netrc_scan (conn->url);
+
+ return conn;
+}
+
+static void
+shttp_auth_free (struct shttp_connection *conn)
+{
+ if (conn->b64auth)
+ {
+ memset (conn->b64auth, 0, strlen (conn->b64auth));
+ free (conn->b64auth);
+ conn->b64auth = NULL;
+ }
+}
+
+static void
+shttp_status_line_free (struct shttp_connection *conn)
+{
+ free (conn->status_line[0]);
+ free (conn->status_line[1]);
+ free (conn->status_line[2]);
+ conn->status_line[0] = NULL;
+ conn->status_line[1] = NULL;
+ conn->status_line[2] = NULL;
+}
+
+static void
+shttp_close (struct shttp_connection *conn)
+{
+ fclose (conn->fp);
+ pies_url_destroy (&conn->url);
+ shttp_io_free (&conn->req);
+ shttp_io_free (&conn->resp);
+ grecs_free (conn->sendbuf.base);
+ grecs_free (conn->readbuf.base);
+ shttp_status_line_free (conn);
+ shttp_auth_free (conn);
+ json_value_free (conn->result);
+ grecs_free (conn);
+}
+
+static void
+shttp_send_line (struct shttp_connection *conn, char const *fmt, ...)
+{
+ va_list ap;
+ va_start (ap, fmt);
+ grecs_vasprintf (&conn->sendbuf.base, &conn->sendbuf.size, fmt, ap);
+ va_end (ap);
+ if (verbose > 1)
+ printf ("> %s\n", conn->sendbuf.base);
+ fprintf (conn->fp, "%s\r\n", conn->sendbuf.base);
+}
+
+static int
+shttp_read_line (struct shttp_connection *conn)
+{
+ ssize_t rc;
+ rc = grecs_getline (&conn->readbuf.base, &conn->readbuf.size, conn->fp);
+ if (rc > 0)
+ {
+ if (conn->readbuf.base[--rc] == '\n')
+ {
+ if (rc > 0 && conn->readbuf.base[rc-1] == '\r')
+ --rc;
+ conn->readbuf.base[rc] = 0;
+ }
+ if (verbose > 1)
+ printf ("< %s\n", conn->readbuf.base);
+ return 0;
+ }
+ else if (rc == 0)
+ {
+ if (verbose > 1)
+ printf ("<EOF>\n");
+ return 1;
+ }
+ grecs_error (NULL, errno, _("HTTP read error"));
+ exit (EX_UNAVAILABLE);
+}
+
+static int
+send_header (void *sym, void *data)
+{
+ struct http_header *h = sym;
+ struct shttp_connection *conn = data;
+ shttp_send_line (conn, "%s: %s", h->name, h->value);
+ return 0;
+}
+
+static void
+shttp_request_send (struct shttp_connection *conn, int method, char const *uri)
+{
+ shttp_io_init (&conn->req);
+ shttp_send_line (conn, "%s %s %s", method_names[method], uri, http_version);
+ shttp_send_line (conn, "Host: %s",
+ conn->url->host ? conn->url->host : "localhost");
+ if (conn->url->user)
+ {
+ shttp_auth_free (conn);
+
+ grecs_asprintf (&conn->readbuf.base, &conn->readbuf.size, "%s:%s",
+ conn->url->user,
+ conn->url->passwd ? conn->url->passwd : 0);
+ base64_encode_alloc (conn->readbuf.base, strlen (conn->readbuf.base),
+ &conn->b64auth);
+
+ pies_url_free_user (conn->url);
+ pies_url_free_passwd (conn->url);
+ }
+
+ if (conn->b64auth)
+ shttp_send_line (conn, "Authorization: Basic %s", conn->b64auth);
+
+ grecs_symtab_enumerate (conn->req.headers, send_header, conn);
+ if (conn->req.content_length)
+ {
+ shttp_send_line (conn, "Content-Length: %lu", conn->req.content_length);
+ shttp_send_line (conn, "Content-Type: application/json");
+ }
+ shttp_send_line (conn, "");
+ if (conn->req.content_length)
+ {
+ size_t s = fwrite (conn->req.content, 1, conn->req.content_length,
+ conn->fp);
+ if (s != conn->req.content_length)
+ {
+ grecs_error (NULL, 0, _("wrote %lu of %lu bytes of content"),
+ (unsigned long) s, conn->req.content_length);
+ exit (EX_UNAVAILABLE);
+ }
+ }
+}
+
+#define ISWS(c) ((c) == ' ' || (c) == '\t')
+
+static void
+shttp_save_header (struct shttp_connection *conn)
+{
+ char *p;
+
+ for (p = conn->readbuf.base; *p; p++)
+ if (*p == ':')
+ {
+ *p++ = 0;
+ p += strspn (p, " \t");
+ shttp_io_header_put (&conn->resp, conn->readbuf.base, "%s", p);
+ return;
+ }
+
+ grecs_error (NULL, 0, _("malformed header line: %s"),
+ conn->readbuf.base);
+ exit (EX_UNAVAILABLE);
+}
+
+static void
+shttp_get_reply (struct shttp_connection *conn)
+{
+ enum input_state { is_initial, is_headers, is_content } state = is_initial;
+ char const *val;
+
+ shttp_io_init (&conn->resp);
+ shttp_status_line_free (conn);
+ json_value_free (conn->result);
+ conn->result = NULL;
+
+ while (state != is_content)
+ {
+ if (shttp_read_line (conn))
+ break;
+ if (state == is_initial)
+ {
+ unsigned long n;
+ char *p;
+ int rc;
+
+ rc = strsplit3 (conn->readbuf.base, conn->status_line, 0);
+ if (rc == -1)
+ {
+ grecs_error (NULL, errno, "strsplit3");
+ exit (EX_OSERR);
+ }
+ else if (rc == 1)
+ {
+ grecs_error (NULL, 0, _("unexpected reply: %s"),
+ conn->readbuf.base);
+ exit (EX_UNAVAILABLE);
+ }
+
+ if (strcmp (conn->status_line[0], http_version))
+ {
+ grecs_error (NULL, 0, _("unsupported HTTP version: %s"),
+ conn->status_line[0]);
+ exit (EX_UNAVAILABLE);
+ }
+ n = strtoul (conn->status_line[1], &p, 0);
+ if (*p || n > 599)
+ {
+ grecs_error (NULL, 0, _("bad reply status: %s"),
+ conn->readbuf.base);
+ exit (EX_UNAVAILABLE);
+ }
+ conn->resp.code = n;
+
+ state = is_headers;
+ }
+ else if (state == is_headers)
+ {
+ if (conn->readbuf.base[0])
+ shttp_save_header (conn);
+ else
+ state = is_content;
+ }
+ }
+
+ val = shttp_io_header_get (&conn->resp, "content-length");
+ if (val)
+ {
+ ssize_t rc;
+ char *p;
+ unsigned long len = strtoul (val, &p, 10);
+
+ if (*p)
+ {
+ grecs_error (NULL, 0, _("bad content length: %s"), val);
+ exit (EX_UNAVAILABLE);
+ }
+
+ if (len)
+ {
+ conn->resp.content = grecs_malloc (len + 1);
+ conn->resp.content_length = len;
+ rc = fread (conn->resp.content, len, 1, conn->fp);
+ if (rc != 1)
+ {
+ grecs_error (NULL, ferror (conn->fp) ? errno : 0,
+ _("content read error"));
+ exit (EX_UNAVAILABLE);
+ }
+
+ val = shttp_io_header_get (&conn->resp, "content-type");
+ if (!val)
+ grecs_error (NULL, 0, _("no content type specified"));
+ else if (strcmp (val, "application/json"))
+ grecs_error (NULL, 0, _("unsupported content type: %s"), val);
+ else
+ {
+ conn->result = json_parse_string (conn->resp.content,
+ conn->resp.content_length);
+ if (!conn->result)
+ {
+ grecs_error (&json_err_locus, 0,
+ "%s", json_err_diag);
+ grecs_error (NULL, 0, _("original json was: %s"),
+ conn->resp.content);
+ grecs_error (NULL, 0, _("original status line: %s %s %s"),
+ conn->status_line[0],
+ conn->status_line[1],
+ conn->status_line[2]);
+ exit (EX_UNAVAILABLE);
+ }
+ }
+ }
+ }
+}
+
+static void
+stderr_writer (void *closure, char const *text, size_t len)
+{
+ fwrite (text, len, 1, stderr);
+}
+
+static void
+shttp_format_result (struct shttp_connection *conn)
+{
+ struct json_format fmt = {
+ .indent = 2,
+ .precision = 0,
+ .write = stderr_writer,
+ };
+ fprintf (stderr, "%s: raw JSON reply follows:\n", program_name);
+ json_format_value (conn->result, &fmt);
+}
+
+static void
+shttp_fatal (struct shttp_connection *conn)
+{
+ struct json_value *jv;
+
+ if (conn->result && (jv = json_value_lookup (conn->result, "error_message")))
+ {
+ if (jv->type == json_string)
+ grecs_error (NULL, 0, "%s", jv->v.s);
+ else
+ shttp_format_result (conn);
+ }
+ else
+ grecs_error (NULL, 0, "%s", conn->status_line[2]);
+
+ exit (1);
+}
+
+static void
+echo_off (struct termios *stored_settings)
+{
+ struct termios new_settings;
+ tcgetattr (0, stored_settings);
+ new_settings = *stored_settings;
+ new_settings.c_lflag &= (~ECHO);
+ tcsetattr (0, TCSANOW, &new_settings);
+}
+
+static void
+echo_on (struct termios *stored_settings)
+{
+ tcsetattr (0, TCSANOW, stored_settings);
+}
+
+static char *
+getans (int echo, const char *fmt, ...)
+{
+ struct termios stored_settings;
+ char *reply = NULL;
+ size_t reply_len = 0;
+ size_t len;
+ va_list ap;
+
+ va_start (ap, fmt);
+ vprintf (fmt, ap);
+ va_end (ap);
+ printf (": ");
+ fflush (stdout);
+ if (!echo)
+ echo_off (&stored_settings);
+ grecs_getline (&reply, &reply_len, stdin);
+ if (!echo)
+ {
+ echo_on (&stored_settings);
+ putchar ('\n');
+ fflush (stdout);
+ }
+ len = strlen (reply);
+ if (len)
+ reply[len - 1] = 0;
+ return reply;
+}
+
+static void
+shttp_get_credentials (struct shttp_connection *conn)
+{
+ char const *val = shttp_io_header_get (&conn->resp, "WWW-Authenticate");
+ char *realm = NULL;
+
+ if (val)
+ {
+ struct wordsplit ws;
+ if (wordsplit (val, &ws,
+ WRDSF_NOVAR | WRDSF_NOCMD
+ | WRDSF_QUOTE | WRDSF_SQUEEZE_DELIMS))
+ grecs_error (NULL, 0, "wordsplit: %s", wordsplit_strerror (&ws));
+ else
+ {
+ if (ws.ws_wordc > 0)
+ {
+ size_t i;
+
+ if (strcmp (ws.ws_wordv[0], "Basic"))
+ {
+ grecs_error (NULL, 0,
+ _("unsupported authentication type: %s"),
+ ws.ws_wordv[0]);
+ shttp_fatal (conn);
+ }
+ for (i = 1; i < ws.ws_wordc; i++)
+ if (strncmp (ws.ws_wordv[i], "realm=", 6) == 0)
+ {
+ realm = grecs_strdup (ws.ws_wordv[i] + 6);
+ break;
+ }
+ }
+ wordsplit_free (&ws);
+ }
+ }
+
+ if (!realm)
+ realm = grecs_strdup ("Server");
+
+ conn->url->user = getans (1, "%s user", realm);
+ conn->url->passwd = getans (0, "%s password", realm);
+
+ grecs_free (realm);
+}
+
+static void
+shttp_process (struct shttp_connection *conn, int method, char const *uri)
+{
+ for (;;)
+ {
+ shttp_request_send (conn, method, uri);
+ shttp_get_reply (conn);
+ if (conn->resp.code / 100 == 2)
+ return;
+ if (conn->resp.code == 401 && isatty (fileno (stdin)))
+ shttp_get_credentials (conn);
+ else
+ shttp_fatal (conn);
+ }
+}
+
+static int
+com_list (struct shttp_connection *conn, int argc, char **argv)
+{
+ shttp_process (conn, METH_GET, "/programs");
+ return 0;
+}
+
+static int
+com_stop (struct shttp_connection *conn, int argc, char **argv)
+{
+ abort ();
+ return 0;
+}
+
+static int
+com_start (struct shttp_connection *conn, int argc, char **argv)
+{
+ abort ();
+ return 0;
+}
+
+static int
+com_restart (struct shttp_connection *conn, int argc, char **argv)
+{
+ abort ();
+ return 0;
+}
+
+static int
+com_shutdown (struct shttp_connection *conn, int argc, char **argv)
+{
+ abort ();
+ return 0;
+}
+
+static int
+com_reboot (struct shttp_connection *conn, int argc, char **argv)
+{
+ abort ();
+ return 0;
+}
+
+typedef int (*ctlcom_t) (struct shttp_connection *, int, char **);
+
+struct comtab
+{
+ char const *name;
+ ctlcom_t command;
+};
+
+static struct comtab comtab[] = {
+ { "list", com_list },
+ { "stop", com_stop },
+ { "start", com_start },
+ { "restart", com_restart },
+ { "shutdown", com_shutdown },
+ { "reboot", com_reboot },
+ { NULL }
+};
+
+static struct comtab *
+find_command (char const *name)
+{
+ struct comtab *p, *match = NULL;
+ size_t match_count = 0;
+ size_t len = strlen (name);
+
+ for (p = comtab; p->name; p++)
+ if (len <= strlen (p->name)
+ && memcmp (p->name, name, len) == 0)
+ {
+ if (!match)
+ {
+ match = p;
+ ++match_count;
+ }
+ else
+ {
+ if (match_count == 1)
+ fprintf (stderr, _("%s: ambiguous command %s:\n"),
+ program_name, name);
+ ++match_count;
+ fprintf (stderr, " %s\n", p->name);
+ }
+ }
+
+ if (match_count == 0)
+ {
+ grecs_error (NULL, 0, _("unknown command: %s"), name);
+ exit (EX_USAGE);
+ }
+ else if (match_count > 1)
+ exit (EX_USAGE);
+
+ return match;
+}
+
+
+int
+main (int argc, char **argv)
+{
+ int i, rc;
+ struct shttp_connection *conn;
+ struct comtab *cmd;
+
+ set_program_name (argv[0]);
+#ifdef ENABLE_NLS
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+#endif
+ grecs_print_diag_fun = piesctl_diag;
+
+ parse_options (argc, argv, &i);
+ argc -= i;
+ argv += i;
+
+ parse_config ();
+
+ conn = shttp_connect (url);
+ if (!conn)
+ return EX_UNAVAILABLE;
+
+ cmd = find_command (argv[0]);
+ argv[0] = (char*) cmd->name;
+ rc = cmd->command (conn, argc, argv);
+ shttp_close (conn);
+ return rc;
+}

Return to:

Send suggestions and report system problems to the System administrator.