diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2015-12-31 13:59:18 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2015-12-31 15:58:17 +0200 |
commit | 00e6c3c3ed06a258a02943fc49fa7c528025d747 (patch) | |
tree | 52530660be48fb5f4611bcc9393886bab8a70b72 /src/piesctl.c | |
parent | 7f204cc788de3e03a51087b1273deb5b59288cf2 (diff) | |
download | pies-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.c | 1067 |
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; +} |