/* This file is part of GNU Pies. Copyright (C) 2007-2017 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 "base64.h" struct control control; pies_identity_t identity; 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) { size_t minsize = buf->level + s; while (minsize >= buf->size) { if (buf->size == 0) buf->size = CTLBUFSIZE; else buf->size *= CTLBUFSIZE; buf->base = grecs_realloc (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; } 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); if (size < 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; } 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 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 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); } 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; }; 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; } static void input_destroy (struct input *inp) { ctlbuf_free (&inp->ibuf); grecs_symtab_free (inp->headers); free (inp->input_method); free (inp->input_uri); free (inp->input_proto); } static void input_reset (struct input *inp) { inp->input_state = is_initial; grecs_symtab_clear (inp->headers); free (inp->input_method); inp->input_method = NULL; free (inp->input_uri); inp->input_uri = NULL; free (inp->input_proto); 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; char *reqline[3]; ctlbuf_chomp (&inp->ibuf); rc = strsplit3 (inp->ibuf.base, reqline, 1); if (rc == 0) { inp->input_method = reqline[0]; inp->input_uri = reqline[1]; inp->input_proto = reqline[2]; } else if (rc == -1) { logmsg (LOG_ERR, _("can't parse input line: %s"), strerror (errno)); return 500; } 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; break; case is_end: break; } return 0; } struct output { unsigned code; struct grecs_symtab *headers; struct json_value *reply; }; static void output_init (struct output *out) { 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; } 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; size_t len = 0; 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 = NULL; grecs_vasprintf (&ret->value, &len, fmt, ap); va_end (ap); return 0; } static struct json_value * json_reply_create (void) { return json_new_object (); } void json_object_set_string (struct json_value *obj, char const *name, char const *fmt, ...) { va_list ap; char *s = NULL; size_t l = 0; va_start (ap, fmt); grecs_vasprintf (&s, &l, fmt, ap); va_end (ap); json_object_set (obj, name, json_new_string (s)); grecs_free (s); } void json_object_set_number (struct json_value *obj, char const *name, double val) { json_object_set (obj, name, json_new_number (val)); } void json_object_set_bool (struct json_value *obj, char const *name, int val) { json_object_set (obj, name, json_new_bool (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, "status", "ER"); 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; int code; struct ctlbuf obuf; }; static struct ctlio * ctlio_create (void) { struct ctlio *io; io = grecs_malloc (sizeof (*io)); 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->obuf); return io; } void ctlio_destroy (struct ctlio *io) { input_destroy (&io->input); output_destroy (&io->output); ctlbuf_free (&io->obuf); 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); 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 = NULL; size_t len = 0; va_start (ap, fmt); grecs_vasprintf (&str, &len, fmt, ap); va_end (ap); io->output.reply = json_error_reply_create (str); grecs_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)); } static void ctlio_printf (struct ctlio *io, const char *fmt, ...) { va_list ap; char *str = NULL; size_t len = 0; va_start (ap, fmt); grecs_vasprintf (&str, &len, fmt, ap); va_end (ap); ctlio_print (io, str); free (str); } static void ctlio_eol (struct ctlio *io) { ctlio_print (io, CRLF); } static int format_header (void *sym, void *data) { 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 json_writer (void *closure, char const *text, size_t len) { struct ctlbuf *buf = closure; ctlbuf_write (buf, text, len); } static void ctlio_adjust_format (struct ctlio *io, struct json_format *fmt) { char const *val = http_get_header (io->input.headers, "X-Pies-Output"); if (!val) return; if (strcasecmp (val, "pretty") == 0) fmt->indent = 2; } static void ctlio_finalize_reply (struct ctlio *io) { size_t size; char const *val; struct ctlbuf tmpbuf; 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; } 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) { struct json_format fmt = { .indent = 0, .precision = 0, .write = json_writer, .data = &tmpbuf }; ctlio_adjust_format (io, &fmt); 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"); } } 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; pies_identity_t id; struct component const *comp; }; static int try_auth (struct prog *prog, void *data) { struct auth_data *auth = data; if (IS_COMPONENT (prog)) { if ((prog->v.p.comp->adm_acl && check_acl (prog->v.p.comp->adm_acl, (struct sockaddr *)&auth->addr, auth->addrlen, auth->id) == 0) || (prog->v.p.comp->list_acl && check_acl (prog->v.p.comp->list_acl, (struct sockaddr *)&auth->addr, auth->addrlen, auth->id) == 0)) { auth->comp = prog->v.p.comp; return 1; } } return 0; } 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 (name); int new_state = CTL_INITIAL_STATE; if (!id) { logmsg (LOG_AUTH, _("%s: can't authenticate: %s"), name, strerror (errno)); return -1; } 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 identity provider %s..."), pname)); 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"), 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"), 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"), name, pname, ad.comp->tag); } else { logmsg (LOG_AUTH, _("%s authenticated via %s, but failed ACL check"), name, pname); } } break; } } if (new_state == CTL_INITIAL_STATE) { pies_identity_destroy (id); return 1; } identity = id; io->state = new_state; return 0; } static int ctlio_authenticate (struct ctlio *io) { const char *val; val = http_get_header (io->input.headers, "Authorization"); if (val) { size_t len = strlen (val); char *data = NULL; size_t datalen = 0; if (len > 6 && memcmp (val, "Basic ", 6) == 0) { 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 = grecs_malloc (s + 1); memcpy (user, data, s); user[s] = 0; passwd = grecs_malloc (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); } else { output_set_header (&io->output, "WWW-Authenticate", "Basic realm=\"%s\"", control.realm ? control.realm : "pies"); ctlio_reply (io, 401, NULL); } return 1; } enum http_method { METH_GET, METH_POST, METH_DELETE, METH_PUT, METH_OPTIONS, METH_INVALID }; #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 int method_decode (char const *method) { int i; for (i = 0; method_names[i]; i++) { if (strcmp (method_names[i], method) == 0) return i; } return METH_INVALID; } static void res_instance (struct ctlio *, enum http_method, char const *, struct json_value *); static void res_programs (struct ctlio *, enum http_method, char const *, struct json_value *); static void res_runlevel (struct ctlio *, enum http_method, char const *, struct json_value *); static void res_environ (struct ctlio *, enum http_method, char const *, struct json_value *); static void res_conf (struct ctlio *, enum http_method, char const *, struct json_value *); static int pred_sysvinit (void); struct ctlio_resource { char const *uri; size_t uri_len; int states; int (*predicate) (void); void (*handler) (struct ctlio *, enum http_method, char const *uri, struct json_value *); }; static struct ctlio_resource restab[] = { #define S(s) #s, (sizeof (#s)-1) { S(/instance), CTL_ADMIN_STATE, NULL, res_instance }, { S(/conf), CTL_ADMIN_STATE, NULL, res_conf }, { S(/programs), CTL_ADMIN_STATE|CTL_USER_STATE, NULL, res_programs }, { S(/runlevel), CTL_ADMIN_STATE, pred_sysvinit, res_runlevel }, { S(/environ), CTL_ADMIN_STATE, pred_sysvinit, res_environ }, { NULL } #undef S }; static struct ctlio_resource * find_resource (struct ctlio *io, const char *endpoint) { struct ctlio_resource *p; size_t len = strcspn (endpoint, "?"); if (len == 0) return NULL; if (endpoint[len-1] == '/') --len; for (p = restab; p->uri; p++) if (len >= p->uri_len && memcmp (p->uri, endpoint, p->uri_len) == 0 && (endpoint[p->uri_len] == 0 || endpoint[p->uri_len] == '/' || endpoint[p->uri_len] == '?') && (!p->predicate || p->predicate())) return p; return NULL; } static char * json_extract (char *uri) { char *p = strchr (uri, '?'); if (p) { *p++ = 0; return p; } return NULL; } static size_t delim_count (char const *str, int delim) { size_t i = 0; for (; *str; str++) if (*str == delim) ++i; return i; } static int check_accepted_media (struct ctlio *io, char const *type, char const *subtype) { char const *val; struct wordsplit ws; int rc = 1; size_t i; val = http_get_header (io->input.headers, "Accept"); if (!val) return 0; ws.ws_delim = ","; if (wordsplit (val, &ws, WRDSF_NOCMD | WRDSF_NOVAR | WRDSF_DELIM | WRDSF_WS | WRDSF_DQUOTE)) { logmsg (LOG_ERR, "wordsplit: %s", wordsplit_strerror (&ws)); ctlio_reply (io, 500, NULL); return -1; } for (i = 0; i < ws.ws_wordc; i++) { char *p = strchr (ws.ws_wordv[i], ';'); if (p) { while (p > ws.ws_wordv[i] && ISWS (p[-1])) --p; *p = 0; } p = strchr (ws.ws_wordv[i], '/'); if (!p) continue; *p++ = 0; if ((strcmp (ws.ws_wordv[i], "*") == 0 || strcmp (ws.ws_wordv[i], type) == 0) && (strcmp (p, "*") == 0 || strcmp (p, subtype) == 0)) { rc = 0; break; } } wordsplit_free (&ws); if (rc) ctlio_reply (io, 406, NULL); return rc; } static void ctlio_do_command (struct ctlio *io) { const char *val; struct ctlio_resource *res; enum http_method method; struct json_value *json; char *uri = io->input.input_uri; char *json_query = NULL; if (strcmp (io->input.input_proto, "HTTP/1.1")) { ctlio_reply (io, 505, NULL); return; } val = http_get_header (io->input.headers, "Content-Type"); if (val) { size_t len = strcspn (val, ";"); if (strncmp (val, "application/json", len)) { ctlio_reply (io, 415, "Unsupported content type"); return; } } if (check_accepted_media (io, "application", "json")) return; method = method_decode (io->input.input_method); if (method == METH_INVALID) { ctlio_reply (io, 405, NULL); return; } res = find_resource (io, uri); if (!res) { ctlio_reply (io, 404, "resource not found"); return; } uri += res->uri_len; if (uri[0]) { json_query = json_extract (uri); if (delim_count (uri, '/') > 1) { ctlio_reply (io, 404, "resource not found"); return; } } if (!uri[0]) uri = NULL; if (!(io->state & res->states)) { if (ctlio_authenticate (io)) return; if (!(io->state & res->states)) { ctlio_reply (io, 403, NULL); return; } } if (io->input.input_content_length) { if (json_query) { ctlio_reply (io, 400, "JSON query supplied twice"); return; } json = json_parse_string (ctlbuf_peek (&io->input.ibuf), ctlbuf_rdsize (&io->input.ibuf)); if (!json) { ctlio_reply (io, 400, "JSON error: %s", json_err_diag); return; } } else if (json_query) { if (method == METH_POST) { ctlio_reply (io, 400, NULL); return; } else { json = json_parse_string (json_query, strlen (json_query)); if (!json) { ctlio_reply (io, 400, "JSON error: %s", json_err_diag); return; } } } else json = json_new_bool (1); res->handler (io, method, uri, json); json_value_free (json); } 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) { if (c == EOT) return ctlio_end (fd, io); else { int rc = input_append (&io->input, c); if (rc == 0) return 0; if (rc == 200) ctlio_do_command (io); else ctlio_reply (io, rc, NULL); ctlio_finalize_reply (io); ctlio_reset (io); update_socket (fd, PIES_EVT_RD, NULL); update_socket (fd, PIES_EVT_WR, ctlwr); } } 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)) { ssize_t rc = write (fd, &c, 1); if (rc != 1) { if (rc == 0) logmsg (LOG_ERR, _("error writing to control socket")); else logmsg (LOG_ERR, _("error writing to control socket: %s"), strerror (errno)); return ctlio_end (fd, io); } } else if (io->state == CTL_END_STATE) return ctlio_end (fd, io); else if (io->state == CTL_ACTION_STATE) { pies_schedule_action (io->action); 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) { logfuncall ("accept", NULL, 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.conn_acl, (struct sockaddr *)&addr, addrlen, NULL)) { close (fd); return 1; } /* FIXME: Check number of connections? */ io = ctlio_create (); io->addr = addr; io->addrlen = addrlen; register_socket (fd, NULL, ctlwr, NULL, io); return 0; } int ctl_open (void) { int fd; if (!control.url) return 0; fd = create_socket (control.url, SOCK_STREAM, NULL, 077); if (fd == -1) { logmsg (LOG_CRIT, _("can't create control socket %s"), control.url->string); return -1; } if (listen (fd, 8)) { logfuncall ("listen", control.url->string, errno); return -1; } register_socket (fd, ctl_accept, NULL, NULL, NULL); return 0; } static void idfmt_string (struct ctlio *io, char const *name, void *ptr) { json_object_set_string (io->output.reply, name, "%s", (char*) ptr); } static void idfmt_string_ptr (struct ctlio *io, char const *name, void *ptr) { char **str = ptr; json_object_set_string (io->output.reply, name, "%s", *str); } static void idfmt_pid (struct ctlio *io, char const *name, void *ptr) { json_object_set_number (io->output.reply, name, getpid ()); } static void idfmt_argv (struct ctlio *io, char const *name, void *ptr) { struct json_value *ar = json_new_array (); size_t i; for (i = 0; i < pies_master_argc; i++) json_array_append (ar, json_new_string (pies_master_argv[i])); json_object_set (io->output.reply, name, ar); } static void idfmt_binary (struct ctlio *io, char const *name, void *ptr) { json_object_set_string (io->output.reply, name, "%s", pies_master_argv[0]); } static void res_instance (struct ctlio *io, enum http_method meth, char const *uri, struct json_value *req) { static struct idparam { char *name; void (*fmt) (struct ctlio *, char const *, void *); void *data; } idparam[] = { { "package", idfmt_string, PACKAGE_NAME }, { "version", idfmt_string, PACKAGE_VERSION }, { "binary", idfmt_binary, NULL }, { "PID", idfmt_pid, NULL }, { "instance", idfmt_string_ptr, &instance }, { "argv", idfmt_argv, NULL }, { NULL } }; struct idparam *p; io->output.reply = json_reply_create (); if (uri) { ++uri; /* skip leading / */ if (meth == METH_GET) { for (p = idparam; p->name; p++) if (strcmp (p->name, uri) == 0) break; if (p->name) { p->fmt (io, p->name, p->data); io->code = 200; } else ctlio_reply (io, 404, NULL); } else if (strcmp (uri, "PID") == 0 || strcmp (uri, "instance") == 0) { if (meth == METH_DELETE) { json_object_set_string (io->output.reply, "status", "OK"); io->action = ACTION_STOP; io->state = CTL_ACTION_STATE; io->code = 200; } else if (meth == METH_POST || meth == METH_PUT) { json_object_set_string (io->output.reply, "status", "OK"); io->action = ACTION_RESTART; io->state = CTL_ACTION_STATE; io->code = 201; } else ctlio_reply (io, 405, NULL); } else ctlio_reply (io, 405, NULL); } else if (meth != METH_GET) ctlio_reply (io, 405, NULL); else { for (p = idparam; p->name; p++) p->fmt (io, p->name, p->data); io->code = 200; } } struct eval_env { struct ctlio *io; int allowed_state; struct pcond_node *cond; int (*fun) (struct json_value *, struct prog *); struct json_value *result; size_t total_count; size_t success_count; size_t noperm_count; }; static int auth_prog (struct prog *prog, struct ctlio *io) { if (io->state == CTL_ADMIN_STATE) return CTL_ADMIN_STATE|CTL_USER_STATE; switch (prog->type) { case TYPE_REDIRECTOR: prog = prog->v.r.master; /* FALL THROUGH */ case TYPE_COMPONENT: if (prog->v.p.comp->adm_acl && check_acl (prog->v.p.comp->adm_acl, (struct sockaddr *)&io->addr, io->addrlen, identity) == 0) return CTL_ADMIN_STATE; if (check_acl (prog->v.p.comp->list_acl, (struct sockaddr *)&io->addr, io->addrlen, identity) == 0) return CTL_USER_STATE; default: break; } return 0; } 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 pies_status_str[] = { [status_stopped] = "stopped", [status_running] = "running", [status_listener] = "listener", [status_sleeping] = "sleeping", [status_stopping] = "stopping", [status_finished] = "finished" }; static void format_idx (struct json_value *obj, const char *name, unsigned i, char * const *a, size_t s) { if (i < s && a[i]) json_object_set (obj, name, json_new_string (a[i])); } #define FORMAT_IDX(io,n,a,i) \ format_idx (io, n, i, a, sizeof(a)/sizeof(a[0])) enum pcond_type { pcond_false, pcond_true, pcond_component, pcond_type, pcond_mode, pcond_active, pcond_status, pcond_not, pcond_and, pcond_or }; struct pcond_node { enum pcond_type type; union { char *tag; enum pies_comp_mode mode; enum prog_status status; enum prog_type type; struct { size_t c; struct pcond_node **v; } arg; } v; }; static struct pcond_node * pcond_node_alloc (enum pcond_type t) { struct pcond_node *pn; pn = grecs_zalloc (sizeof (*pn)); pn->type = t; return pn; } static void pcond_node_free (struct pcond_node *node) { if (!node) return; switch (node->type) { case pcond_not: case pcond_and: case pcond_or: { size_t i; for (i = 0; i < node->v.arg.c; i++) pcond_node_free (node->v.arg.v[i]); } grecs_free (node->v.arg.v); break; default: break; } grecs_free (node); } static int pcond_eval (struct pcond_node *node, struct prog *p) { size_t i; if (!node) return 0; switch (node->type) { case pcond_true: return 1; case pcond_false: return 0; case pcond_component: return strcmp (prog_tag (p), 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_active: return p->active; case pcond_status: return IS_COMPONENT (p) && p->v.p.status == node->v.status; case pcond_not: return !pcond_eval (node->v.arg.v[0], p); case pcond_and: for (i = 0; i < node->v.arg.c; i++) if (!pcond_eval (node->v.arg.v[i], p)) return 0; return 1; case pcond_or: for (i = 0; i < node->v.arg.c; i++) if (pcond_eval (node->v.arg.v[i], p)) return 1; return 0; default: abort (); } } static int json_to_pcond (struct ctlio *io, struct json_value *val, struct pcond_node **pnode); static int pcond_conv_component (struct pcond_node *node, struct json_value *arg, struct ctlio *io) { if (arg->type != json_string) { ctlio_reply (io, 400, "component must be string"); return -1; } node->v.tag = arg->v.s; return 0; } static int pcond_conv_n (struct pcond_node *node, struct json_value *arg, struct ctlio *io, char * const *ar, const char *what) { int n; if (arg->type != json_string) { ctlio_reply (io, 400, "%s must be string", what); return -1; } n = array_index (ar, arg->v.s); if (n == -1) { ctlio_reply (io, 400, "bad %s", what); return -1; } return n; } static int pcond_conv_type (struct pcond_node *node, struct json_value *arg, struct ctlio *io) { int n = pcond_conv_n (node, arg, io, pies_type_str, "type"); if (n == -1) return -1; node->v.type = n; return 0; } static int pcond_conv_mode (struct pcond_node *node, struct json_value *arg, struct ctlio *io) { int n = pcond_conv_n (node, arg, io, pies_type_str, "mode"); if (n == -1) return -1; node->v.mode = n; return 0; } static int pcond_conv_status (struct pcond_node *node, struct json_value *arg, struct ctlio *io) { int n = pcond_conv_n (node, arg, io, pies_status_str, "status"); if (n == -1) return -1; node->v.status = n; return 0; } static int pcond_conv_not (struct pcond_node *node, struct json_value *arg, struct ctlio *io) { node->v.arg.v = grecs_calloc (1, sizeof (node->v.arg.v[0])); node->v.arg.c = 1; return json_to_pcond (io, arg, &node->v.arg.v[0]); } static int pcond_conv_binary (struct pcond_node *node, struct json_value *arg, struct ctlio *io) { size_t i, n; if (arg->type != json_arr) { ctlio_reply (io, 400, "arguments to binary opcode must be array"); return -1; } n = json_array_size (arg); node->v.arg.v = grecs_calloc (n, sizeof (node->v.arg.v[0])); node->v.arg.c = n; for (i = 0; i < n; i++) { struct json_value *v; if (json_array_get (arg, i, &v) == 0) { if (json_to_pcond (io, v, &node->v.arg.v[i])) return -1; } } return 0; } struct pcond_conv { char *term; int (*handler) (struct pcond_node *, struct json_value *arg, struct ctlio *); }; static struct pcond_conv pcond_conv[] = { [pcond_component] = { "component", pcond_conv_component }, [pcond_type] = { "type", pcond_conv_type }, [pcond_mode] = { "mode", pcond_conv_mode }, [pcond_active] = { "active", NULL }, [pcond_status] = { "status", pcond_conv_status }, [pcond_not] = { "not", pcond_conv_not }, [pcond_and] = { "and", pcond_conv_binary }, [pcond_or] = { "or", pcond_conv_binary }, }; static int max_type = sizeof (pcond_conv) / sizeof (pcond_conv[0]); static int pcond_conv_find (char const *name) { int i; for (i = 0; i < max_type; i++) { if (pcond_conv[i].term && strcmp (pcond_conv[i].term, name) == 0) return i; } return -1; } static int object_to_cond (struct ctlio *io, struct json_value *obj, struct pcond_node **pnode) { struct json_value *op, *arg; struct pcond_node *node; int type; if (json_object_get (obj, "op", &op)) { ctlio_reply (io, 400, "bad conditional: missing op"); return -1; } if (op->type != json_string) { ctlio_reply (io, 400, "bad conditional: bad op type"); return -1; } type = pcond_conv_find (op->v.s); if (type == -1) { ctlio_reply (io, 400, "bad conditional: unknown opcode"); return -1; } if (json_object_get (obj, "arg", &arg) && pcond_conv[type].handler) { ctlio_reply (io, 400, "bad conditional: missing arg"); return -1; } node = pcond_node_alloc (type); if (pcond_conv[type].handler && pcond_conv[type].handler (node, arg, io)) { pcond_node_free (node); return -1; } *pnode = node; return 0; } static int json_to_pcond (struct ctlio *io, struct json_value *val, struct pcond_node **pnode) { if (!val) *pnode = pcond_node_alloc (pcond_true); else switch (val->type) { case json_null: *pnode = NULL; break; case json_bool: *pnode = pcond_node_alloc (val->v.b ? pcond_true : pcond_false); break; case json_number: case json_string: case json_arr: ctlio_reply (io, 400, "bad conditional"); return -1; case json_object: return object_to_cond (io, val, pnode); } return 0; } static int selector (struct prog *prog, void *data) { struct eval_env *env = data; if (pcond_eval (env->cond, prog)) { if (auth_prog (prog, env->io) & env->allowed_state) { struct json_value *status = json_new_object (); json_object_set_string (status, "tag", prog_tag (prog)); if (env->fun (status, prog) == 0) env->success_count++; json_array_append (env->result, status); env->total_count++; } else env->noperm_count++; } return 0; } /* Return true if PROG is active. For TCPMUX progs, this takes into account the state of the master listener. FIXME: Ideally all TCPMUXen must form a list attached to their master listener. When this is fixed, this function will disappear. */ static int prog_active (struct prog *prog) { if (prog->active) { if (ISCF_TCPMUX (prog->v.p.comp->flags)) { prog = progman_locate (prog->v.p.comp->tcpmux); return prog && prog->active; } } return prog->active; } static struct json_value * prog_serialize (struct json_value *ret, struct prog *prog) { struct json_value *v; size_t i; FORMAT_IDX (ret, "type", pies_type_str, prog->type); switch (prog->type) { case TYPE_COMPONENT: FORMAT_IDX (ret, "mode", pies_comp_mode_str, prog->v.p.comp->mode); FORMAT_IDX (ret, "status", pies_status_str, prog->v.p.status); json_object_set_bool (ret, "active", prog_active (prog)); if (prog->pid) json_object_set_number (ret, "PID", prog->pid); else if (prog->v.p.status == status_listener) { if (prog->v.p.comp->socket_url) json_object_set_string (ret, "URL", "%s", prog->v.p.comp->socket_url->string); if (prog->v.p.comp->service) json_object_set_string (ret, "service", "%s", prog->v.p.comp->service); if (ISCF_TCPMUX (prog->v.p.comp->flags) && prog->v.p.comp->tcpmux) json_object_set_string (ret, "master", "%s", prog->v.p.comp->tcpmux); } if (prog->v.p.comp->runlevels) json_object_set_string (ret, "runlevels", "%s", prog->v.p.comp->runlevels); if (prog->v.p.status == status_sleeping) json_object_set_number (ret, "wakeup-time", prog->v.p.timestamp + SLEEPTIME); v = json_new_array (); for (i = 0; i < prog->v.p.comp->argc; i++) json_array_append (v, json_new_string (prog->v.p.comp->argv[i])); json_object_set (ret, "argv", v); break; case TYPE_REDIRECTOR: json_object_set_number (ret, "PID", prog->pid); break; case TYPE_COMMAND: json_object_set_string (ret, "command", "%s", prog->v.c.command); } return ret; } static int fun_list (struct json_value *result, struct prog *prog) { prog_serialize (result, prog); return 0; } static int fun_stop (struct json_value *result, struct prog *prog) { if (!prog->active) { json_object_set_string (result, "status", "ER"); json_object_set_string (result, "error_message", "already stopped"); } else { prog->active = 0; progman_stop_component (&prog); json_object_set_string (result, "status", "OK"); } return 0; } static int fun_start (struct json_value *result, struct prog *prog) { if (!IS_COMPONENT (prog)) { json_object_set_string (result, "status", "ER"); json_object_set_string (result, "error_message", "not a component"); } else { switch (prog->v.p.status) { case status_stopped: prog->v.p.comp->flags &= ~CF_DISABLED; json_object_set_string (result, "status", "OK"); break; case status_sleeping: case status_finished: prog->v.p.status = status_stopped; prog->v.p.failcount = 0; prog->v.p.timestamp = 0; json_object_set_string (result, "status", "OK"); break; case status_listener: if (prog_activate_listener (prog) == 0) json_object_set_string (result, "status", "OK"); else { json_object_set_string (result, "status", "ER"); /* FIXME: error message */ json_object_set_string (result, "error_message", "can't open socket"); } break; default: json_object_set_string (result, "status", "ER"); json_object_set_string (result, "error_message", "already running"); return 0; } prog->active = 1; } return 0; } static int fun_restart (struct json_value *result, struct prog *prog) { if (!IS_COMPONENT (prog)) { json_object_set_string (result, "status", "ER"); json_object_set_string (result, "error_message", "not a component"); } else if (!prog->active) { json_object_set_string (result, "status", "ER"); json_object_set_string (result, "error_message", "not active"); } else { switch (prog->v.p.status) { case status_running: progman_stop_component (&prog); json_object_set_string (result, "status", "OK"); break; case status_listener: json_object_set_string (result, "status", "ER"); json_object_set_string (result, "error_message", "not applicable"); break; default: json_object_set_string (result, "status", "ER"); json_object_set_string (result, "error_message", "not running"); } } return 0; } static void select_and_run (struct ctlio *io, struct pcond_node *cond, enum http_method meth) { struct eval_env env; int wakeup = 0; memset (&env, 0, sizeof (env)); env.io = io; env.cond = cond; env.fun = NULL; env.result = NULL; switch (meth) { case METH_GET: env.allowed_state = CTL_USER_STATE | CTL_ADMIN_STATE; env.fun = fun_list; break; case METH_DELETE: env.allowed_state = CTL_ADMIN_STATE; env.fun = fun_stop; break; case METH_PUT: env.allowed_state = CTL_ADMIN_STATE; env.fun = fun_start; wakeup = 1; break; case METH_POST: env.allowed_state = CTL_ADMIN_STATE; env.fun = fun_restart; wakeup = 1; break; default: ctlio_reply (io, 405, NULL); } if (env.fun) { env.result = json_new_array (); progman_foreach (selector, &env); if (env.success_count == 0 && env.noperm_count) ctlio_reply (io, 403, NULL); else { if (env.success_count && wakeup) progman_wake_sleeping (1); io->output.reply = env.result; io->code = 200; } } } static void res_programs_select (struct ctlio *io, enum http_method meth, char const *uri, struct json_value *json) { struct pcond_node *cond; if (json_to_pcond (io, json, &cond) == 0) { select_and_run (io, cond, meth); pcond_node_free (cond); } } static void res_programs_component (struct ctlio *io, enum http_method meth, char const *uri, struct json_value *json) { struct pcond_node *np, *node; node = pcond_node_alloc (pcond_type); node->v.type = TYPE_COMPONENT; np = pcond_node_alloc (pcond_and); np->v.arg.c = 2; np->v.arg.v = grecs_calloc (np->v.arg.c, sizeof (np->v.arg.v[0])); np->v.arg.v[0] = node; np->v.arg.v[1] = pcond_node_alloc (pcond_component); np->v.arg.v[1]->v.tag = (char*) uri + 1; node = np; select_and_run (io, node, meth); pcond_node_free (node); } static void res_programs (struct ctlio *io, enum http_method meth, char const *uri, struct json_value *json) { if (uri) res_programs_component (io, meth, uri, json); else res_programs_select (io, meth, uri, json); } static int pred_sysvinit (void) { return init_process; } static void res_runlevel (struct ctlio *io, enum http_method meth, char const *uri, struct json_value *json) { if (uri) ctlio_reply (io, 404, NULL); else if (meth == METH_GET) { io->output.reply = json_reply_create (); io->code = 200; sysvinit_report (io->output.reply); } else if (meth == METH_PUT) { struct json_value *val; // { "runlevel": "3" } if (json_object_get (json, "runlevel", &val)) ctlio_reply (io, 400, "missing runlevel request"); else if (val->type != json_string) ctlio_reply (io, 400, "bad runlevel type"); else { io->output.reply = json_reply_create (); io->code = 200; if (strlen (val->v.s) == 1 && sysvinit_set_runlevel (val->v.s[0]) == 0) { json_object_set_string (io->output.reply, "status", "OK"); } else { json_object_set_string (io->output.reply, "status", "ER"); json_object_set_string (io->output.reply, "error_message", "invalid runlevel value"); } } } else ctlio_reply (io, 405, NULL); } /* GET /conf/runtime - List configuration * PUT /conf/runtime - Reload configuration * POST /conf/runtime - Post new configuration * * GET /conf/files - List configuration files * DELETE /conf/files - Delete some or all configuration files * from the list * POST /conf/files - Install new list configuration file */ static void conf_list_files (struct ctlio *io) { io->output.reply = json_new_array (); io->code = 200; config_file_list_serialize (io->output.reply); } static void conf_add_file (struct ctlio *io, struct json_value *json) { struct json_value *v; struct config_syntax *synt; if (json->type != json_object) { ctlio_reply (io, 400, NULL); return; } if (json_object_get (json, "syntax", &v) || v->type != json_string) { ctlio_reply (io, 400, NULL); return; } synt = str_to_config_syntax (v->v.s); if (!synt) { ctlio_reply (io, 400, NULL); return; } if (json_object_get (json, "file", &v) || v->type != json_string) { ctlio_reply (io, 400, NULL); return; } config_file_add (synt, v->v.s); ctlio_reply (io, 201, NULL); } static void conf_delete_files (struct ctlio *io, struct json_value *json) { io->code = 200; io->output.reply = json_reply_create (); if ((json->type == json_bool && json->v.b == 1) || (json->type == json_arr && json_array_size (json) == 0)) { config_file_remove_all (); json_object_set_string (io->output.reply, "status", "OK"); json_object_set_string (io->output.reply, "message", "file list cleared"); } else if (json->type == json_arr) { size_t i, n, fails = 0; struct json_value *reply; n = json_array_size (json); /* Check request */ for (i = 0; i < n; i++) { struct json_value *val; json_array_get (json, i, &val); if (val->type != json_string) { json_object_set_string (io->output.reply, "status", "ER"); json_object_set_string (io->output.reply, "error_message", "malformed request"); return; } } reply = json_new_array (); /* Process request */ for (i = 0; i < n; i++) { struct json_value *val; int rc; json_array_get (json, i, &val); rc = config_file_remove (val->v.s); if (rc) ++fails; json_array_append (reply, json_new_bool (!rc)); } if (fails == n) { json_object_set_string (io->output.reply, "status", "ER"); json_object_set_string (io->output.reply, "error_message", "no matching files found"); json_value_free (reply); } else { json_object_set_string (io->output.reply, "status", "OK"); if (fails) { json_object_set_string (io->output.reply, "error_message", "some files not removed"); json_object_set (io->output.reply, "result", reply); } else json_value_free (reply); } } } static void (*saved_diag_fun)(grecs_locus_t const *, int, int, const char *msg); static struct json_value *messages; static void reload_diag_fun (grecs_locus_t const *locus, int err, int errcode, const char *text) { struct json_value *msg = json_new_object (); if (locus) { struct json_value *ar = json_new_array (); json_array_append (ar, json_new_string (locus->beg.file)); json_array_append (ar, json_new_string (locus->end.file)); json_object_set (msg, "file", ar); ar = json_new_array (); json_array_append (ar, json_new_number (locus->beg.line)); json_array_append (ar, json_new_number (locus->end.line)); json_object_set (msg, "line", ar); ar = json_new_array (); json_array_append (ar, json_new_number (locus->beg.col)); json_array_append (ar, json_new_number (locus->end.col)); json_object_set (msg, "col", ar); json_object_set (msg, "error", json_new_bool (err)); if (errcode) { json_object_set (msg, "syserrno", json_new_number (errno)); json_object_set (msg, "syserrstr", json_new_string (strerror (errno))); } json_object_set (msg, "message", json_new_string (text)); json_array_append (messages, msg); } saved_diag_fun (locus, err, errcode, text); } static void conf_reload (struct ctlio *io) { io->code = 200; io->output.reply = json_reply_create (); saved_diag_fun = grecs_print_diag_fun; grecs_print_diag_fun = reload_diag_fun; messages = json_new_array (); if (pies_reread_config ()) { json_object_set_string (io->output.reply, "status", "ER"); json_object_set_string (io->output.reply, "error_message", "configuration syntax error"); } else { pies_schedule_action (ACTION_COMMIT); json_object_set_string (io->output.reply, "status", "OK"); json_object_set_string (io->output.reply, "message", "reload successful"); } json_object_set (io->output.reply, "parser_messages", messages); grecs_print_diag_fun = saved_diag_fun; } static void res_conf (struct ctlio *io, enum http_method meth, char const *uri, struct json_value *json) { if (!uri) { ctlio_reply (io, 404, NULL); return; } ++uri; /* skip leading / */ if (strcmp (uri, "files") == 0) { switch (meth) { case METH_GET: conf_list_files (io); break; case METH_POST: conf_add_file (io, json); break; case METH_DELETE: conf_delete_files (io, json); break; default: ctlio_reply (io, 405, NULL); } } else if (strcmp (uri, "runtime") == 0) { switch (meth) { case METH_GET: case METH_POST: ctlio_reply (io, 501, NULL); break; case METH_PUT: conf_reload (io); break; default: ctlio_reply (io, 405, NULL); } } else ctlio_reply (io, 404, NULL); } /* GET /environ - List entire environment * ["RUNLEVEL=3", "CONSOLE=/dev/tty", ...] * GET /environ/NAME - Get value of variable NAME * { "status":"OK", "value":"..." } * { "status":"ER", "error_message":"..." } * DELETE /environ/NAME - Unset variable * { "status":"OK" } * { "status":"ER", "error_message":"..." } * PUT /environ/NAME=VALUE - Set variable * { "status":"OK" } * { "status":"ER", "error_message":"..." } */ static void env_reply (struct ctlio *io, int ok, int rc) { switch (rc) { case 0: io->code = ok; io->output.reply = json_reply_create (); json_object_set_string (io->output.reply, "status", "OK"); break; case 1: ctlio_reply (io, 403, NULL); break; case -1: ctlio_reply (io, 404, NULL); } } static void res_environ (struct ctlio *io, enum http_method meth, char const *uri, struct json_value *json) { if (meth == METH_GET) { if (uri && uri[1]) { char *value; if (sysvinit_envlocate (uri + 1, &value) == -1) ctlio_reply (io, 404, NULL); else { env_reply (io, 200, 0); json_object_set_string (io->output.reply, "value", "%s", value); } } else { size_t i; io->output.reply = json_new_array (); io->code = 200; for (i = 0; sysvinit_environ_hint[i]; i++) { json_array_append (io->output.reply, json_new_string (sysvinit_environ_hint[i])); } } } else if (meth == METH_DELETE) { if (!(uri && uri[0])) ctlio_reply (io, 400, NULL); else env_reply (io, 200, sysvinit_envupdate (uri + 1)); } else if (meth == METH_PUT) { if (!(uri && uri[0])) ctlio_reply (io, 400, NULL); else env_reply (io, 201, sysvinit_envupdate (uri + 1)); } else ctlio_reply (io, 405, NULL); }