diff options
Diffstat (limited to 'src/main-cynagoracli.c')
-rw-r--r-- | src/main-cynagoracli.c | 830 |
1 files changed, 830 insertions, 0 deletions
diff --git a/src/main-cynagoracli.c b/src/main-cynagoracli.c new file mode 100644 index 0000000..db36243 --- /dev/null +++ b/src/main-cynagoracli.c @@ -0,0 +1,830 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author José Bollo <jose.bollo@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/******************************************************************************/ +/******************************************************************************/ +/* IMPLEMENTATION OF CYNAGORA ADMINISTRATION TOOL */ +/******************************************************************************/ +/******************************************************************************/ + +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <signal.h> +#include <poll.h> +#include <sys/epoll.h> + +#include "cynagora.h" +#include "cyn-protocol.h" +#include "expire.h" + +#define DEFAULT_CACHE_SIZE 5000 + +#define _CACHE_ 'c' +#define _ECHO_ 'e' +#define _HELP_ 'h' +#define _SOCKET_ 's' +#define _VERSION_ 'v' + +static +const char +shortopts[] = "c:ehs:v"; + +static +const struct option +longopts[] = { + { "cache", 1, NULL, _CACHE_ }, + { "echo", 0, NULL, _ECHO_ }, + { "help", 0, NULL, _HELP_ }, + { "socket", 1, NULL, _SOCKET_ }, + { "version", 0, NULL, _VERSION_ }, + { NULL, 0, NULL, 0 } +}; + +static +const char +helptxt[] = + "\n" + "usage: cynagoracli [options]... [action [arguments]]\n" + "\n" + "otpions:\n" + " -s, --socket xxx set the base xxx for sockets\n" + " -e, --echo print the evaluated command\n" + " -c, --cache xxx set the cache size to xxx bytes\n" + " (default: %d)\n" + " -h, --help print this help and exit\n" + " -v, --version print the version and exit\n" + "\n" + "When action is given, cynagoracli performs the action and exits.\n" + "Otherwise cynagoracli continuously read its input to get the actions.\n" + "For a list of actions type 'cynagoracli help'.\n" + "\n" +; + +static +const char +versiontxt[] = + "cynagoracli version 1.99.99\n" +; + +static +const char +help__text[] = + "\n" + "Commands are: list, set, drop, check, test, cache, clear, quit, log, help\n" + "Type 'help command' to get help on the command\n" + "Type 'help expiration' to get help on expirations\n" + "\n" +; + +static +const char +help_list_text[] = + "\n" + "Command: list [client [session [user [permission]]]]\n" + "\n" + "List the rules matching the optionally given 'client', 'session',\n" + "'user', 'permission'.\n" + "\n" + "This command requires to be connected to the administrator socket.\n" + "\n" + "The value given can be '#' (sharp) to match any value. When no value\n" + "is given, it is implied as being '#'.\n" + "\n" + "Examples:\n" + "\n" + " list list all rules\n" + " list # # 1001 list the rules of the user 1001\n" + "\n" +; + +static +const char +help_set_text[] = + "\n" + "Command: set client session user permission value expiration\n" + "\n" + "Set the rule associating the given 'client', 'session', 'user'\n" + "permission' with the 'value' for a time given by 'expiration'.\n" + "\n" + "Type 'help expiration' to get help on expirations\n" + "\n" + "This command requires to be connected to the administrator socket.\n" + "\n" + "Examples:\n" + "\n" + " set * * 0 * yes * set forever the value yes for user 0 and any\n" + " permission, client or session.\n" + "\n" + " set wrt * * X no 1d set for one day the value no for client xrt and\n" + " permission X of any user and session.\n" + "\n" +; + +static +const char +help_drop_text[] = + "\n" + "Command: drop [client [session [user [permission]]]]\n" + "\n" + "Removes the rules matching the optionally given 'client', 'session',\n" + "'user', 'permission'.\n" + "\n" + "This command requires to be connected to the administrator socket.\n" + "\n" + "The value given can be '#' (sharp) to match any value. When no value\n" + "is given, it is implied as being '#'.\n" + "\n" + "Examples:\n" + "\n" + " drop drop all rules\n" + " drop # # 1001 drop the rules of the user 1001\n" + "\n" +; + +static +const char +help_check_text[] = + "\n" + "Command: check client session user permission\n" + "\n" + "Check authorization for the given 'client', 'session', 'user', 'permission'.\n" + "\n" + "Examples:\n" + "\n" + " check wrt W3llcomE 1001 audio check that client 'wrt' of session\n" + " 'W3llcomE' for user '1001' has the\n" + " 'audio' permission\n" + "\n" +; + +static +const char +help_acheck_text[] = + "\n" + "Command: acheck client session user permission\n" + "\n" + "Check asynchronously authorization for the given 'client', 'session', 'user', 'permission'.\n" + "Same as check but don't wait the answer.\n" + "\n" +; + +static +const char +help_test_text[] = + "\n" + "Command: test client session user permission\n" + "\n" + "Test authorization for the given 'client', 'session', 'user', 'permission'.\n" + "Same as command 'check' except that it doesn't use query agent if it were\n" + "needed to avoid asynchronous timely unlimited queries.\n" + "\n" + "Examples:\n" + "\n" + " test wrt W3llcomE 1001 audio check that client 'wrt' of session\n" + " 'W3llcomE' for user '1001' has the\n" + " 'audio' permission\n" + "\n" +; + +static +const char +help_atest_text[] = + "\n" + "Command: atest client session user permission\n" + "\n" + "Test asynchronously authorization for the given 'client', 'session', 'user', 'permission'.\n" + "Same as test but don't wait the answer.\n" + "\n" +; + +static +const char +help_log_text[] = + "\n" + "Command: log [on|off]\n" + "\n" + "With the 'on' or 'off' arguments, set the logging state to what required.\n" + "In all cases, prints the logging state.\n" + "\n" + "Examples:\n" + "\n" + " log on activates the logging\n" + "\n" +; + +static +const char +help_cache_text[] = + "\n" + "Command: cache client session user permission\n" + "\n" + "Test cache for authorization for the given 'client', 'session', 'user', 'permission'.\n" + "\n" + "Examples:\n" + "\n" + " cache wrt W3llcomE 1001 audio check that client 'wrt' of session\n" + " 'W3llcomE' for user '1001' has the\n" + " 'audio' permission\n" + "\n" +; + +static +const char +help_clear_text[] = + "\n" + "Command: clear\n" + "\n" + "Clear the current cache.\n" + "\n" +; + +static +const char +help_quit_text[] = + "\n" + "Command: quit\n" + "\n" + "Quit the program\n" + "\n" +; + +static +const char +help_help_text[] = + "\n" + "Command: help [command | topic]\n" + "\n" + "Gives help on the command or on the topic.\n" + "\n" + "Available commands: list, set, drop, check, test, cache, clear, quit, help\n" + "Available topics: expiration\n" + "\n" +; + +static +const char +help_expiration_text[] = + "\n" + "Expirations limited in the time are expressed using the scheme NyNdNhNmNs\n" + "where N are numeric values and ydhms are unit specifications.\n" + "Almost all part of the scheme are optional. The default unit is second.\n" + "\n" + "Unlimited expirations can be expressed using: 0, *, always or forever.\n" + "\n" + "Examples:\n" + "\n" + " 6y5d 6 years and 5 days\n" + " 1d6h 1 day and 6 hours\n" + " 56 56 seconds\n" + " forever unlimited, no expiration\n" + "\n" +; + +static cynagora_t *cynagora; +static char buffer[4000]; +static size_t bufill; +static char *str[40]; +static int nstr; +static int pending; +static int echo; + +cynagora_key_t key; +cynagora_value_t value; + +int plink(int ac, char **av, int *used, int maxi) +{ + int r = 0; + + if (maxi < ac) + ac = maxi; + while (r < ac && strcmp(av[r], ";")) + r++; + + *used = r + (r < ac); + return r; +} + +int get_csupve(int ac, char **av, int *used, const char *def) +{ + int n = plink(ac, av, used, 7); + + key.client = n > 1 ? av[1] : def; + key.session = n > 2 ? av[2] : def; + key.user = n > 3 ? av[3] : def; + key.permission = n > 4 ? av[4] : def; + value.value = n > 5 ? av[5] : "no"; + value.expire = n > 6 ? txt2exp(av[6]) : 0; + + return key.client && key.session && key.user && key.permission && value.value && value.expire >= 0 ? 0 : -EINVAL; +} + +int get_csup(int ac, char **av, int *used, const char *def) +{ + int n = plink(ac, av, used, 5); + + key.client = n > 1 ? av[1] : def; + key.session = n > 2 ? av[2] : def; + key.user = n > 3 ? av[3] : def; + key.permission = n > 4 ? av[4] : def; + + return key.client && key.session && key.user && key.permission ? 0 : -EINVAL; +} + + +struct listresult { + int count; + size_t lengths[6]; + struct listitem { + struct listitem *next; + const char *items[6]; + } *head; +}; + +struct listitem *listresult_sort(int count, struct listitem *head) +{ + int n1, n2, i, r, c1, c2; + struct listitem *i1, *i2, **pp; + + if (count == 1) + head->next = 0; + else { + i2 = head; + n2 = count >> 1; + n1 = 0; + while (n1 < n2) { + n1++; + i2 = i2->next; + } + n2 = count - n1; + i1 = listresult_sort(n1, head); + i2 = listresult_sort(n2, i2); + pp = &head; + while(n1 || n2) { + c1 = !n2; + c2 = !n1; + for (i = 0 ; !c1 && !c2 && i < 4 ; i++) { + r = strcmp(i1->items[i], i2->items[i]); + c1 = r < 0; + c2 = r > 0; + } + if (c1) { + *pp = i1; + i1 = i1->next; + n1--; + } else { + *pp = i2; + i2 = i2->next; + n2--; + } + pp = &(*pp)->next; + } + *pp = 0; + } + return head; +} + +void listcb(void *closure, const cynagora_key_t *k, const cynagora_value_t *v) +{ + struct listresult *lr = closure; + char buffer[100], *p; + const char *items[6]; + struct listitem *it; + size_t s, as; + int i; + + exp2txt(v->expire, buffer, sizeof buffer); + items[0] = k->client; + items[1] = k->session; + items[2] = k->user; + items[3] = k->permission; + items[4] = v->value; + items[5] = buffer; + + as = 0; + for (i = 0 ; i < 6 ; i++) { + s = strlen(items[i]); + as += s; + if (s > lr->lengths[i]) + lr->lengths[i] = s; + } + it = malloc(sizeof *it + as + 6); + if (!it) + fprintf(stdout, "%s\t%s\t%s\t%s\t%s\t%s\n", + k->client, k->session, k->user, k->permission, + v->value, buffer); + else { + p = (char*)(it + 1); + for (i = 0 ; i < 6 ; i++) { + it->items[i] = p; + p = 1 + stpcpy(p, items[i]); + } + it->next = lr->head; + lr->head = it; + } + lr->count++; +} + +int do_list(int ac, char **av) +{ + int uc, rc; + struct listresult lr; + struct listitem *it; + + rc = get_csup(ac, av, &uc, "#"); + if (rc == 0) { + memset(&lr, 0, sizeof lr); + rc = cynagora_get(cynagora, &key, listcb, &lr); + if (rc < 0) + fprintf(stderr, "error %s\n", strerror(-rc)); + else { + if (lr.count) { + it = lr.head = listresult_sort(lr.count, lr.head); + while(it) { + fprintf(stdout, "%-*s %-*s %-*s %-*s %-*s %-*s\n", + (int)lr.lengths[0], it->items[0], + (int)lr.lengths[1], it->items[1], + (int)lr.lengths[2], it->items[2], + (int)lr.lengths[3], it->items[3], + (int)lr.lengths[4], it->items[4], + (int)lr.lengths[5], it->items[5]); + it = it->next; + } + } + fprintf(stdout, "%d entries found\n", lr.count); + } + /* free list */ + while(lr.head) { + it = lr.head; + lr.head = it->next; + free(it); + } + } + return uc; +} + +int do_set(int ac, char **av) +{ + int uc, rc; + + rc = get_csupve(ac, av, &uc, "*"); + if (rc == 0) + rc = cynagora_enter(cynagora); + if (rc == 0) { + rc = cynagora_set(cynagora, &key, &value); + cynagora_leave(cynagora, !rc); + } + if (rc < 0) + fprintf(stderr, "error %s\n", strerror(-rc)); + return uc; +} + +int do_drop(int ac, char **av) +{ + int uc, rc; + + rc = get_csup(ac, av, &uc, "#"); + if (rc == 0) + rc = cynagora_enter(cynagora); + if (rc == 0) { + rc = cynagora_drop(cynagora, &key); + cynagora_leave(cynagora, !rc); + } + if (rc < 0) + fprintf(stderr, "error %s\n", strerror(-rc)); + return uc; +} + +int do_check(int ac, char **av, int (*f)(cynagora_t*,const cynagora_key_t*)) +{ + int uc, rc; + + rc = get_csup(ac, av, &uc, NULL); + if (rc == 0) { + rc = f(cynagora, &key); + if (rc > 0) + fprintf(stdout, "allowed\n"); + else if (rc == 0) + fprintf(stdout, "denied\n"); + else if (rc == -ENOENT && f == cynagora_cache_check) + fprintf(stdout, "not in cache!\n"); + else if (rc == -EEXIST) + fprintf(stderr, "denied but an entry exist\n"); + else + fprintf(stderr, "error %s\n", strerror(-rc)); + } + return uc; +} + +void acheck_cb(void *closure, int status) +{ + if (status > 0) + fprintf(stdout, "allowed\n"); + else if (status == 0) + fprintf(stdout, "denied\n"); + else + fprintf(stderr, "error %s\n", strerror(-status)); + pending--; +} + +int do_acheck(int ac, char **av, bool simple) +{ + int uc, rc; + + rc = get_csup(ac, av, &uc, NULL); + if (rc == 0) { + pending++; + rc = cynagora_async_check(cynagora, &key, simple, acheck_cb, NULL); + if (rc < 0) { + fprintf(stderr, "error %s\n", strerror(-rc)); + pending--; + } + } + return uc; +} + +int do_log(int ac, char **av) +{ + int uc, rc; + int on = 0, off = 0; + int n = plink(ac, av, &uc, 2); + + if (n > 1) { + on = !strcmp(av[1], "on"); + off = !strcmp(av[1], "off"); + if (!on && !off) { + fprintf(stderr, "bad argument '%s'\n", av[1]); + return uc; + } + } + rc = cynagora_log(cynagora, on, off); + if (rc < 0) + fprintf(stderr, "error %s\n", strerror(-rc)); + else + fprintf(stdout, "logging %s\n", rc ? "on" : "off"); + return uc; +} + +int do_help(int ac, char **av) +{ + if (ac > 1 && !strcmp(av[1], "list")) + fprintf(stdout, "%s", help_list_text); + else if (ac > 1 && !strcmp(av[1], "set")) + fprintf(stdout, "%s", help_set_text); + else if (ac > 1 && !strcmp(av[1], "drop")) + fprintf(stdout, "%s", help_drop_text); + else if (ac > 1 && !strcmp(av[1], "check")) + fprintf(stdout, "%s", help_check_text); + else if (ac > 1 && !strcmp(av[1], "acheck")) + fprintf(stdout, "%s", help_acheck_text); + else if (ac > 1 && !strcmp(av[1], "test")) + fprintf(stdout, "%s", help_test_text); + else if (ac > 1 && !strcmp(av[1], "atest")) + fprintf(stdout, "%s", help_atest_text); + else if (ac > 1 && !strcmp(av[1], "cache")) + fprintf(stdout, "%s", help_cache_text); + else if (ac > 1 && !strcmp(av[1], "clear")) + fprintf(stdout, "%s", help_clear_text); + else if (ac > 1 && !strcmp(av[1], "log")) + fprintf(stdout, "%s", help_log_text); + else if (ac > 1 && !strcmp(av[1], "quit")) + fprintf(stdout, "%s", help_quit_text); + else if (ac > 1 && !strcmp(av[1], "help")) + fprintf(stdout, "%s", help_help_text); + else if (ac > 1 && !strcmp(av[1], "expiration")) + fprintf(stdout, "%s", help_expiration_text); + else { + fprintf(stdout, "%s", help__text); + return 1; + } + return 2; +} + +int do_any(int ac, char **av) +{ + if (!ac) + return 0; + + if (!strcmp(av[0], "list")) + return do_list(ac, av); + + if (!strcmp(av[0], "set")) + return do_set(ac, av); + + if (!strcmp(av[0], "drop")) + return do_drop(ac, av); + + if (!strcmp(av[0], "check")) + return do_check(ac, av, cynagora_check); + + if (!strcmp(av[0], "acheck")) + return do_acheck(ac, av, 0); + + if (!strcmp(av[0], "test")) + return do_check(ac, av, cynagora_test); + + if (!strcmp(av[0], "atest")) + return do_acheck(ac, av, 1); + + if (!strcmp(av[0], "cache")) + return do_check(ac, av, cynagora_cache_check); + + if (!strcmp(av[0], "log")) + return do_log(ac, av); + + if (!strcmp(av[0], "clear")) { + cynagora_cache_clear(cynagora); + return 1; + } + + if (!strcmp(av[0], "quit")) + return 0; + + if (!strcmp(av[0], "help") || !strcmp(av[0], "?")) + return do_help(ac, av); + + fprintf(stderr, "unknown command %s\n", av[0]); + return 0; +} + +void do_all(int ac, char **av) +{ + int rc; + + if (echo) { + for (rc = 0 ; rc < ac ; rc++) + fprintf(stdout, "%s%s", rc ? " " : "", av[rc]); + fprintf(stdout, "\n"); + } + while(ac) { + rc = do_any(ac, av); + if (rc <= 0) + exit(1); + ac -= rc; + av += rc; + } +} + +int async_ctl(void *closure, int op, int fd, uint32_t events) +{ + int *pfd = closure; + + switch(op) { + case EPOLL_CTL_ADD: + case EPOLL_CTL_MOD: + *pfd = fd; + break; + case EPOLL_CTL_DEL: + *pfd = -1; + break; + } + return 0; +} + +int main(int ac, char **av) +{ + int opt; + int rc; + int help = 0; + int version = 0; + int error = 0; + uint32_t cachesize = DEFAULT_CACHE_SIZE; + unsigned long ul; + char *socket = NULL; + char *cache = NULL; + struct pollfd fds[2]; + char *p; + + setlinebuf(stdout); + + /* scan arguments */ + for (;;) { + opt = getopt_long(ac, av, shortopts, longopts, NULL); + if (opt == -1) + break; + + switch(opt) { + case _CACHE_: + cache = optarg; + ul = strtoul(optarg, &cache, 0); + if (*cache || ul > UINT32_MAX) + error = 1; + else + cachesize = (uint32_t)ul; + break; + case _ECHO_: + echo = 1; + break; + case _HELP_: + help = 1; + break; + case _SOCKET_: + socket = optarg; + break; + case _VERSION_: + version = 1; + break; + default: + error = 1; + break; + } + } + + /* handles help, version, error */ + if (help) { + fprintf(stdout, helptxt, DEFAULT_CACHE_SIZE); + return 0; + } + if (version) { + fprintf(stdout, versiontxt); + return 0; + } + if (error) + return 1; + + /* initialize server */ + signal(SIGPIPE, SIG_IGN); /* avoid SIGPIPE! */ + rc = cynagora_create(&cynagora, cynagora_Admin, cachesize, socket); + if (rc < 0) { + fprintf(stderr, "initialization failed: %s\n", strerror(-rc)); + return 1; + } + + fds[1].fd = -1; + rc = cynagora_async_setup(cynagora, async_ctl, &fds[1].fd); + if (rc < 0) { + fprintf(stderr, "asynchronous setup failed: %s\n", strerror(-rc)); + return 1; + } + + if (optind < ac) { + do_all(ac - optind, av + optind); + return 0; + } + + fcntl(0, F_SETFL, O_NONBLOCK); + bufill = 0; + fds[0].fd = 0; + fds[0].events = fds[1].events = POLLIN; + for(;;) { + rc = poll(fds, 2, -1); + if (fds[0].revents & POLLIN) { + rc = (int)read(0, &buffer[bufill], sizeof buffer - bufill); + if (rc == 0) + break; + if (rc > 0) { + bufill += (size_t)rc; + while((p = memchr(buffer, '\n', bufill))) { + /* process one line */ + *p++ = 0; + str[nstr = 0] = strtok(buffer, " \t"); + while(str[nstr]) + str[++nstr] = strtok(NULL, " \t"); + do_all(nstr, str); + bufill -= (size_t)(p - buffer); + if (!bufill) + break; + memmove(buffer, p, bufill); + } + } + } + if (fds[0].revents & POLLHUP) { + if (!pending) + break; + fds[0].fd = -1; + } + if (fds[1].revents & POLLIN) { + rc = cynagora_async_process(cynagora); + if (rc < 0) + fprintf(stderr, "asynchronous processing failed: %s\n", strerror(-rc)); + if (fds[0].fd < 0 && !pending) + break; + } + if (fds[1].revents & POLLHUP) { + if (fds[0].fd < 0) + break; + fds[1].fd = -1; + } + } + return 0; +} |