/* * Copyright (C) 2018 "IoT.bzh" * Author José Bollo * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cynagora.h" #include "expire.h" #define _HELP_ 'h' #define _SOCKET_ 's' #define _VERSION_ 'v' #define _PIPED_ 'p' static const char shortopts[] = "hps:v"; static const struct option longopts[] = { { "help", 0, NULL, _HELP_ }, { "piped", 0, NULL, _PIPED_ }, { "socket", 1, NULL, _SOCKET_ }, { "version", 0, NULL, _VERSION_ }, { NULL, 0, NULL, 0 } }; static const char helptxt[] = "\n" "usage: cynagora-agent [options]... name [program]\n" "\n" "otpions:\n" " -s, --socket xxx set the base xxx for sockets\n" " -p, --piped replace stdin/out by out/in of program" " -h, --help print this help and exit\n" " -v, --version print the version and exit\n" "\n" "When program is given, cynagora-agent performs invoke it with\n" "arguments VALUE CLIENT SESSION USER PERMISSION and expect it\n" "to echo the result with optional expiration\n" "Otherwise cynagora-agent echo its requests on one line:\n" "ID VALUE CLIENT SESSION USER PERMISSION and expect to read the\n" "replies: ID RESULT [EXPIRE]\n" "\n" ; static const char versiontxt[] = "cynagora-agent version 1.99.99\n" ; typedef struct { int filled; char buffer[4000]; } buf_t; typedef struct { int count; char *args[20]; } args_t; typedef struct { int id; cynagora_query_t *query; } query_t; typedef struct { pid_t pid; int id; int io[2]; } proc_t; static char *name; static char **prog; static int piped; static cynagora_t *cynagora; static int nexid; static buf_t buffer; static query_t queries[200]; static proc_t procs[200]; int qidx(int id) { int r = sizeof queries / sizeof *queries; while (r-- && queries[r].id != id); return r; } int pidx(pid_t pid) { int r = sizeof procs / sizeof *procs; while (r-- && procs[r].pid != pid); return r; } int buf_parse(buf_t *buf, args_t *args) { char *p, *x; size_t s; int r; static const char seps[] = " \t"; p = memchr(buf->buffer, '\n', (size_t)buf->filled); if (!p) r = 0; else { /* process one line: split args */ *p++ = 0; r = (int)(p - buf->buffer); args->count = 0; x = buf->buffer; s = strspn(x, seps); x = &x[s]; while (*x) { if (args->count < (int)(sizeof args->args / sizeof *args->args)) args->args[args->count++] = x; s = strcspn(x, seps); x = &x[s]; if (!*x) break; *x++ = 0; s = strspn(x, seps); x = &x[s]; } } return r; } void buf_unprefix(buf_t *buf, int count) { int remain; if (count > 0) { remain = buf->filled - count; if (remain >= 0) { if (remain) memmove(buf->buffer, &buf->buffer[count], (size_t)remain); buf->filled = remain; } } } void read_and_dispatch(int fd, buf_t *buf, void (*fun)(int,char**)) { int n; ssize_t sz; args_t args; sz = read(fd, &buf->buffer[buf->filled], sizeof buf->buffer - (size_t)buf->filled); if (sz > 0) { buf->filled += (int)sz; n = buf_parse(buf, &args); while (n) { if (args.count) fun(args.count, args.args); buf_unprefix(buf, n); n = buf_parse(buf, &args); } } } pid_t split(int io[2]) { int rc; int parent2child[2], child2parent[2]; pid_t pid = -1; /* create pipes */ rc = pipe(parent2child); if (rc == 0) { rc = pipe(child2parent); if (rc == 0) { pid = fork(); if (pid >= 0) { if (pid == 0) { /* in child */ close(0); dup(parent2child[0]); close(1); dup(child2parent[1]); } else { /* in parent */ io[0] = dup(child2parent[0]); io[1] = dup(parent2child[1]); } } close(child2parent[0]); close(child2parent[1]); } close(parent2child[0]); close(parent2child[1]); } return pid; } void deadchild(int sig, siginfo_t *info, void *item) { int i; pid_t pid; buf_t buf; args_t args; ssize_t sz; pid = info->si_pid; i = pidx(pid); if (i >= 0) { args.count = 0; sz = read(procs[i].io[0], buf.buffer, sizeof buf.buffer); if (sz > 0) { buf.filled = (int)sz; buf_parse(&buf, &args); } if (!args.count) { args.args[0] = "no"; args.args[1] = "-"; args.count = 2; } if (args.count == 1) printf("%d %s\n", procs[i].id, args.args[0]); else printf("%d %s %s\n", procs[i].id, args.args[0], args.args[1]); fflush(stdout); close(procs[i].io[0]); close(procs[i].io[1]); procs[i].pid = 0; } waitpid(pid, NULL, 0); } void onquery(int ac, char **av) { int i; pid_t pid; i = pidx(0); if (ac == 6 && i >= 0) { procs[i].pid = pid = split(procs[i].io); if (pid >= 0) { procs[i].id = atoi(av[0]); if (!pid) { setenv("CYAG_VALUE", av[1], 1); setenv("CYAG_CLIENT", av[2], 1); setenv("CYAG_SESSION", av[3], 1); setenv("CYAG_USER", av[4], 1); setenv("CYAG_PERMISSION", av[5], 1); execvp(prog[0], prog); fprintf(stderr, "error: can't exec %s: %s\n", prog[0], strerror(errno)); exit(1); } return; close(procs[i].io[0]); close(procs[i].io[1]); } procs[i].pid = 0; } fprintf(stdout, "%s no -\n", av[0]); } int runloop() { struct pollfd pfd; struct sigaction sigact; /* set the signal handler */ sigact.sa_sigaction = deadchild; sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_NOCLDSTOP | SA_SIGINFO; sigaction(SIGCHLD, &sigact, NULL); pfd.fd = 0; pfd.events = POLLIN; for(;;) { pfd.revents = 0; poll(&pfd, 1, -1); if (pfd.revents & POLLIN) read_and_dispatch(0, &buffer, onquery); if (pfd.revents & POLLHUP) break; } return 0; } int setup_child() { int rc, io[2]; pid_t pid; /* fork the child */ pid = split(io); if (pid < 0) return -1; if (pid) { close(0); dup(io[0]); close(io[0]); close(1); dup(io[1]); close(io[1]); return 0; } /* run piped if required */ if (piped) { rc = execvp(prog[0], prog); fprintf(stderr, "error: can't exec %s: %s\n", prog[0], strerror(errno)); } else { rc = runloop(); if (rc) fprintf(stderr, "error: can't loop: %s\n", strerror(errno)); } exit(!!rc); } int agentcb(void *closure, cynagora_query_t *query) { int i, id, rc; /* get an id */ do { id = ++nexid; if (id < 0) id = nexid = 1; } while (qidx(id) >= 0); /* get an index */ i = qidx(0); if (i < 0) return -ECANCELED; queries[i].id = id; queries[i].query = query; /* compose the value */ rc = fprintf(stdout, "%d %s %s %s %s %s\n", id, query->value, query->key.client, query->key.session, query->key.user, query->key.permission); if (rc < 0) { queries[i].query = NULL; queries[i].id = 0; return -ECANCELED; } return 0; } void onreply(int ac, char **av) { int i, id; cynagora_value_t value; id = atoi(av[0]); i = qidx(id); if (i >= 0) { value.value = "no"; value.expire = 0; if (ac > 1) value.value = av[1]; if (ac > 2) txt2exp(av[2], &value.expire, true); cynagora_agent_reply(queries[i].query, &value); queries[i].query = NULL; queries[i].id = 0; } } 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; char *socket = NULL; struct pollfd fds[2]; /* scan arguments */ for (;;) { opt = getopt_long(ac, av, shortopts, longopts, NULL); if (opt == -1) break; switch(opt) { case _HELP_: help = 1; break; case _PIPED_: piped = 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); return 0; } if (version) { fprintf(stdout, versiontxt); return 0; } /* check agent name */ if (optind == ac) { fprintf(stderr, "error: name missing\n"); error = 1; } else { name = av[optind++]; if (!cynagora_agent_is_valid_name(name)) { fprintf(stderr, "error: invalid agent name %s\n", name); error = 1; } else if (optind == ac) { prog = NULL; } else { prog = &av[optind++]; } } if (error) return 1; /* initialize server */ signal(SIGPIPE, SIG_IGN); /* avoid SIGPIPE! */ rc = cynagora_create(&cynagora, cynagora_Agent, 0, socket); if (rc < 0) { fprintf(stderr, "error: 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, "error: asynchronous setup failed, %s\n", strerror(-rc)); return 1; } /* create the agent */ rc = cynagora_agent_create(cynagora, name, agentcb, NULL); if (rc < 0) { fprintf(stderr, "error: creation of agent failed, %s\n", strerror(-rc)); return 1; } /* setup piped */ if (prog) { rc = setup_child(); if (rc < 0) { fprintf(stderr, "error: can't setup child, %s\n", strerror(errno)); return 1; } } /* setup output */ setlinebuf(stdout); fcntl(0, F_SETFL, O_NONBLOCK); fds[0].fd = 0; fds[0].events = fds[1].events = POLLIN; for(;;) { rc = poll(fds, 2, -1); if (fds[0].revents & POLLIN) read_and_dispatch(0, &buffer, onreply); 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].revents & POLLHUP) break; if (fds[1].revents & POLLHUP) break; } return 0; }