aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJose Bollo <jose.bollo@iot.bzh>2018-09-10 12:00:18 +0200
committerJose Bollo <jose.bollo@iot.bzh>2018-09-10 12:00:18 +0200
commit11654afcb5753a54a033db12e1ed4a19b3f7c86e (patch)
tree0d493c80584392eec2c5dc0f1c1c68c9057cf043 /src
Initial commit
Signed-off-by: Jose Bollo <jose.bollo@iot.bzh>
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore5
-rw-r--r--src/CMakeLists.txt95
-rw-r--r--src/Makefile26
-rw-r--r--src/cache.c163
-rw-r--r--src/cache.h48
-rw-r--r--src/cyn.c267
-rw-r--r--src/cyn.h113
-rw-r--r--src/db.c661
-rw-r--r--src/db.h102
-rw-r--r--src/export.map9
-rw-r--r--src/fbuf.c279
-rw-r--r--src/fbuf.h111
-rw-r--r--src/lib-compat.c651
-rw-r--r--src/prot.c440
-rw-r--r--src/prot.h85
-rw-r--r--src/queue.c192
-rw-r--r--src/queue.h31
-rw-r--r--src/rcyn-client.c722
-rw-r--r--src/rcyn-client.h149
-rw-r--r--src/rcyn-protocol.c33
-rw-r--r--src/rcyn-protocol.h28
-rw-r--r--src/rcyn-protocol.txt79
-rw-r--r--src/rcyn-server.c669
-rw-r--r--src/socket.c320
-rw-r--r--src/socket.h21
-rw-r--r--src/test-lib-compat.c256
26 files changed, 5555 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..d551f05
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,5 @@
+cynarad
+test-cynara
+cynara.names
+cynara.rules
+.*.sw*
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..fa3724c
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,95 @@
+###########################################################################
+# 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.
+###########################################################################
+
+set(SERVER_SOURCES
+ cyn.c
+ db.c
+ fbuf.c
+ prot.c
+ queue.c
+ rcyn-protocol.c
+ rcyn-server.c
+ socket.c
+)
+
+set(LIB_SOURCES
+ cache.c
+ lib-compat.c
+ prot.c
+ rcyn-client.c
+ rcyn-protocol.c
+ socket.c
+)
+
+###########################################
+# build and install cynarad
+###########################################
+add_executable(cynarad ${SERVER_SOURCES})
+install(TARGETS cynarad
+ RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR})
+
+###########################################
+# build and install libcynara
+###########################################
+ADD_LIBRARY(cynara SHARED ${LIB_SOURCES})
+SET_TARGET_PROPERTIES(cynara PROPERTIES
+ VERSION ${CYNARA_VERSION}
+ SOVERSION ${CYNARA_SOVERSION})
+TARGET_LINK_LIBRARIES(cynara
+ -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/export.map
+ -Wl,--as-needed
+ -Wl,--gc-sections
+)
+INSTALL(TARGETS cynara LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})
+INSTALL(FILES rcyn-client.h DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/cynara)
+
+###########################################
+# build and install test-cynara
+###########################################
+ADD_EXECUTABLE(test-cynara test-lib-compat.c)
+TARGET_LINK_LIBRARIES(test-cynara cynara)
+INSTALL(TARGETS test-cynara
+ RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR})
+
+###########################################
+# alterations for SYSTEMD and socket specs
+###########################################
+
+if(SYSTEMD)
+ target_compile_definitions(cynarad PRIVATE RCYN_DEFAULT_CHECK_SOCKET_SPEC="sd:check")
+ target_compile_definitions(cynarad PRIVATE RCYN_DEFAULT_ADMIN_SOCKET_SPEC="sd:admin")
+ target_compile_definitions(cynarad PRIVATE WITH_SYSTEMD_ACTIVATION)
+ target_link_libraries(cynarad ${libsystemd_LDFLAGS} ${libsystemd_LINK_LIBRARIES})
+ target_include_directories(cynarad PRIVATE ${libsystemd_INCLUDE_DIRS})
+ target_compile_options(cynarad PRIVATE ${libsystemd_CFLAGS})
+else()
+ if(CHECK_SOCKET_SPEC)
+ target_compile_definitions(cynarad PRIVATE RCYN_DEFAULT_CHECK_SOCKET_SPEC="${CHECK_SOCKET_SPEC}")
+ endif()
+ if(ADMIN_SOCKET_SPEC)
+ target_compile_definitions(cynarad PRIVATE RCYN_DEFAULT_ADMIN_SOCKET_SPEC="${ADMIN_SOCKET_SPEC}")
+ endif()
+endif()
+
+if(CHECK_SOCKET_SPEC)
+ target_compile_definitions(cynara PRIVATE RCYN_DEFAULT_CHECK_SOCKET_SPEC="${CHECK_SOCKET_SPEC}")
+endif()
+if(ADMIN_SOCKET_SPEC)
+ target_compile_definitions(cynara PRIVATE RCYN_DEFAULT_ADMIN_SOCKET_SPEC="${ADMIN_SOCKET_SPEC}")
+endif()
+
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..f5969c3
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,26 @@
+.PHONY: all clean
+
+srcssrv = db.c fbuf.c queue.c cyn.c prot.c rcyn-protocol.c socket.c
+
+srcscli = prot.c rcyn-client.c rcyn-protocol.c lib-compat.c cache.c socket.c
+
+incssrv = db.h fbuf.h cyn.h prot.h rcyn-protocol.h socket.h
+
+incscli = prot.h rcyn-client.h rcyn-protocol.h cache.h socket.h
+
+defs = -DRCYN_DEFAULT_CHECK_SOCKET_SPEC=\"tcp:localhost:5555\" \
+ -DRCYN_DEFAULT_ADMIN_SOCKET_SPEC=\"tcp:localhost:4444\"
+
+bins = cynarad test-cynara
+
+all: $(bins)
+
+clean:
+ rm cynara.names cynara.rules $(bins) 2>/dev/null || true
+
+cynarad: rcyn-server.c $(srcssrv) $(incssrv)
+ gcc -o cynarad -g -Wall rcyn-server.c $(srcssrv) $(defs)
+
+test-cynara: test-lib-compat.c $(srcscli) $(incscli)
+ gcc -o test-cynara -I../include -g -Wall test-lib-compat.c $(srcscli) $(defs)
+
diff --git a/src/cache.c b/src/cache.c
new file mode 100644
index 0000000..c2db00d
--- /dev/null
+++ b/src/cache.c
@@ -0,0 +1,163 @@
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+
+#include "cache.h"
+
+struct cache
+{
+ uint32_t begin;
+ uint32_t used;
+ uint32_t count;
+ char content[1];
+};
+
+static
+uint32_t
+lenat(
+ cache_t *cache,
+ uint32_t pos
+) {
+ uint32_t p, n;
+ char c;
+
+ p = pos + 1;
+ if (p >= cache->count)
+ p -= cache->count;
+ n = 4;
+ while (n--) {
+ do {
+ c = cache->content[p++];
+ if (p >= cache->count)
+ p -= cache->count;
+ } while(c);
+ }
+ return (p > pos ? p : (p + cache->count)) - pos;
+}
+
+static
+void
+drop_one(
+ cache_t *cache
+) {
+ uint32_t l = lenat(cache, cache->begin);
+ cache->used -= l;
+ cache->begin += l;
+ if (cache->begin > cache->count)
+ cache->begin -= cache->count;
+}
+
+static
+void
+addc(
+ cache_t *cache,
+ char c
+) {
+ uint32_t pos;
+ if (cache->used == cache->count)
+ drop_one(cache);
+ pos = cache->begin + cache->used++;
+ if (pos > cache->count)
+ pos -= cache->count;
+ cache->content[pos < cache->count ? pos : pos - cache->count] = c;
+}
+
+static
+void
+adds(
+ cache_t *cache,
+ const char *s
+) {
+ do { addc(cache, *s); } while(*s++);
+}
+
+int
+cache_put(
+ cache_t *cache,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ int value
+) {
+ if (cache == NULL
+ || strlen(client) + strlen(session)
+ + strlen(user) + strlen(permission)
+ + 5 > cache->count)
+ return -EINVAL;
+
+ addc(cache, (char)value);
+ adds(cache, client);
+ adds(cache, session);
+ adds(cache, user);
+ adds(cache, permission);
+ return 0;
+}
+
+int
+cache_search(
+ cache_t *cache,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+) {
+ return -ENOENT;
+}
+
+void
+cache_clear(
+ cache_t *cache
+) {
+ if (cache) {
+ cache->used = 0;
+ cache->begin = 0;
+ }
+}
+
+int
+cache_resize(
+ cache_t **cache,
+ uint32_t newsize
+) {
+ cache_t *c = *cache, *nc;
+
+ while (c && c->used > newsize)
+ drop_one(c);
+
+ nc = malloc(newsize - 1 + sizeof *c);
+ if (nc == NULL)
+ return -ENOMEM;
+
+ nc->begin = 0;
+ nc->count = newsize;
+ if (!c || c->used == 0)
+ nc->used = 0;
+ else {
+ if (c->begin + c->used <= c->count)
+ memcpy(&nc->content[0], &c->content[c->begin], c->used);
+ else {
+ memcpy(&nc->content[0], &c->content[c->begin], c->count - c->begin);
+ memcpy(&nc->content[c->count - c->begin], &c->content[0], c->used + c->begin - c->count);
+ }
+
+ nc->used = c->used;
+ }
+ *cache = nc;
+ free(c);
+ return 0;
+}
+
+int
+cache_create(
+ cache_t **cache,
+ uint32_t size
+) {
+ *cache = NULL;
+ return cache_resize(cache, size);
+}
+
+
+
diff --git a/src/cache.h b/src/cache.h
new file mode 100644
index 0000000..2f3a9a1
--- /dev/null
+++ b/src/cache.h
@@ -0,0 +1,48 @@
+#pragma once
+
+struct cache;
+typedef struct cache cache_t;
+
+extern
+int
+cache_search(
+ cache_t *cache,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+);
+
+extern
+int
+cache_put(
+ cache_t *cache,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ int value
+);
+
+extern
+void
+cache_clear(
+ cache_t *cache
+);
+
+extern
+int
+cache_resize(
+ cache_t **cache,
+ uint32_t newsize
+);
+
+extern
+int
+cache_create(
+ cache_t **cache,
+ uint32_t size
+);
+
+
+
diff --git a/src/cyn.c b/src/cyn.c
new file mode 100644
index 0000000..4eb6d9e
--- /dev/null
+++ b/src/cyn.c
@@ -0,0 +1,267 @@
+#define _GNU_SOURCE
+
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+
+
+#include "db.h"
+#include "queue.h"
+#include "cyn.h"
+
+struct callback
+{
+ struct callback *next;
+ void (*callback)(void *closure);
+ void *closure;
+};
+
+/** locking critical section */
+static const void *lock;
+static struct callback *awaiters;
+static struct callback *observers;
+
+static
+int
+delcb(
+ void (*callback)(void *closure),
+ void *closure,
+ struct callback **head
+) {
+ struct callback *c;
+
+ while((c = *head)) {
+ if (c->callback == callback && c->closure == closure) {
+ *head = c->next;
+ free(c);
+ return 1;
+ }
+ head = &c->next;
+ }
+ return 0;
+}
+
+static
+int
+addcb(
+ void (*callback)(void *closure),
+ void *closure,
+ struct callback **head
+) {
+ struct callback *c;
+
+ c = malloc(sizeof *c);
+ if (c == NULL)
+ return -(errno = ENOMEM);
+ c->callback = callback;
+ c->closure = closure;
+ c->next = *head;
+ *head = c;
+ return 0;
+}
+
+static
+int
+changed(
+) {
+ int rc = db_sync();
+ struct callback *c;
+
+ for (c = observers; c ; c = c->next)
+ c->callback(c->closure);
+ return rc;
+}
+
+int
+cyn_init(
+) {
+ /* TODO: paths? */
+ int rc = db_open("/var/lib/cynara/cynara.names", "/var/lib/cynara/cynara.rules");
+ if (rc == 0 && db_is_empty()) {
+ /* TODO: init? */
+ rc = db_set("System", "*", "*", "*", 1);
+ db_sync();
+ }
+ return rc;
+}
+
+/** enter critical recoverable section */
+int
+cyn_enter(
+ const void *magic
+) {
+ if (lock)
+ return -EBUSY;
+ lock = magic;
+ return 0;
+}
+
+int
+cyn_enter_async(
+ void (*enter_cb)(void *closure),
+ void *closure
+) {
+ if (lock)
+ return addcb(enter_cb, closure, &awaiters);
+
+ lock = closure;
+ enter_cb(closure);
+ return 0;
+}
+
+int
+cyn_enter_async_cancel(
+ void (*enter_cb)(void *closure),
+ void *closure
+) {
+ return delcb(enter_cb, closure, &awaiters);
+}
+
+int
+cyn_on_change_add(
+ void (*on_change_cb)(void *closure),
+ void *closure
+) {
+ return addcb(on_change_cb, closure, &observers);
+}
+
+
+int
+cyn_on_change_remove(
+ void (*on_change_cb)(void *closure),
+ void *closure
+) {
+ return delcb(on_change_cb, closure, &observers);
+}
+
+/** leave critical recoverable section */
+int
+cyn_leave(
+ const void *magic,
+ bool commit
+) {
+ int rc;
+ struct callback *e, **p;
+
+ if (!magic)
+ return -EINVAL;
+ if (!lock)
+ return -EALREADY;
+ if (lock != magic)
+ return -EPERM;
+
+ lock = &lock;
+ if (commit)
+ rc = queue_play() ?: changed();
+ else
+ rc = 0;
+ queue_clear();
+
+ e = awaiters;
+ if (!e)
+ lock = 0;
+ else {
+ p = &awaiters;
+ while(e->next) {
+ p = &e->next;
+ e = *p;
+ }
+ *p = NULL;
+ lock = e->closure;
+ e->callback(e->closure);
+ free(e);
+ }
+
+ return rc;
+}
+
+int
+cyn_set(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value
+) {
+ if (lock && lock != &lock)
+ return queue_set(client, session, user, permission, value);
+
+ return db_set(client, session, user, permission, value) ?: changed();
+}
+
+int
+cyn_drop(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+) {
+ if (lock && lock != &lock)
+ return queue_drop(client, session, user, permission);
+
+ return db_drop(client, session, user, permission) ?: changed();
+}
+
+int
+cyn_test(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t *value
+) {
+ int rc;
+
+ rc = db_test(client, session, user, permission, value);
+ if (rc <= 0)
+ *value = DEFAULT;
+ else
+ rc = 0;
+ return rc;
+}
+
+void
+cyn_list(
+ void *closure,
+ void (*callback)(
+ void *closure,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value),
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+) {
+ db_for_all(closure, callback, client, session, user, permission);
+}
+
+int
+cyn_check_async(
+ void (*check_cb)(void *closure, uint32_t value),
+ void *closure,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+) {
+ uint32_t value;
+
+ cyn_test(client, session, user, permission, &value);
+ if (value == ALLOW || value == DENY) {
+ check_cb(closure, value);
+ return 0;
+ }
+
+ /* TODO: try to resolve AGENT?? */
+
+ check_cb(closure, value);
+ return 0;
+}
+
diff --git a/src/cyn.h b/src/cyn.h
new file mode 100644
index 0000000..0a061c2
--- /dev/null
+++ b/src/cyn.h
@@ -0,0 +1,113 @@
+
+#pragma once
+
+#define DENY 0
+#define ALLOW 1
+#define ASK 2
+#define DEFAULT DENY
+
+extern
+int
+cyn_init(
+);
+
+/** enter critical recoverable section */
+extern
+int
+cyn_enter(
+ const void *magic
+);
+
+/** leave critical recoverable section */
+extern
+int
+cyn_leave(
+ const void *magic,
+ bool commit
+);
+
+extern
+int
+cyn_set(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value
+);
+
+extern
+int
+cyn_drop(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+);
+
+extern
+int
+cyn_test(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t *value
+);
+
+extern
+void
+cyn_list(
+ void *closure,
+ void (*callback)(
+ void *closure,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value),
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+);
+
+extern
+int
+cyn_check_async(
+ void (*check_cb)(void *closure, uint32_t value),
+ void *closure,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+);
+
+extern
+int
+cyn_enter_async(
+ void (*enter_cb)(void *closure),
+ void *closure
+);
+
+extern
+int
+cyn_enter_async_cancel(
+ void (*enter_cb)(void *closure),
+ void *closure
+);
+
+extern
+int
+cyn_on_change_add(
+ void (*on_change_cb)(void *closure),
+ void *closure
+);
+
+extern
+int
+cyn_on_change_remove(
+ void (*on_change_cb)(void *closure),
+ void *closure
+);
+
diff --git a/src/db.c b/src/db.c
new file mode 100644
index 0000000..c7efdca
--- /dev/null
+++ b/src/db.c
@@ -0,0 +1,661 @@
+
+#define _GNU_SOURCE
+
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdalign.h>
+#include <string.h>
+#include <errno.h>
+#include <syslog.h>
+
+#include "fbuf.h"
+#include "db.h"
+
+#define NOIDX 0
+
+#define ANYIDX 40
+#define ANYSTR "#"
+
+#define WIDEIDX 42
+#define WIDESTR "*"
+
+/**
+ * A rule is a set of 4 integers
+ */
+struct rule
+{
+ uint32_t client, user, permission, value;
+};
+typedef struct rule rule_t;
+
+/**
+ * Sessions
+ */
+struct session
+{
+ struct session *next, *prev;
+ rule_t *rules;
+ const char *name;
+ uint32_t count;
+};
+typedef struct session session_t;
+
+/*
+ * The cynara-agl database is made of 2 memory mapped files:
+ * - names: the zero terminated names
+ * - rules: the rules based on name indexes as 32bits indexes
+ * These files are normally in /var/lib/cynara
+ */
+
+/** the file for the names */
+static fbuf_t fnames;
+
+/** the file for the rules */
+static fbuf_t frules;
+
+/** identification of names version 1 (uuidgen --sha1 -n @url -N urn:AGL:cynara:db:names:1) */
+static const char uuid_names_v1[] = "e9481f9e-b2f4-5716-90cf-c286d98d1868\n--\n";
+
+/** identification of rules version 1 (uuidgen --sha1 -n @url -N urn:AGL:cynara:db:rules:1) */
+static const char uuid_rules_v1[] = "8f7a5b21-48b1-57af-96c9-d5d7192be370\n--\n";
+
+/** length of the identification */
+static const int uuidlen = 40;
+
+/** count of names */
+static uint32_t names_count;
+
+/** the name indexes sorted */
+static uint32_t *names_sorted;
+
+/** the sessions */
+static session_t sessions = {
+ .next = &sessions,
+ .prev = &sessions,
+ .name = WIDESTR
+};
+
+/** return the name of 'index' */
+static
+const char*
+name_at(
+ uint32_t index
+) {
+ return (const char*)(fnames.buffer + index);
+}
+
+/** compare names. used by qsort and bsearch */
+static
+int
+cmpnames(
+ const void *pa,
+ const void *pb
+) {
+ uint32_t a = *(const uint32_t*)pa;
+ uint32_t b = *(const uint32_t*)pb;
+ return strcmp(name_at(a), name_at(b));
+}
+
+/** search the index of 'name' and create it if 'needed' */
+int
+db_get_name_index(
+ uint32_t *index,
+ const char *name,
+ bool needed
+) {
+ uint32_t lo, up, m, i, *p;
+ int c;
+ const char *n;
+ size_t len;
+
+ /* special names */
+ if (!name || !name[0])
+ name = ANYSTR;
+
+ /* dichotomic search */
+ lo = 0;
+ up = names_count;
+ while(lo < up) {
+ m = (lo + up) >> 1;
+ i = names_sorted[m];
+ n = name_at(i);
+ c = strcmp(n, name);
+
+ if (c == 0) {
+ /* found */
+ *index = i;
+ return 0;
+ }
+
+ /* dichotomic iteration */
+ if (c < 0)
+ lo = m + 1;
+ else
+ up = m;
+ }
+
+ /* not found */
+ if (!needed) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ /* check length */
+ len = strnlen(name, MAX_NAME_LENGTH + 1);
+ if (len > MAX_NAME_LENGTH) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* add the name in the file */
+ i = fnames.used;
+ c = fbuf_append(&fnames, name, 1 + (uint32_t)len);
+ if (c < 0)
+ return c;
+
+ /* add the name in sorted array */
+ up = names_count;
+ if (!(up & 1023)) {
+ p = realloc(names_sorted, (up + 1024) * sizeof *names_sorted);
+ if (p == NULL) {
+ syslog(LOG_ERR, "out of memory");
+ return -1;
+ }
+ names_sorted = p;
+ }
+ memmove(&names_sorted[lo + 1], &names_sorted[lo], (up - lo) * sizeof *names_sorted);
+ names_count = up + 1;
+ *index = names_sorted[lo] = i;
+ return 0;
+}
+
+/** initialize names */
+static
+int
+init_names(
+) {
+ int rc;
+ uint32_t pos, len, *ns, *p, all, nc;
+
+ all = 0;
+ nc = 0;
+ ns = NULL;
+
+ /* iterate over names */
+ pos = uuidlen;
+ while (pos < fnames.saved) {
+ /* get name length */
+ len = (uint32_t)strlen(name_at(pos));
+ if (pos + len <= pos || pos + len > fnames.saved) {
+ free(ns);
+ goto bad_file;
+ }
+ /* store the position */
+ if (all <= nc) {
+ all += 1024;
+ p = realloc(ns, all * sizeof *ns);
+ if (p == NULL) {
+ free(ns);
+ syslog(LOG_ERR, "out of memory");
+ goto error;
+ }
+ ns = p;
+ }
+ ns[nc++] = pos;
+ /* next */
+ pos += len + 1;
+ }
+
+ /* sort and record */
+ qsort(ns, nc, sizeof *ns, cmpnames);
+ names_sorted = ns;
+ names_count = nc;
+
+ /* predefined symbols */
+ rc = db_get_name_index(&pos, ANYSTR, true);
+ if (rc < 0)
+ goto error;
+ if (pos != ANYIDX)
+ goto bad_file;
+ rc = db_get_name_index(&pos, WIDESTR, true);
+ if (rc < 0)
+ goto error;
+ if (pos != WIDEIDX)
+ goto bad_file;
+
+ return 0;
+bad_file:
+ syslog(LOG_ERR, "bad file %s", fnames.name);
+ errno = ENOEXEC;
+error:
+ return -1;
+}
+
+/** check whether the 'text' fit ANYSTR, NULL or "" */
+static
+bool
+is_any(
+ const char *text
+) {
+ return text == NULL || text[0] == 0 || 0 == strcmp(text, ANYSTR);
+}
+
+/** check whether the 'text' fit ANYSTR, WIDESTR, NULL or "" */
+static
+bool
+is_any_or_wide(
+ const char *text
+) {
+ return is_any(text) || 0 == strcmp(text, WIDESTR);
+}
+
+/** get in 'session' the session for 'name' and create it if 'needed' */
+static
+int
+get_session(
+ const char *name,
+ bool needed,
+ session_t **session
+) {
+ session_t *s;
+ size_t len;
+
+ /* start on ANY sessions */
+ s = &sessions;
+ if (is_any_or_wide(name))
+ goto found;
+
+ /* look to other sessions */
+ s = s->next;
+ while(s != &sessions) {
+ if (!strcmp(s->name, name))
+ goto found;
+ s = s->next;
+ }
+
+ /* not found */
+ if (!needed) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ /* check length */
+ len = strnlen(name, MAX_NAME_LENGTH + 1);
+ if (len > MAX_NAME_LENGTH) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* create it */
+ s = malloc(sizeof * s + len + 1);
+ if (s == NULL)
+ return -1; /* out of memory */
+
+ /* init new session */
+ s->rules = NULL;
+ s->count = 0;
+ s->name = strcpy((char*)(s + 1), name);
+ s->next = &sessions;
+ s->prev = sessions.prev;
+ sessions.prev = s;
+ s->prev->next = s;
+found:
+ *session = s;
+ return 0;
+}
+
+/** for 'session' set the value the rule at 'index' */
+static
+void
+session_set_at(
+ session_t *session,
+ uint32_t index,
+ uint32_t value
+) {
+ uint32_t pos;
+
+ assert(index < session->count);
+ session->rules[index].value = value;
+ if (session == &sessions) {
+ pos = (uint32_t)(((void*)&session->rules[index]) - frules.buffer);
+ if (pos < frules.saved)
+ frules.saved = pos;
+ }
+}
+
+/** drop of 'session' the rule at 'index' */
+static
+void
+session_drop_at(
+ session_t *session,
+ uint32_t index
+) {
+ uint32_t pos;
+
+ assert(index < session->count);
+ if (index < --session->count)
+ session->rules[index] = session->rules[session->count];
+ if (session == &sessions) {
+ pos = (uint32_t)(((void*)&session->rules[index]) - frules.buffer);
+ if (pos < frules.saved)
+ frules.saved = pos;
+ pos = (uint32_t)(((void*)&session->rules[session->count]) - frules.buffer);
+ frules.used = pos;
+ }
+}
+
+/** add to 'session' the rule 'client' x 'user' x 'permission' x 'value' */
+static
+int
+session_add(
+ session_t *session,
+ uint32_t client,
+ uint32_t user,
+ uint32_t permission,
+ uint32_t value
+) {
+ int rc;
+ uint32_t c;
+ rule_t *rule;
+
+ if (session == &sessions) {
+ c = frules.used + (uint32_t)sizeof *rule;
+ rc = fbuf_ensure_capacity(&frules, c);
+ if (rc)
+ return rc;
+ frules.used = c;
+ session->rules = (rule_t*)(frules.buffer + uuidlen);
+ } else {
+ c = session->count + 32 - (session->count & 31);
+ rule = realloc(session->rules, c * sizeof *rule);
+ if (rule == NULL)
+ return -ENOMEM;
+ session->rules = rule;
+ }
+ rule = &session->rules[session->count++];
+ rule->client = client;
+ rule->user = user;
+ rule->permission = permission;
+ rule->value = value;
+ return 0;
+}
+
+/** init the rules from the file */
+static
+void
+init_rules(
+) {
+ sessions.rules = (rule_t*)(frules.buffer + uuidlen);
+ sessions.count = (frules.used - uuidlen) / sizeof *sessions.rules;
+}
+
+/** open the database for files 'names' and 'rules' (can be NULL) */
+int
+db_open(
+ const char *names,
+ const char *rules
+) {
+ int rc;
+
+ /* open the names */
+ rc = fbuf_open_identify(&fnames, names ?: "cynara.names", uuid_names_v1, uuidlen);
+ if (rc < 0)
+ goto error;
+
+ /* open the rules */
+ rc = fbuf_open_identify(&frules, rules ?: "cynara.rules", uuid_rules_v1, uuidlen);
+ if (rc < 0)
+ goto error;
+
+ /* connect internals */
+ rc = init_names();
+ if (rc < 0)
+ goto error;
+
+ init_rules();
+ return 0;
+error:
+ return -1;
+}
+
+/** close the database */
+void
+db_close(
+) {
+ assert(fnames.name && frules.name);
+ fbuf_close(&fnames);
+ fbuf_close(&frules);
+}
+
+/** is the database empty */
+bool
+db_is_empty(
+) {
+ return !sessions.count;
+}
+
+/** synchronize db on files */
+int
+db_sync(
+) {
+ int rc;
+
+ assert(fnames.name && frules.name);
+ rc = fbuf_sync(&fnames);
+ if (rc == 0)
+ rc = fbuf_sync(&frules);
+ return rc;
+}
+
+/** enumerate */
+void
+db_for_all(
+ void *closure,
+ void (*callback)(
+ void *closure,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value),
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+) {
+ uint32_t ucli, uusr, i;
+ bool anyperm, anysession;
+ session_t *ses;
+
+ if (db_get_name_index(&ucli, client, false)
+ || db_get_name_index(&uusr, user, false))
+ return; /* nothing to do! */
+
+ anyperm = is_any(permission);
+ anysession = is_any(session);
+ if (anysession)
+ ses = &sessions;
+ else {
+ if (get_session(session, false, &ses))
+ return; /* ignore if no session */
+ }
+ for(;;) {
+ for (i = 0; i < ses->count; i++) {
+ if ((ucli == ANYIDX || ucli == ses->rules[i].client)
+ && (uusr == ANYIDX || uusr == ses->rules[i].user)
+ && (anyperm || !strcasecmp(permission, name_at(ses->rules[i].permission)))) {
+ callback(closure,
+ name_at(ses->rules[i].client),
+ ses->name,
+ name_at(ses->rules[i].user),
+ name_at(ses->rules[i].permission),
+ ses->rules[i].value);
+ }
+ }
+ if (!anysession)
+ break;
+ ses = ses->next;
+ if (ses == &sessions)
+ break;
+ }
+}
+
+/** drop rules */
+int
+db_drop(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+) {
+ uint32_t ucli, uusr, i;
+ bool anyperm, anysession;
+ session_t *ses;
+
+ if (db_get_name_index(&ucli, client, false)
+ || db_get_name_index(&uusr, user, false))
+ return 0; /* nothing to do! */
+
+ anyperm = is_any(permission);
+ anysession = is_any(session);
+ if (anysession)
+ ses = &sessions;
+ else {
+ if (get_session(session, false, &ses))
+ return 0; /* ignore if no session */
+ }
+ for(;;) {
+ i = 0;
+ while (i < ses->count) {
+ if ((ucli == ANYIDX || ucli == ses->rules[i].client)
+ && (uusr == ANYIDX || uusr == ses->rules[i].user)
+ && (anyperm || !strcasecmp(permission, name_at(ses->rules[i].permission))))
+ session_drop_at(ses, i);
+ else
+ i++;
+ }
+ if (!anysession)
+ break;
+ ses = ses->next;
+ if (ses == &sessions)
+ break;
+ }
+ return 0;
+}
+
+/** set rules */
+int
+db_set(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value
+) {
+ int rc;
+ uint32_t ucli, uusr, uperm, i;
+ session_t *ses;
+
+ /* normalize */
+ client = is_any_or_wide(client) ? WIDESTR : client;
+ session = is_any_or_wide(session) ? WIDESTR : session;
+ user = is_any_or_wide(user) ? WIDESTR : user;
+ permission = is_any_or_wide(permission) ? WIDESTR : permission;
+
+ /* get the session */
+ rc = get_session(session, true, &ses);
+ if (rc)
+ goto error;
+
+ /* get/create strings */
+ rc = db_get_name_index(&ucli, client, true);
+ if (rc)
+ goto error;
+ rc = db_get_name_index(&uusr, user, true);
+ if (rc)
+ goto error;
+
+ /* search the existing rule */
+ for (i = 0 ; i < ses->count ; i++) {
+ if (ucli == ses->rules[i].client
+ && uusr == ses->rules[i].user
+ && !strcasecmp(permission, name_at(ses->rules[i].permission))) {
+ /* found */
+ session_set_at(ses, i, value);
+ return 0;
+ }
+ }
+
+ /* create the rule */
+ rc = db_get_name_index(&uperm, permission, true);
+ if (rc)
+ goto error;
+
+ rc = session_add(ses, ucli, uusr, uperm, value);
+
+ return 0;
+error:
+ return rc;
+}
+
+/** check rules */
+int
+db_test(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t *value
+) {
+ uint32_t ucli, uusr, i, val, score, sc;
+ session_t *ses;
+ rule_t *rule;
+
+ /* check */
+ client = is_any_or_wide(client) ? WIDESTR : client;
+ session = is_any_or_wide(session) ? WIDESTR : session;
+ user = is_any_or_wide(user) ? WIDESTR : user;
+ permission = is_any_or_wide(permission) ? WIDESTR : permission;
+
+ /* search the items */
+ val = score = 0;
+#define NOIDX 0
+ if (db_get_name_index(&ucli, client, false))
+ ucli = NOIDX;
+ if (db_get_name_index(&uusr, user, false))
+ uusr = NOIDX;
+
+ /* get the session */
+ if (get_session(session, false, &ses))
+ ses = &sessions;
+
+retry:
+ /* search the existing rule */
+ for (i = 0 ; i < ses->count ; i++) {
+ rule = &ses->rules[i];
+ if ((ucli == rule->client || WIDEIDX == rule->client)
+ && (uusr == rule->user || WIDEIDX == rule->user)
+ && (WIDEIDX == rule->permission
+ || !strcasecmp(permission, name_at(rule->permission)))) {
+ /* found */
+ sc = 1 + (rule->client != WIDEIDX)
+ + (rule->user != WIDEIDX) + (rule->permission != WIDEIDX);
+ if (sc > score) {
+ score = sc;
+ val = rule->value;
+ }
+ }
+ }
+ if (!score && ses != &sessions) {
+ ses = &sessions;
+ goto retry;
+ }
+
+ if (score)
+ *value = val;
+ return score > 0;
+}
+
diff --git a/src/db.h b/src/db.h
new file mode 100644
index 0000000..ce56e8c
--- /dev/null
+++ b/src/db.h
@@ -0,0 +1,102 @@
+#pragma once
+
+#define MAX_NAME_LENGTH 32767
+
+/** open the database for files 'names' and 'rules' (can be NULL) */
+extern
+int
+db_open(
+ const char *names,
+ const char *rules
+);
+
+/** close the database */
+extern
+void
+db_close(
+);
+
+/** is the database empty */
+extern
+bool
+db_is_empty(
+);
+
+/** sync the database */
+extern
+int
+db_sync(
+);
+
+/** enter critical recoverable section */
+extern
+int
+db_enter(
+);
+
+/** leave critical recoverable section */
+extern
+int
+db_leave(
+ bool commit
+);
+
+/** get an index for a name */
+extern
+int
+db_get_name_index(
+ uint32_t *index,
+ const char *name,
+ bool needed
+);
+
+/** enumerate */
+extern
+void
+db_for_all(
+ void *closure,
+ void (*callback)(
+ void *closure,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value),
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+);
+
+/** erase rules */
+extern
+int
+db_drop(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+);
+
+/** set rules */
+extern
+int
+db_set(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value
+);
+
+/** check rules */
+extern
+int
+db_test(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t *value
+);
+
diff --git a/src/export.map b/src/export.map
new file mode 100644
index 0000000..84467af
--- /dev/null
+++ b/src/export.map
@@ -0,0 +1,9 @@
+{
+global:
+ rcyn_*;
+ cynara_*;
+local:
+ *;
+};
+
+
diff --git a/src/fbuf.c b/src/fbuf.c
new file mode 100644
index 0000000..6d4d41c
--- /dev/null
+++ b/src/fbuf.c
@@ -0,0 +1,279 @@
+
+#define _GNU_SOURCE
+
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <syslog.h>
+
+#include "fbuf.h"
+
+/** compute the size to allocate for ensuring 'sz' bytes */
+static
+uint32_t
+get_asz(
+ uint32_t sz
+) {
+ return (sz & 0xfffffc00) + 0x000004cf;
+}
+
+/** open in 'fb' the file of 'name' */
+int
+fbuf_open(
+ fbuf_t *fb,
+ const char *name
+) {
+ struct stat st;
+ int fd, rc;
+ uint32_t sz, asz;
+ void *buffer;
+
+ /* open the file */
+ //fd = open(name, O_RDWR|O_CREAT|O_SYNC|O_DIRECT, 0600);
+ fd = open(name, O_RDWR|O_CREAT, 0600);
+ if (fd < 0)
+ goto error;
+
+ /* lock it */
+ rc = flock(fd, LOCK_EX|LOCK_NB);
+ if (rc < 0)
+ goto error2;
+
+ /* get file stat */
+ rc = fstat(fd, &st);
+ if (rc < 0)
+ goto error3;
+
+ /* check file size */
+ if ((off_t)INT32_MAX < st.st_size) {
+ errno = EFBIG;
+ goto error3;
+ }
+
+ /* compute allocation size */
+ sz = (uint32_t)st.st_size;
+ asz = get_asz(sz);
+ buffer = malloc(asz);
+ if (buffer == NULL)
+ goto error3;
+
+ /* read the file */
+ if (read(fd, buffer, (size_t)sz) != (ssize_t)sz)
+ goto error4;
+
+ /* done */
+ fb->name = name;
+ fb->buffer = buffer;
+ fb->saved = fb->used = fb->size = sz;
+ fb->capacity = asz;
+ fb->fd = fd;
+ return 0;
+
+error4:
+ free(buffer);
+error3:
+ flock(fd, LOCK_UN);
+error2:
+ close(fd);
+error:
+ syslog(LOG_ERR, "can't open file %s: %m", name);
+ memset(fb, 0, sizeof *fb);
+ return -errno;
+}
+
+/** close the file 'fb' */
+void
+fbuf_close(
+ fbuf_t *fb
+) {
+ free(fb->buffer);
+ flock(fb->fd, LOCK_UN);
+ close(fb->fd);
+ memset(fb, 0, sizeof *fb);
+}
+
+/** write to file 'fb' at 'offset' the 'count' bytes pointed by 'buffer' */
+int
+fbuf_write(
+ fbuf_t *fb,
+ const void *buffer,
+ uint32_t count,
+ uint32_t offset
+) {
+ off_t rco;
+ ssize_t rcs;
+
+ /* don't call me for nothing */
+ assert(count);
+
+ /* set write position */
+ rco = lseek(fb->fd, (off_t)offset, SEEK_SET);
+ if (rco != (off_t)offset)
+ goto error;
+
+ /* effective write */
+ rcs = write(fb->fd, buffer, (size_t)count);
+ if (rcs != (ssize_t)count)
+ goto error;
+
+ return 0;
+error:
+ syslog(LOG_ERR, "write of file %s failed: %m", fb->name);
+ return -errno;
+}
+
+/** write to file 'fb' the unsaved bytes and flush the content to the file */
+int
+fbuf_sync(
+ fbuf_t *fb
+) {
+ int rc;
+ bool changed = false;
+
+ /* write unsaved bytes */
+ if (fb->used > fb->saved) {
+ rc = fbuf_write(fb, fb->buffer + fb->saved, fb->used - fb->saved, fb->saved);
+ if (rc < 0)
+ return rc;
+ fb->saved = fb->used;
+ changed = true;
+ }
+
+ /* truncate on needed */
+ if (fb->used < fb->size) {
+ rc = ftruncate(fb->fd, (off_t)fb->used);
+ if (rc < 0)
+ goto error;
+ changed = true;
+ }
+ fb->size = fb->used;
+
+ /* force synchronisation of the file */
+ if (changed) {
+ rc = fsync(fb->fd);
+ if (rc < 0)
+ goto error;
+ }
+
+ return 0;
+error:
+ syslog(LOG_ERR, "sync of file %s failed: %m", fb->name);
+ return -errno;
+}
+
+/** allocate enough memory in 'fb' to store 'count' bytes */
+int
+fbuf_ensure_capacity(
+ fbuf_t *fb,
+ uint32_t count
+) {
+ uint32_t capacity;
+ void *buffer;
+
+ if (count > fb->capacity) {
+ capacity = get_asz(count);
+ buffer = realloc(fb->buffer, capacity);
+ if (buffer == NULL) {
+ syslog(LOG_ERR, "alloc %u for file %s failed: %m", capacity, fb->name);
+ return -ENOMEM;
+ }
+ fb->buffer = buffer;
+ fb->capacity = capacity;
+ }
+ return 0;
+}
+
+/** put at 'offset' in the memory of 'fb' the 'count' bytes pointed by 'buffer' */
+int
+fbuf_put(
+ fbuf_t *fb,
+ const void *buffer,
+ uint32_t count,
+ uint32_t offset
+) {
+ int rc;
+ uint32_t end = offset + count;
+
+ /* don't call me for nothing */
+ assert(count);
+
+ /* grow as necessary */
+ if (end > fb->used) {
+ rc = fbuf_ensure_capacity(fb, end);
+ if (rc < 0)
+ return rc;
+ fb->used = end;
+ }
+
+ /* copy the data */
+ memcpy(fb->buffer + offset, buffer, count);
+
+ /* write the data to the disk */
+ if (offset < fb->saved)
+ fb->saved = offset;
+ return 0;
+}
+
+/** append at end in the memory of 'fb' the 'count' bytes pointed by 'buffer' */
+int
+fbuf_append(
+ fbuf_t *fb,
+ const void *buffer,
+ uint32_t count
+) {
+ /* don't call me for nothing */
+ assert(count);
+
+ return fbuf_put(fb, buffer, count, fb->used);
+}
+
+/** check or make identification of file 'fb' by 'id' of 'len' */
+int
+fbuf_identify(
+ fbuf_t *fb,
+ const char *id,
+ uint32_t idlen
+) {
+ /* init if empty */
+ if (fb->saved == 0 && fb->used == 0)
+ return fbuf_append(fb, id, idlen);
+
+ /* check if not empty */
+ if (fb->saved >= idlen && !memcmp(fb->buffer, id, idlen))
+ return 0;
+
+ /* bad identification */
+ errno = ENOKEY;
+ syslog(LOG_ERR, "identification of file %s failed: %m", fb->name);
+ return -ENOKEY;
+}
+
+/** check or make identification by 'uuid' of file 'fb' */
+int
+fbuf_open_identify(
+ fbuf_t *fb,
+ const char *name,
+ const char *id,
+ uint32_t idlen
+) {
+ int rc;
+
+ rc = fbuf_open(fb, name);
+ if (rc == 0) {
+ rc = fbuf_identify(fb, id, idlen);
+ if (rc < 0)
+ fbuf_close(fb);
+ }
+ return rc;
+}
+
diff --git a/src/fbuf.h b/src/fbuf.h
new file mode 100644
index 0000000..3bbdbde
--- /dev/null
+++ b/src/fbuf.h
@@ -0,0 +1,111 @@
+#pragma once
+
+/**
+ * A fbuf records file data and access
+ */
+struct fbuf
+{
+ /** filename for messages */
+ const char *name;
+
+ /** in memory copy of the file */
+ void *buffer;
+
+ /** size saved to the file */
+ uint32_t saved;
+
+ /** size currently used */
+ uint32_t used;
+
+ /** size currently allocated */
+ uint32_t capacity;
+
+ /** size of the file */
+ uint32_t size;
+
+ /** opened file descriptor for the file */
+ int fd;
+};
+
+/** short type */
+typedef struct fbuf fbuf_t;
+
+
+/** open in 'fb' the file of 'name' */
+extern
+int
+fbuf_open(
+ fbuf_t *fb,
+ const char *name
+);
+
+/** close the file 'fb' */
+extern
+void
+fbuf_close(
+ fbuf_t *fb
+);
+
+/** write to file 'fb' at 'offset' the 'count' bytes pointed by 'buffer' */
+extern
+int
+fbuf_write(
+ fbuf_t *fb,
+ const void *buffer,
+ uint32_t count,
+ uint32_t offset
+);
+
+/** write to file 'fb' the unsaved bytes and flush the content to the file */
+extern
+int
+fbuf_sync(
+ fbuf_t *fb
+);
+
+/** allocate enough memory in 'fb' to store 'count' bytes */
+extern
+int
+fbuf_ensure_capacity(
+ fbuf_t *fb,
+ uint32_t count
+);
+
+/** put at 'offset' in the memory of 'fb' the 'count' bytes pointed by 'buffer' */
+extern
+int
+fbuf_put(
+ fbuf_t *fb,
+ const void *buffer,
+ uint32_t count,
+ uint32_t offset
+);
+
+/** append at end in the memory of 'fb' the 'count' bytes pointed by 'buffer' */
+extern
+int
+fbuf_append(
+ fbuf_t *fb,
+ const void *buffer,
+ uint32_t count
+);
+
+/** check or make identification of file 'fb' by 'id' of 'len' */
+extern
+int
+fbuf_identify(
+ fbuf_t *fb,
+ const char *id,
+ uint32_t idlen
+);
+
+/** check or make identification by 'uuid' of file 'fb' */
+extern
+int
+fbuf_open_identify(
+ fbuf_t *fb,
+ const char *name,
+ const char *id,
+ uint32_t idlen
+);
+
diff --git a/src/lib-compat.c b/src/lib-compat.c
new file mode 100644
index 0000000..5c6c4e2
--- /dev/null
+++ b/src/lib-compat.c
@@ -0,0 +1,651 @@
+/*
+cynara_admin_initialize(&m_CynaraAdmin),
+cynara_admin_finish(m_CynaraAdmin);
+cynara_admin_set_policies(m_CynaraAdmin, pp_policies.data()),
+cynara_admin_list_policies(m_CynaraAdmin, bucketName.c_str(), appId.c_str(),
+cynara_admin_erase(m_CynaraAdmin, bucketName.c_str(), static_cast<int>(recursive),
+cynara_admin_check(m_CynaraAdmin, bucket.c_str(), recursive, label.c_str(),
+
+cynara_initialize(&m_Cynara, nullptr),
+cynara_finish(m_Cynara);
+cynara_check(m_Cynara,
+*/
+#define _GNU_SOURCE
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/epoll.h>
+
+#include <cynara/cynara-admin.h>
+#include <cynara/cynara-client.h>
+#include <cynara/cynara-client-async.h>
+#include <cynara/cynara-creds-commons.h>
+
+#ifndef CYNARA_ADMIN_ASK
+# define CYNARA_ADMIN_ASK 11
+#endif
+
+#include "rcyn-client.h"
+
+/******************** ADMIN ********************************/
+
+static int from_status(int rc)
+{
+ switch (-rc) {
+ case 0: rc = CYNARA_API_SUCCESS; break;
+ case ENOMEM: rc = CYNARA_API_OUT_OF_MEMORY; break;
+ case ENOTSUP: rc = CYNARA_API_METHOD_NOT_SUPPORTED; break;
+ case ENOENT: rc = CYNARA_API_CACHE_MISS; break;
+ default: rc = CYNARA_API_UNKNOWN_ERROR; break;
+ }
+ return rc;
+}
+
+static int from_check_status(int rc)
+{
+ switch (rc) {
+ case 0: rc = CYNARA_API_ACCESS_DENIED; break;
+ case 1: rc = CYNARA_API_ACCESS_ALLOWED; break;
+ case -EEXIST: rc = CYNARA_API_ACCESS_NOT_RESOLVED; break;
+ default: rc = from_status(rc); break;
+ }
+ return rc;
+}
+
+static int from_value(uint32_t value)
+{
+ switch(value) {
+ case 0: return CYNARA_ADMIN_DENY;
+ case 1: return CYNARA_ADMIN_ALLOW;
+ case 2: return CYNARA_ADMIN_ASK;
+ }
+ return (int)value;
+}
+
+static uint32_t to_value(int value)
+{
+ switch(value) {
+ case CYNARA_ADMIN_DENY: return 0;
+ case CYNARA_ADMIN_NONE: return 0;
+ case CYNARA_ADMIN_BUCKET: return 0;
+ case CYNARA_ADMIN_ALLOW: return 1;
+ case CYNARA_ADMIN_ASK: return 2;
+ }
+ return (uint32_t)value;
+}
+
+/************************************ ERROR ****************************************/
+
+static const struct {
+ int num;
+ const char *text;
+} error_descriptions[] = {
+ { CYNARA_API_INTERRUPTED, "API call was interrupted by user" },
+ { CYNARA_API_ACCESS_NOT_RESOLVED, "access cannot be resolved without further actions" },
+ { CYNARA_API_ACCESS_ALLOWED, "access that was checked is allowed" },
+ { CYNARA_API_ACCESS_DENIED, "access that was checked is denied" },
+ { CYNARA_API_SUCCESS, "successful" },
+ { CYNARA_API_CACHE_MISS, "value is not present in cache" },
+ { CYNARA_API_MAX_PENDING_REQUESTS, "pending requests reached maximum" },
+ { CYNARA_API_OUT_OF_MEMORY, "system is running out of memory" },
+ { CYNARA_API_INVALID_PARAM, "parameter is malformed" },
+ { CYNARA_API_SERVICE_NOT_AVAILABLE, "service is not available" },
+ { CYNARA_API_METHOD_NOT_SUPPORTED, "method is not supported by library" },
+ { CYNARA_API_OPERATION_NOT_ALLOWED, "not allowed to perform requested operation" },
+ { CYNARA_API_OPERATION_FAILED, "failed to perform requested operation" },
+ { CYNARA_API_BUCKET_NOT_FOUND, "service hasn't found requested bucket" },
+ { CYNARA_API_UNKNOWN_ERROR, "unknown error" },
+ { CYNARA_API_CONFIGURATION_ERROR, "configuration error" },
+ { CYNARA_API_INVALID_COMMANDLINE_PARAM, "invalid parameter in command-line" },
+ { CYNARA_API_BUFFER_TOO_SHORT, "provided buffer is too short" },
+ { CYNARA_API_DATABASE_CORRUPTED, "database is corrupted" },
+ { CYNARA_API_PERMISSION_DENIED, "user doesn't have enough permission to perform action" },
+};
+
+int cynara_strerror(int errnum, char *buf, size_t buflen)
+{
+ int i = (int)(sizeof error_descriptions / sizeof *error_descriptions);
+ while(i) {
+ if (error_descriptions[--i].num == errnum) {
+ if (strlen(error_descriptions[i].text) >= buflen)
+ return CYNARA_API_BUFFER_TOO_SHORT;
+ if (buf == NULL)
+ break;
+ strcpy(buf, error_descriptions[i].text);
+ return CYNARA_API_SUCCESS;
+ }
+ }
+ return CYNARA_API_INVALID_PARAM;
+}
+
+/******************** ADMIN ********************************/
+
+struct cynara_admin;
+
+int cynara_admin_initialize(struct cynara_admin **pp_cynara_admin)
+{
+ return from_status(rcyn_open((rcyn_t**)pp_cynara_admin, rcyn_Admin, 0));
+}
+
+int cynara_admin_finish(struct cynara_admin *p_cynara_admin)
+{
+ rcyn_close((rcyn_t*)p_cynara_admin);
+ return CYNARA_API_SUCCESS;
+}
+
+int cynara_admin_set_policies(struct cynara_admin *p_cynara_admin,
+ const struct cynara_admin_policy *const *policies)
+{
+ int rc, rc2;
+ const struct cynara_admin_policy *p;
+
+ rc = rcyn_enter((rcyn_t*)p_cynara_admin);
+ if (rc == 0) {
+ p = *policies;
+ while (rc == 0 && p != NULL) {
+ if (p->result == CYNARA_ADMIN_DELETE)
+ rc = rcyn_drop((rcyn_t*)p_cynara_admin,
+ p->client, "*", p->user, p->privilege);
+ else if (p->result != CYNARA_ADMIN_BUCKET && p->result != CYNARA_ADMIN_NONE)
+ rc = rcyn_set((rcyn_t*)p_cynara_admin,
+ p->client, "*", p->user, p->privilege, to_value(p->result));
+ p = *++policies;
+ }
+ rc2 = rcyn_leave((rcyn_t*)p_cynara_admin, rc == 0);
+ if (rc == 0)
+ rc = rc2;
+ }
+ return rc;
+}
+
+static void check_cb(
+ void *closure,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value
+) {
+ *((int*)closure) = from_value(value);
+}
+
+int cynara_admin_check(struct cynara_admin *p_cynara_admin,
+ const char *start_bucket, const int recursive,
+ const char *client, const char *user, const char *privilege,
+ int *result, char **result_extra)
+{
+ if (result_extra)
+ *result_extra = NULL;
+ *result = CYNARA_ADMIN_DENY;
+ return from_status(rcyn_get((rcyn_t*)p_cynara_admin, client, "*", user, privilege, check_cb, result));
+}
+
+struct list_data
+{
+ struct cynara_admin_policy **policies;
+ const char *bucket;
+ unsigned count;
+ int error;
+};
+
+static void list_cb(
+ void *closure,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value
+) {
+ struct list_data *data = closure;
+ struct cynara_admin_policy *pol;
+
+ if (data->error)
+ return;
+
+ pol = calloc(1, sizeof *pol);
+ if (pol == NULL)
+ goto error;
+
+ pol->bucket = strdup(data->bucket ?: "");
+ pol->client = strdup(client);
+ pol->user = strdup(user);
+ pol->privilege = strdup(permission);
+ if (pol->bucket == NULL || pol->client == NULL || pol->user == NULL || pol->privilege == NULL)
+ goto error;
+
+ pol->result = from_value(value);
+ pol->result_extra = 0;
+ closure = realloc(data->policies, (data->count + 1) * sizeof *data->policies);
+ if (closure == NULL)
+ goto error;
+
+ (data->policies = closure)[data->count++] = pol;
+ return;
+error:
+ if (pol) {
+ free(pol->bucket);
+ free(pol->client);
+ free(pol->user);
+ free(pol->privilege);
+ free(pol);
+ }
+ data->error = -ENOMEM;
+
+}
+
+int cynara_admin_list_policies(struct cynara_admin *p_cynara_admin, const char *bucket,
+ const char *client, const char *user, const char *privilege,
+ struct cynara_admin_policy ***policies)
+{
+ int rc;
+ struct list_data data;
+
+ data.policies = NULL;
+ data.bucket = bucket && strcmp(bucket, "#") && strcmp(bucket, "*") ? bucket : NULL;
+ data.count = 0;
+ data.error = 0;
+ rc = rcyn_get((rcyn_t*)p_cynara_admin, client, "*", user, privilege, list_cb, &data);
+ if (rc == 0 && data.error != 0)
+ rc = data.error;
+ if (rc == 0 && !data.error) {
+ if ((*policies = realloc(data.policies, (data.count + 1) * sizeof *data.policies)) != NULL)
+ policies[0][data.count] = NULL;
+ else
+ rc = -ENOMEM;
+ }
+ if (rc) {
+ while(data.count)
+ free(data.policies[--data.count]);
+ free(data.policies);
+ *policies = NULL;
+ }
+ return from_status(rc);
+}
+
+int cynara_admin_erase(struct cynara_admin *p_cynara_admin,
+ const char *start_bucket, int recursive,
+ const char *client, const char *user, const char *privilege)
+{
+ int rc, rc2;
+
+ rc = rcyn_enter((rcyn_t*)p_cynara_admin);
+ if (rc == 0) {
+ rc = rcyn_drop((rcyn_t*)p_cynara_admin,
+ client, "*", user, privilege);
+ rc2 = rcyn_leave((rcyn_t*)p_cynara_admin, rc == 0);
+ if (rc == 0)
+ rc = rc2;
+ }
+ return from_status(rc);
+}
+
+
+int cynara_admin_list_policies_descriptions(struct cynara_admin *p_cynara_admin,
+ struct cynara_admin_policy_descr ***descriptions)
+{
+ struct cynara_admin_policy_descr **d = malloc(4 * sizeof *d), *s;
+ if (d) {
+ d[0] = malloc(sizeof *s);
+ d[1] = malloc(sizeof *s);
+ d[2] = malloc(sizeof *s);
+ d[3] = NULL;
+ if (d[0] != NULL && d[1] != NULL && d[2] != NULL) {
+ d[0]->name = strdup("Deny");
+ d[1]->name = strdup("AskUser");
+ d[2]->name = strdup("Allow");
+ if (d[0]->name != NULL && d[1]->name != NULL && d[2]->name != NULL) {
+ d[0]->result = CYNARA_ADMIN_DENY;
+ d[1]->result = CYNARA_ADMIN_ASK;
+ d[2]->result = CYNARA_ADMIN_ALLOW;
+ *descriptions = d;
+ return CYNARA_API_SUCCESS;
+ }
+ free(d[0]->name);
+ free(d[1]->name);
+ free(d[2]->name);
+ }
+ free(d[0]);
+ free(d[1]);
+ free(d[2]);
+ }
+ *descriptions = NULL;
+ return CYNARA_API_OUT_OF_MEMORY;
+}
+
+/************************************* CLIENT-ASYNC **************************************/
+
+int cynara_async_configuration_create(cynara_async_configuration **pp_conf)
+{
+ *pp_conf = (cynara_async_configuration*)pp_conf;
+ return CYNARA_API_SUCCESS;
+}
+
+void cynara_async_configuration_destroy(cynara_async_configuration *p_conf)
+{
+}
+
+int cynara_async_configuration_set_cache_size(cynara_async_configuration *p_conf,
+ size_t cache_size)
+{
+ return CYNARA_API_SUCCESS;
+}
+
+struct reqasync
+{
+ struct reqasync *next;
+ cynara_async *cynasync;
+ cynara_response_callback callback;
+ void *user_response_data;
+ cynara_check_id id;
+ bool canceled;
+};
+
+struct cynara_async
+{
+ rcyn_t *rcyn;
+ cynara_status_callback callback;
+ void *user_status_data;
+ struct reqasync *reqs;
+ cynara_check_id ids;
+};
+
+static int async_control_cb(void *closure, int op, int fd, uint32_t events)
+{
+ cynara_async *p_cynara = closure;
+ cynara_async_status s = (events & EPOLLOUT) ? CYNARA_STATUS_FOR_RW : CYNARA_STATUS_FOR_READ;
+ switch(op) {
+ case EPOLL_CTL_ADD:
+ p_cynara->callback(-1, fd, s, p_cynara->user_status_data);
+ break;
+ case EPOLL_CTL_MOD:
+ p_cynara->callback(fd, fd, s, p_cynara->user_status_data);
+ break;
+ case EPOLL_CTL_DEL:
+ p_cynara->callback(fd, -1, 0, p_cynara->user_status_data);
+ break;
+ }
+ return 0;
+}
+
+int cynara_async_initialize(cynara_async **pp_cynara, const cynara_async_configuration *p_conf,
+ cynara_status_callback callback, void *user_status_data)
+{
+ int ret;
+ cynara_async *p_cynara;
+
+ p_cynara = malloc(sizeof *p_cynara);
+ if (p_cynara == NULL)
+ ret = CYNARA_API_OUT_OF_MEMORY;
+ else {
+ ret = from_status(rcyn_open(&p_cynara->rcyn, rcyn_Check, 0));
+ if (ret != CYNARA_API_SUCCESS)
+ free(p_cynara);
+ else {
+ p_cynara->callback = callback;
+ p_cynara->user_status_data = user_status_data;
+ p_cynara->reqs = NULL;
+ p_cynara->ids = 0;
+ rcyn_async_setup(p_cynara->rcyn, async_control_cb, p_cynara);
+ *pp_cynara = p_cynara;
+ }
+ }
+ return ret;
+}
+
+void cynara_async_finish(cynara_async *p_cynara)
+{
+ struct reqasync *req;
+
+ for(req = p_cynara->reqs ; req ; req = req->next) {
+ if (!req->canceled) {
+ req->callback(req->id, CYNARA_CALL_CAUSE_FINISH, 0, req->user_response_data);
+ req->canceled = true;
+ }
+ }
+
+ rcyn_close(p_cynara->rcyn);
+
+ while((req = p_cynara->reqs)) {
+ p_cynara->reqs = req->next;
+ free(req);
+ }
+ free(p_cynara);
+}
+
+int cynara_async_check_cache(cynara_async *p_cynara, const char *client, const char *client_session,
+ const char *user, const char *privilege)
+{
+ return from_check_status(rcyn_cache_check(p_cynara->rcyn, client, client_session,user, privilege));
+}
+
+static void reqcb(void *closure, int status)
+{
+ struct reqasync *req = closure, **p;
+
+ p = &req->cynasync->reqs;
+ while(*p && *p != req)
+ p = &(*p)->next;
+ if (*p)
+ *p = req->next;
+
+ if (!req->canceled)
+ req->callback(req->id, CYNARA_CALL_CAUSE_ANSWER, status, req->user_response_data);
+
+ free(req);
+}
+
+static int create_reqasync(cynara_async *p_cynara, const char *client,
+ const char *client_session, const char *user, const char *privilege,
+ cynara_check_id *p_check_id, cynara_response_callback callback,
+ void *user_response_data, bool simple)
+{
+ int rc;
+ struct reqasync *req;
+
+ req = malloc(sizeof *req);
+ if (req == NULL)
+ return CYNARA_API_OUT_OF_MEMORY;
+
+ req->next = p_cynara->reqs;
+ req->cynasync = p_cynara;
+ req->callback = callback;
+ req->user_response_data = user_response_data;
+ req->id = ++p_cynara->ids;
+ req->canceled = false;
+
+ rc = rcyn_async_check(p_cynara->rcyn, client, client_session, user, privilege, simple, reqcb, req);
+ if (rc == 0)
+ p_cynara->reqs = req;
+ else
+ free(req);
+ return from_status(rc);
+}
+
+int cynara_async_create_request(cynara_async *p_cynara, const char *client,
+ const char *client_session, const char *user, const char *privilege,
+ cynara_check_id *p_check_id, cynara_response_callback callback,
+ void *user_response_data)
+{
+ return create_reqasync(p_cynara, client, client_session, user, privilege, p_check_id, callback, user_response_data, false);
+}
+
+int cynara_async_create_simple_request(cynara_async *p_cynara, const char *client,
+ const char *client_session, const char *user,
+ const char *privilege, cynara_check_id *p_check_id,
+ cynara_response_callback callback, void *user_response_data)
+{
+ return create_reqasync(p_cynara, client, client_session, user, privilege, p_check_id, callback, user_response_data, true);
+}
+
+
+int cynara_async_process(cynara_async *p_cynara)
+{
+ return rcyn_async_process(p_cynara->rcyn);
+}
+
+int cynara_async_cancel_request(cynara_async *p_cynara, cynara_check_id check_id)
+{
+ struct reqasync *req = p_cynara->reqs;
+
+ while(req && req->id != check_id)
+ req = req->next;
+ if (req && !req->canceled) {
+ req->canceled = true;
+ req->callback(req->id, CYNARA_CALL_CAUSE_CANCEL, 0, req->user_response_data);
+ }
+ return CYNARA_API_SUCCESS;
+}
+
+/************************************* CLIENT **************************************/
+
+int cynara_configuration_create(cynara_configuration **pp_conf)
+{
+ *pp_conf = (cynara_configuration*)pp_conf;
+ return CYNARA_API_SUCCESS;
+}
+
+void cynara_configuration_destroy(cynara_configuration *p_conf)
+{
+}
+
+int cynara_configuration_set_cache_size(cynara_configuration *p_conf, size_t cache_size)
+{
+ return CYNARA_API_SUCCESS;
+}
+
+int cynara_initialize(cynara **pp_cynara, const cynara_configuration *p_conf)
+{
+ return from_status(rcyn_open((rcyn_t**)pp_cynara, rcyn_Check, 0));
+}
+
+int cynara_finish(cynara *p_cynara)
+{
+ rcyn_close((rcyn_t*)p_cynara);
+ return CYNARA_API_SUCCESS;
+}
+
+int cynara_check(cynara *p_cynara, const char *client, const char *client_session, const char *user,
+ const char *privilege)
+{
+ return from_check_status(rcyn_check((rcyn_t*)p_cynara, client, client_session, user, privilege));
+}
+
+int cynara_simple_check(cynara *p_cynara, const char *client, const char *client_session,
+ const char *user, const char *privilege)
+{
+ return from_check_status(rcyn_test((rcyn_t*)p_cynara, client, client_session, user, privilege));
+}
+
+/************************************* CREDS... & SESSION *********************************/
+#define MAX_LABEL_LENGTH 1024
+
+#if !defined(DEFAULT_PEERSEC_LABEL)
+# define DEFAULT_PEERSEC_LABEL "NoLabel"
+#endif
+
+int cynara_creds_get_default_client_method(enum cynara_client_creds *method)
+{
+ *method = CLIENT_METHOD_SMACK;
+ return CYNARA_API_SUCCESS;
+}
+
+int cynara_creds_get_default_user_method(enum cynara_user_creds *method)
+{
+ *method = USER_METHOD_UID;
+ return CYNARA_API_SUCCESS;
+}
+
+int cynara_creds_self_get_client(enum cynara_client_creds method, char **client)
+{
+ char label[MAX_LABEL_LENGTH + 1];
+ int len, fd;
+
+ label[0] = 0;
+ fd = open("/proc/self/current/attr", O_RDONLY);
+ if (fd >= 0) {
+ len = (int)read(fd, label, sizeof label - 1);
+ label[len >= 0 ? len : 0] = 0;
+ close(fd);
+ }
+ return (*client = strdup(label[0] ? label : DEFAULT_PEERSEC_LABEL))
+ ? CYNARA_API_SUCCESS : CYNARA_API_OUT_OF_MEMORY;
+}
+
+int cynara_creds_self_get_user(enum cynara_user_creds method, char **user)
+{
+ return asprintf(user, "%ld", (long)getuid()) > 0
+ ? CYNARA_API_SUCCESS : CYNARA_API_OUT_OF_MEMORY;
+}
+
+int cynara_creds_socket_get_client(int fd, enum cynara_client_creds method, char **client)
+{
+ int rc;
+ socklen_t length;
+ struct ucred ucred;
+ char label[MAX_LABEL_LENGTH + 1];
+
+ /* get the credentials */
+ length = (socklen_t)(sizeof ucred);
+ rc = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &length);
+ if (rc < 0 || length != (socklen_t)(sizeof ucred) || !~ucred.uid)
+ return CYNARA_API_OPERATION_FAILED;
+
+ /* get the security label */
+ length = (socklen_t)(sizeof label);
+ rc = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, label, &length);
+ if (rc < 0 || length > (socklen_t)(sizeof label))
+ return CYNARA_API_OPERATION_FAILED;
+
+ return (*client = strdup(label))
+ ? CYNARA_API_SUCCESS : CYNARA_API_OUT_OF_MEMORY;
+}
+
+
+
+int cynara_creds_socket_get_user(int fd, enum cynara_user_creds method, char **user)
+{
+ int rc;
+ socklen_t length;
+ struct ucred ucred;
+
+ /* get the credentials */
+ length = (socklen_t)(sizeof ucred);
+ rc = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &length);
+ if (rc < 0 || length != (socklen_t)(sizeof ucred) || !~ucred.uid)
+ return CYNARA_API_OPERATION_FAILED;
+ return asprintf(user, "%ld", (long)ucred.uid) > 0
+ ? CYNARA_API_SUCCESS : CYNARA_API_OUT_OF_MEMORY;
+}
+
+
+
+int cynara_creds_socket_get_pid(int fd, pid_t *pid)
+{
+ int rc;
+ socklen_t length;
+ struct ucred ucred;
+
+ /* get the credentials */
+ length = (socklen_t)(sizeof ucred);
+ rc = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &length);
+ if (rc < 0 || length != (socklen_t)(sizeof ucred) || !~ucred.uid)
+ return CYNARA_API_OPERATION_FAILED;
+ *pid = ucred.pid;
+ return CYNARA_API_SUCCESS;
+}
+
+char *cynara_session_from_pid(pid_t client_pid)
+{
+ char *r;
+
+ return asprintf(&r, "%ld", (long)client_pid) < 0 ? NULL : r;
+}
+
diff --git a/src/prot.c b/src/prot.c
new file mode 100644
index 0000000..db0e41e
--- /dev/null
+++ b/src/prot.c
@@ -0,0 +1,440 @@
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/uio.h>
+
+#include "prot.h"
+
+/** the structure buf is generic the meaning of pos/count is not fixed */
+struct buf
+{
+ /** a position */
+ unsigned pos;
+
+ /** a count */
+ unsigned count;
+
+ /** a fixed size content */
+ char content[MAXBUFLEN];
+};
+typedef struct buf buf_t;
+
+/** structure for recording received fields */
+struct fields
+{
+ /** count of field (negative if invalid) */
+ int count;
+
+ /** the fields as strings */
+ const char *fields[MAXARGS];
+};
+typedef struct fields fields_t;
+
+/** structure for handling the protocol */
+struct prot
+{
+ /** input buf, pos is the scanning position */
+ buf_t inbuf;
+
+ /** output buf, pos is to be written position */
+ buf_t outbuf;
+
+ /** the fields */
+ fields_t fields;
+};
+
+
+/**
+ * Put the 'count' in 'fields' to the 'buf'
+ * returns:
+ * - 0 on success
+ * - -EINVAL if the count of fields is too big
+ * - -ECANCELED if there is not enought space in the buffer
+ */
+static
+int
+buf_put_fields(
+ buf_t *buf,
+ unsigned count,
+ const char **fields
+) {
+ unsigned ifield, pos, remain;
+ const char *t;
+ char c;
+
+ /* check the count of fields */
+ if (count > MAXARGS)
+ return -EINVAL;
+
+ /* get the writing position and the free count */
+ pos = buf->pos + buf->count;
+ if (pos >= MAXBUFLEN)
+ pos -= MAXBUFLEN;
+ remain = MAXBUFLEN - buf->count;
+
+ /* put all fields */
+ for (ifield = 0 ; ifield < count ; ifield++) {
+ /* prepend the field separator if needed */
+ if (ifield) {
+ if (!remain--)
+ goto cancel;
+ buf->content[pos++] = FS;
+ if (pos == MAXBUFLEN)
+ pos = 0;
+ }
+ /* put the field if any (NULL aliases "") */
+ t = fields[ifield];
+ if (t) {
+ /* put all chars of the field */
+ while((c = *t++)) {
+ /* escape special characters */
+ if (c == FS || c == RS || c == ESC) {
+ if (!remain--)
+ goto cancel;
+ buf->content[pos++] = ESC;
+ if (pos == MAXBUFLEN)
+ pos = 0;
+ }
+ /* put the char */
+ if (!remain--)
+ goto cancel;
+ buf->content[pos++] = c;
+ if (pos == MAXBUFLEN)
+ pos = 0;
+ }
+ }
+ }
+
+ /* put the end indicator */
+ if (!remain--)
+ goto cancel;
+ buf->content[pos] = RS;
+
+ /* record the new values */
+ buf->count = MAXBUFLEN - remain;
+ return 0;
+
+cancel:
+ return -ECANCELED;
+}
+
+/**
+ * write the content of 'buf' to 'fd'
+ */
+static
+int
+buf_write(
+ buf_t *buf,
+ int fd
+) {
+ int n;
+ unsigned count;
+ ssize_t rc;
+ struct iovec vec[2];
+
+ /* get the count of byte to write (avoid int overflow) */
+ count = buf->count > INT_MAX ? INT_MAX : buf->count;
+
+ /* calling it with nothing to write is an error */
+ if (count == 0)
+ return -ENODATA;
+
+ /* prepare the iovec */
+ vec[0].iov_base = buf->content + buf->pos;
+ if (buf->pos + count <= MAXBUFLEN) {
+ vec[0].iov_len = count;
+ n = 1;
+ } else {
+ vec[0].iov_len = MAXBUFLEN - buf->pos;
+ vec[1].iov_base = buf->content;
+ vec[1].iov_len = count - vec[0].iov_len;
+ n = 2;
+ }
+
+ /* write the buffers */
+ do {
+ rc = writev(fd, vec, n);
+ } while(rc < 0 && errno == EINTR);
+
+ /* check error */
+ if (rc < 0)
+ rc = -errno;
+ else {
+ /* update the state */
+ buf->count -= (unsigned)rc;
+ buf->pos += (unsigned)rc;
+ if (buf->pos >= MAXBUFLEN)
+ buf->pos -= MAXBUFLEN;
+ }
+
+ return (int)rc;
+}
+
+/* get the 'fields' from 'buf' */
+static
+void
+buf_get_fields(
+ buf_t *buf,
+ fields_t *fields
+) {
+ char c;
+ unsigned read, write;
+
+ /* advance the pos after the end */
+ assert(buf->content[buf->pos] == RS);
+ buf->pos++;
+
+ /* init first field */
+ fields->fields[fields->count = 0] = buf->content;
+ read = write = 0;
+ for (;;) {
+ c = buf->content[read++];
+ switch(c) {
+ case FS: /* field separator */
+ buf->content[write++] = 0;
+ if (fields->count >= MAXARGS)
+ return;
+ fields->fields[++fields->count] = &buf->content[write];
+ break;
+ case RS: /* end of line (record separator) */
+ buf->content[write] = 0;
+ fields->count += (write > 0);
+ return;
+ case ESC: /* escaping */
+ c = buf->content[read++];
+ if (c != FS && c != RS && c != ESC)
+ buf->content[write++] = ESC;
+ buf->content[write++] = c;
+ break;
+ default: /* other characters */
+ buf->content[write++] = c;
+ break;
+ }
+ }
+}
+
+/**
+ * Advance pos of 'buf' until end of record RS found in buffer.
+ * return 1 if found or 0 if not found
+ */
+static
+int
+buf_scan_end_record(
+ buf_t *buf
+) {
+ unsigned nesc;
+
+ /* search the next RS */
+ while(buf->pos < buf->count) {
+ if (buf->content[buf->pos] == RS) {
+ /* check whether RS is escaped */
+ nesc = 0;
+ while (buf->pos > nesc && buf->content[buf->pos - (nesc + 1)] == ESC)
+ nesc++;
+ if ((nesc & 1) == 0)
+ return 1; /* not escaped */
+ }
+ buf->pos++;
+ }
+ return 0;
+}
+
+/** remove chars of 'buf' until pos */
+static
+void
+buf_crop(
+ buf_t *buf
+) {
+ buf->count -= buf->pos;
+ if (buf->count)
+ memmove(buf->content, buf->content + buf->pos, buf->count);
+ buf->pos = 0;
+}
+
+/** read input 'buf' from 'fd' */
+static
+int
+inbuf_read(
+ buf_t *buf,
+ int fd
+) {
+ ssize_t szr;
+ int rc;
+
+ if (buf->count == MAXBUFLEN)
+ return -ENOBUFS;
+
+ do {
+ szr = read(fd, buf->content + buf->count, MAXBUFLEN - buf->count);
+ } while(szr < 0 && errno == EINTR);
+ if (szr >= 0)
+ buf->count += (unsigned)(rc = (int)szr);
+ else if (szr < 0)
+ rc = -(errno == EWOULDBLOCK ? EAGAIN : errno);
+
+ return rc;
+}
+
+/**
+ * create the prot structure in 'prot'
+ * Return 0 in case of success or -ENOMEM in case of error
+ */
+int
+prot_create(
+ prot_t **prot
+) {
+ prot_t *p;
+
+ /* allocation of the structure */
+ *prot = p = malloc(sizeof *p);
+ if (p == NULL)
+ return -ENOMEM;
+
+ /* initialisation of the structure */
+ prot_reset(p);
+
+ /* terminate */
+ return 0;
+}
+
+/**
+ * Destroys the protocol 'prot'
+ */
+void
+prot_destroy(
+ prot_t *prot
+) {
+ free(prot);
+}
+
+/**
+ * reset the protocol 'prot'
+ */
+void
+prot_reset(
+ prot_t *prot
+) {
+ /* initialisation of the structure */
+ prot->inbuf.pos = prot->inbuf.count = 0;
+ prot->outbuf.pos = prot->outbuf.count = 0;
+ prot->fields.count = -1;
+}
+
+/**
+ * Put protocol encoded 'count' 'fields' to the output buffer
+ * returns:
+ * - 0 on success
+ * - -EINVAL if the count of fields is too big
+ * - -ECANCELED if there is not enought space in the buffer
+ */
+int
+prot_put(
+ prot_t *prot,
+ unsigned count,
+ const char **fields
+) {
+ return buf_put_fields(&prot->outbuf, count, fields);
+}
+
+/**
+ * Put protocol encoded fields until NULL found to the output buffer
+ * returns:
+ * - 0 on success
+ * - -EINVAL if the count of fields is too big
+ * - -ECANCELED if there is not enought space in the buffer
+ */
+int
+prot_putx(
+ prot_t *prot,
+ ...
+) {
+ const char *p, *fields[MAXARGS];
+ unsigned n;
+ va_list l;
+
+ va_start(l, prot);
+ n = 0;
+ p = va_arg(l, const char *);
+ while (p) {
+ if (n == MAXARGS)
+ return -EINVAL;
+ fields[n++] = p;
+ p = va_arg(l, const char *);
+ }
+ va_end(l);
+ return prot_put(prot, n, fields);
+}
+
+/**
+ * Check whether write should be done or not
+ * Returns 1 if there is something to write or 0 otherwise
+ */
+int
+prot_should_write(
+ prot_t *prot
+) {
+ return prot->outbuf.count > 0;
+}
+
+/**
+ * Write the content to write and return either the count
+ * of bytes written or an error code (negative). Note that
+ * the returned value tries to be the same as those returned
+ * by "man 2 write". The only exception is -ENODATA that is
+ * returned if there is nothing to be written.
+ */
+int
+prot_write(
+ prot_t *prot,
+ int fdout
+) {
+ return buf_write(&prot->outbuf, fdout);
+}
+
+int
+prot_can_read(
+ prot_t *prot
+) {
+ return prot->inbuf.count < MAXBUFLEN;
+}
+
+int
+prot_read(
+ prot_t *prot,
+ int fdin
+) {
+ return inbuf_read(&prot->inbuf, fdin);
+}
+
+int
+prot_get(
+ prot_t *prot,
+ const char ***fields
+) {
+ if (prot->fields.count < 0) {
+ if (!buf_scan_end_record(&prot->inbuf))
+ return -EAGAIN;
+ buf_get_fields(&prot->inbuf, &prot->fields);
+ }
+ if (fields)
+ *fields = prot->fields.fields;
+ return (int)prot->fields.count;
+}
+
+void
+prot_next(
+ prot_t *prot
+) {
+ if (prot->fields.count >= 0) {
+ buf_crop(&prot->inbuf);
+ prot->fields.count = -1;
+ }
+}
+
+
diff --git a/src/prot.h b/src/prot.h
new file mode 100644
index 0000000..a1ded3b
--- /dev/null
+++ b/src/prot.h
@@ -0,0 +1,85 @@
+#pragma once
+
+struct prot;
+typedef struct prot prot_t;
+
+#define MAXBUFLEN 2000
+#define MAXARGS 20
+#define FS ' '
+#define RS '\n'
+#define ESC '\\'
+
+
+extern
+int
+prot_create(
+ prot_t **prot
+);
+
+extern
+void
+prot_destroy(
+ prot_t *prot
+);
+
+extern
+void
+prot_reset(
+ prot_t *prot
+);
+
+extern
+int
+prot_put(
+ prot_t *prot,
+ unsigned count,
+ const char **fields
+);
+
+extern
+int
+prot_putx(
+ prot_t *prot,
+ ...
+);
+
+extern
+int
+prot_should_write(
+ prot_t *prot
+);
+
+extern
+int
+prot_write(
+ prot_t *prot,
+ int fdout
+);
+
+extern
+int
+prot_can_read(
+ prot_t *prot
+);
+
+extern
+int
+prot_read(
+ prot_t *prot,
+ int fdin
+);
+
+extern
+int
+prot_get(
+ prot_t *prot,
+ const char ***fields
+);
+
+extern
+void
+prot_next(
+ prot_t *prot
+);
+
+
diff --git a/src/queue.c b/src/queue.c
new file mode 100644
index 0000000..16b0a0f
--- /dev/null
+++ b/src/queue.c
@@ -0,0 +1,192 @@
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "db.h"
+
+#define DROP 0
+#define SET 1
+
+/**
+ * Queue
+ */
+struct queue
+{
+ uint32_t read, write, capacity;
+ void *queue;
+};
+typedef struct queue queue_t;
+
+/** the queue */
+static queue_t queue;
+
+static
+bool
+qread(
+ void *data,
+ uint32_t length
+) {
+ if (queue.read + length > queue.write)
+ return false;
+
+ memcpy(data, queue.queue + queue.read, length);
+ queue.read += length;
+ return true;
+}
+
+static
+bool
+qget_char(
+ char *value
+) {
+ return qread(value, sizeof *value);
+}
+
+static
+bool
+qget_uint32(
+ uint32_t *value
+) {
+ return qread(value, sizeof *value);
+}
+
+static
+bool
+qget_string(
+ const char **text
+) {
+ char *p = queue.queue + queue.read;
+ uint32_t length = (uint32_t)strlen(p);
+ if (queue.read + length >= queue.write)
+ return false;
+ *text = p;
+ queue.read += length + 1;
+ return true;
+}
+
+static
+bool
+qwrite(
+ const void *data,
+ uint32_t length
+) {
+ uint32_t c;
+ void *b;
+
+ c = queue.capacity;
+ while (c < queue.write + length)
+ c += 4096;
+ if (c != queue.capacity) {
+ b = realloc(queue.queue, c);
+ if (b == NULL)
+ return false;
+ queue.queue = b;
+ queue.capacity = c;
+ }
+ memcpy(queue.queue + queue.write, data, length);
+ queue.write += length;
+ return true;
+}
+
+static
+bool
+qput_char(
+ char value
+) {
+ return qwrite(&value, sizeof value);
+}
+
+static
+bool
+qput_uint32(
+ uint32_t value
+) {
+ return qwrite(&value, sizeof value);
+}
+
+static
+bool
+qput_string(
+ const char *text
+) {
+ size_t len;
+ text = text ?: "";
+ /* check length */
+ len = strnlen(text, MAX_NAME_LENGTH + 1);
+ if (len > MAX_NAME_LENGTH)
+ return false;
+ return qwrite(text, 1 + (uint32_t)len);
+}
+
+int
+queue_drop(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+) {
+ return qput_char(DROP)
+ && qput_string(client)
+ && qput_string(session)
+ && qput_string(user)
+ && qput_string(permission)
+ ? 0 : -(errno = ENOMEM);
+}
+
+int
+queue_set(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value
+) {
+ return qput_char(SET)
+ && qput_string(client)
+ && qput_string(session)
+ && qput_string(user)
+ && qput_string(permission)
+ && qput_uint32(value)
+ ? 0 : -(errno = ENOMEM);
+}
+
+
+void
+queue_clear(
+) {
+ queue.write = 0;
+}
+
+int
+queue_play(
+) {
+ int rc, rc2;
+ char op;
+ const char *client;
+ const char *session;
+ const char *user;
+ const char *permission;
+ uint32_t value;
+
+ rc = 0;
+ queue.read = 0;
+ while (queue.read < queue.write) {
+ rc2 = -EINVAL;
+ if (qget_char(&op)
+ && qget_string(&client)
+ && qget_string(&session)
+ && qget_string(&user)
+ && qget_string(&permission)) {
+ if (op == DROP)
+ rc2 = db_drop(client, session, user, permission);
+ else if (qget_uint32(&value))
+ rc2 = db_set(client, session, user, permission, value);
+ }
+ if (rc2 != 0 && rc == 0)
+ rc = rc2;
+ }
+ return rc;
+}
+
diff --git a/src/queue.h b/src/queue.h
new file mode 100644
index 0000000..70518c4
--- /dev/null
+++ b/src/queue.h
@@ -0,0 +1,31 @@
+
+extern
+int
+queue_drop(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+);
+
+extern
+int
+queue_set(
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value
+);
+
+extern
+void
+queue_clear(
+);
+
+extern
+int
+queue_play(
+);
+
+
diff --git a/src/rcyn-client.c b/src/rcyn-client.c
new file mode 100644
index 0000000..f712ca6
--- /dev/null
+++ b/src/rcyn-client.c
@@ -0,0 +1,722 @@
+#define _GNU_SOURCE
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/epoll.h>
+
+#include "prot.h"
+#include "rcyn-protocol.h"
+#include "rcyn-client.h"
+#include "cache.h"
+#include "socket.h"
+
+#define MIN_CACHE_SIZE 400
+
+struct asreq;
+typedef struct asreq asreq_t;
+
+/** recording of asynchronous requests */
+struct asreq
+{
+ /** link to the next pending request */
+ asreq_t *next;
+
+ /** callback function */
+ void (*callback)(
+ void *closure,
+ int status);
+
+ /** closure of the callback */
+ void *closure;
+};
+
+struct rcyn;
+typedef struct rcyn rcyn_t;
+
+/**
+ * structure recording a rcyn client
+ */
+struct rcyn
+{
+ /** file descriptor of the socket */
+ int fd;
+
+ /** count of pending requests */
+ int pending;
+
+ /** type of link */
+ rcyn_type_t type;
+
+ /** protocol manager object */
+ prot_t *prot;
+
+ /** cache object */
+ cache_t *cache;
+
+ /** copy of the reply */
+ struct {
+ /** count of fields of the reply */
+ int count;
+
+ /** fields (or fields) of the reply */
+ const char **fields;
+ } reply;
+
+ /** async */
+ struct {
+ /** control callback */
+ rcyn_async_ctl_t controlcb;
+
+ /** closure */
+ void *closure;
+
+ /** reqests */
+ asreq_t *requests;
+ } async;
+};
+
+static void disconnection(rcyn_t *rcyn);
+
+/**
+ * Flush the write buffer
+ */
+static
+int
+flushw(
+ rcyn_t *rcyn
+) {
+ int rc;
+ struct pollfd pfd;
+
+ rc = prot_should_write(rcyn->prot);
+ while (rc) {
+ rc = prot_write(rcyn->prot, rcyn->fd);
+ if (rc == -EAGAIN) {
+ pfd.fd = rcyn->fd;
+ pfd.events = POLLOUT;
+ do { rc = poll(&pfd, 1, 0); } while (rc < 0 && errno == EINTR);
+ if (rc < 0)
+ rc = -errno;
+ }
+ if (rc < 0) {
+ break;
+ }
+ rc = prot_should_write(rcyn->prot);
+ }
+ return rc;
+}
+
+/**
+ * Put the command made of arguments ...
+ * Increment the count of pending requests.
+ * Return 0 in case of success or a negative number on error.
+ */
+static
+int
+putx(
+ rcyn_t *rcyn,
+ ...
+) {
+ const char *p, *fields[MAXARGS];
+ unsigned n;
+ va_list l;
+ int rc;
+
+ /* reconstruct the array of arguments */
+ va_start(l, rcyn);
+ n = 0;
+ p = va_arg(l, const char *);
+ while (p && n < MAXARGS) {
+ fields[n++] = p;
+ p = va_arg(l, const char *);
+ }
+ va_end(l);
+
+ /* put it to the output buffer */
+ rc = prot_put(rcyn->prot, n, fields);
+ if (rc == -ECANCELED) {
+ /* not enough room in the buffer, flush it */
+ rc = flushw(rcyn);
+ if (rc == 0)
+ rc = prot_put(rcyn->prot, n, fields);
+ }
+ /* client always flushes */
+ if (rc == 0) {
+ rcyn->pending++;
+ rc = flushw(rcyn);
+ }
+ return rc;
+}
+
+static
+int
+wait_input(
+ rcyn_t *rcyn
+) {
+ int rc;
+ struct pollfd pfd;
+
+ pfd.fd = rcyn->fd;
+ pfd.events = POLLIN;
+ do { rc = poll(&pfd, 1, 0); } while (rc < 0 && errno == EINTR);
+ return rc < 0 ? -errno : 0;
+}
+
+static
+int
+get_reply(
+ rcyn_t *rcyn
+) {
+ int rc;
+
+ prot_next(rcyn->prot);
+ rc = rcyn->reply.count = prot_get(rcyn->prot, &rcyn->reply.fields);
+ if (rc <= 0)
+ return rc;
+ if (0 != strcmp(rcyn->reply.fields[0], _clear_)) {
+ if (0 != strcmp(rcyn->reply.fields[0], _item_))
+ rcyn->pending--;
+ return rc;
+ }
+ cache_clear(rcyn->cache);
+ return rcyn->reply.count = 0;
+}
+
+static
+int
+wait_reply(
+ rcyn_t *rcyn,
+ bool block
+) {
+ int rc;
+
+ for(;;) {
+ prot_next(rcyn->prot);
+ rc = get_reply(rcyn);
+ if (rc > 0)
+ return rc;
+ if (rc < 0) {
+ rc = prot_read(rcyn->prot, rcyn->fd);
+ while (rc <= 0) {
+ if (rc == 0)
+ return -(errno = EPIPE);
+ if (rc == -EAGAIN && block)
+ rc = wait_input(rcyn);
+ if (rc < 0)
+ return rc;
+ rc = prot_read(rcyn->prot, rcyn->fd);
+ }
+ }
+ }
+}
+
+static
+int
+flushr(
+ rcyn_t *rcyn
+) {
+ int rc;
+
+ do { rc = wait_reply(rcyn, false); } while(rc > 0);
+ return rc;
+}
+
+static
+int
+status_done(
+ rcyn_t *rcyn
+) {
+ return strcmp(rcyn->reply.fields[0], _done_) ? -ECANCELED : 0;
+}
+
+static
+int
+status_check(
+ rcyn_t *rcyn
+) {
+ return !strcmp(rcyn->reply.fields[0], _yes_) ? 1
+ : !strcmp(rcyn->reply.fields[0], _no_) ? 0
+ : -EEXIST;
+}
+
+static
+int
+wait_pending_reply(
+ rcyn_t *rcyn
+) {
+ int rc;
+ for (;;) {
+ rc = wait_reply(rcyn, true);
+ if (rc < 0)
+ return rc;
+ if (rc > 0 && rcyn->pending == 0)
+ return rc;
+ }
+}
+
+static
+int
+wait_done(
+ rcyn_t *rcyn
+) {
+ int rc = wait_pending_reply(rcyn);
+ if (rc > 0)
+ rc = status_done(rcyn);
+ return rc;
+}
+
+static
+int
+async(
+ rcyn_t *rcyn,
+ int op,
+ uint32_t events
+) {
+ return rcyn->async.controlcb
+ ? rcyn->async.controlcb(rcyn->async.closure, op, rcyn->fd, events)
+ : 0;
+}
+
+static
+void
+disconnection(
+ rcyn_t *rcyn
+) {
+ if (rcyn->fd >= 0) {
+ async(rcyn, EPOLL_CTL_DEL, 0);
+ close(rcyn->fd);
+ rcyn->fd = -1;
+ }
+}
+
+static
+int
+connection(
+ rcyn_t *rcyn
+) {
+ int rc;
+ const char *spec;
+
+ /* socket spec */
+ switch(rcyn->type) {
+ default:
+ case rcyn_Check: spec = rcyn_default_check_socket_spec; break;
+ case rcyn_Admin: spec = rcyn_default_admin_socket_spec; break;
+ }
+
+ /* init the client */
+ rcyn->pending = 0;
+ rcyn->reply.count = -1;
+ cache_clear(rcyn->cache);
+ prot_reset(rcyn->prot);
+ rcyn->fd = socket_open(spec, 0);
+ if (rcyn->fd < 0)
+ return -errno;
+
+ /* negociate the protocol */
+ rc = putx(rcyn, _rcyn_, "1", NULL);
+ if (rc >= 0) {
+ rc = wait_pending_reply(rcyn);
+ if (rc >= 0) {
+ rc = -EPROTO;
+ if (rcyn->reply.count == 2
+ && 0 == strcmp(rcyn->reply.fields[0], _yes_)
+ && 0 == strcmp(rcyn->reply.fields[1], "1")) {
+ rc = async(rcyn, EPOLL_CTL_ADD, EPOLLIN);
+ if (rc >= 0)
+ return 0;
+ }
+ }
+ }
+ disconnection(rcyn);
+ return rc;
+}
+
+
+/************************************************************************************/
+
+int
+rcyn_open(
+ rcyn_t **prcyn,
+ rcyn_type_t type,
+ uint32_t cache_size
+) {
+ rcyn_t *rcyn;
+ int rc;
+
+ /* allocate the structure */
+ *prcyn = rcyn = malloc(sizeof *rcyn);
+ if (rcyn == NULL) {
+ rc = -ENOMEM;
+ goto error;
+ }
+
+ /* create a protocol object */
+ rc = prot_create(&rcyn->prot);
+ if (rc < 0)
+ goto error2;
+
+ /* record type and weakly create cache */
+ cache_create(&rcyn->cache, cache_size < MIN_CACHE_SIZE ? MIN_CACHE_SIZE : cache_size);
+ rcyn->type = type;
+ rcyn->async.controlcb = NULL;
+ rcyn->async.closure = 0;
+ rcyn->async.requests = NULL;
+
+ /* connection */
+ rc = connection(rcyn);
+ if (rc < 0)
+ goto error3;
+
+ /* done */
+ return 0;
+
+error3:
+ free(rcyn->cache);
+ prot_destroy(rcyn->prot);
+error2:
+ free(rcyn);
+error:
+ *prcyn = NULL;
+ return rc;
+}
+
+void
+rcyn_close(
+ rcyn_t *rcyn
+) {
+ rcyn_async_setup(rcyn, NULL, NULL);
+ disconnection(rcyn);
+ prot_destroy(rcyn->prot);
+ free(rcyn->cache);
+ free(rcyn);
+}
+
+int
+rcyn_enter(
+ rcyn_t *rcyn
+) {
+ int rc;
+
+ if (rcyn->type != rcyn_Admin)
+ return -EPERM;
+ if (rcyn->async.requests != NULL)
+ return -EINPROGRESS;
+
+ rc = putx(rcyn, _enter_, NULL);
+ if (rc >= 0)
+ rc = wait_done(rcyn);
+ return rc;
+}
+
+int
+rcyn_leave(
+ rcyn_t *rcyn,
+ bool commit
+) {
+ int rc;
+
+ if (rcyn->type != rcyn_Admin)
+ return -EPERM;
+ if (rcyn->async.requests != NULL)
+ return -EINPROGRESS;
+
+ rc = putx(rcyn, _leave_, commit ? _commit_ : NULL/*default: rollback*/, NULL);
+ if (rc >= 0)
+ rc = wait_done(rcyn);
+ return rc;
+}
+
+static
+int
+check_or_test(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ const char *action
+) {
+ int rc;
+
+ if (rcyn->async.requests != NULL)
+ return -EINPROGRESS;
+
+ /* ensure there is no clear cache pending */
+ flushr(rcyn);
+
+ /* check cache item */
+ rc = cache_search(rcyn->cache, client, session, user, permission);
+ if (rc >= 0)
+ return rc;
+
+ /* send the request */
+ rc = putx(rcyn, action, client, session, user, permission, NULL);
+ if (rc >= 0) {
+ /* get the response */
+ rc = wait_pending_reply(rcyn);
+ if (rc >= 0) {
+ rc = status_check(rcyn);
+ if (rc >= 0)
+ cache_put(rcyn->cache, client, session, user, permission, rc);
+ }
+ }
+ return rc;
+}
+
+int
+rcyn_check(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+) {
+ return check_or_test(rcyn, client, session, user, permission, _check_);
+}
+
+int
+rcyn_test(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+) {
+ return check_or_test(rcyn, client, session, user, permission, _check_);
+}
+
+int
+rcyn_set(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ int value
+) {
+ char val[30];
+ int rc;
+
+ if (rcyn->type != rcyn_Admin)
+ return -EPERM;
+ if (rcyn->async.requests != NULL)
+ return -EINPROGRESS;
+
+ snprintf(val, sizeof val, "%u", (unsigned)value);
+ rc = putx(rcyn, _set_, client, session, user, permission, val, NULL);
+ if (rc >= 0)
+ rc = wait_done(rcyn);
+ return rc;
+}
+
+int
+rcyn_get(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ void (*callback)(
+ void *closure,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value
+ ),
+ void *closure
+) {
+ int rc;
+
+ if (rcyn->type != rcyn_Admin)
+ return -EPERM;
+ if (rcyn->async.requests != NULL)
+ return -EINPROGRESS;
+
+ rc = putx(rcyn, _get_, client, session, user, permission, NULL);
+ if (rc >= 0) {
+ rc = wait_reply(rcyn, true);
+ while (rc == 6 && !strcmp(rcyn->reply.fields[0], _item_)) {
+ callback(closure,
+ rcyn->reply.fields[1],
+ rcyn->reply.fields[2],
+ rcyn->reply.fields[3],
+ rcyn->reply.fields[4],
+ (uint32_t)atoi(rcyn->reply.fields[5]));
+ rc = wait_reply(rcyn, true);
+ }
+ rc = status_done(rcyn);
+ }
+ return rc;
+}
+
+int
+rcyn_drop(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+) {
+ int rc;
+
+ if (rcyn->type != rcyn_Admin)
+ return -EPERM;
+ if (rcyn->async.requests != NULL)
+ return -EINPROGRESS;
+
+ rc = putx(rcyn, _drop_, client, session, user, permission, NULL);
+ if (rc >= 0)
+ rc = wait_done(rcyn);
+ return rc;
+}
+
+/************************************************************************************/
+
+int
+rcyn_cache_resize(
+ rcyn_t *rcyn,
+ uint32_t size
+) {
+ return cache_resize(&rcyn->cache, size < MIN_CACHE_SIZE ? MIN_CACHE_SIZE : size);
+}
+
+void
+rcyn_cache_clear(
+ rcyn_t *rcyn
+) {
+ cache_clear(rcyn->cache);
+}
+
+int
+rcyn_cache_check(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+) {
+ return cache_search(rcyn->cache, client, session, user, permission);
+}
+
+
+/************************************************************************************/
+
+int
+rcyn_async_setup(
+ rcyn_t *rcyn,
+ rcyn_async_ctl_t controlcb,
+ void *closure
+) {
+ asreq_t *ar;
+
+ /* cancel pending requests */
+ while((ar = rcyn->async.requests) != NULL) {
+ rcyn->async.requests = ar->next;
+ ar->callback(ar->closure, -ECANCELED);
+ free(ar);
+ }
+ /* remove existing polling */
+ async(rcyn, EPOLL_CTL_DEL, 0);
+ /* records new data */
+ rcyn->async.controlcb = controlcb;
+ rcyn->async.closure = closure;
+ /* record to polling */
+ return async(rcyn, EPOLL_CTL_ADD, EPOLLIN);
+}
+
+int
+rcyn_async_process(
+ rcyn_t *rcyn
+) {
+ int rc;
+ const char *first;
+ asreq_t *ar;
+
+ for (;;) {
+ /* non blocking wait for a reply */
+ rc = wait_reply(rcyn, false);
+ if (rc < 0)
+ return rc == -EAGAIN ? 0 : rc;
+
+ /* skip empty replies */
+ if (rc == 0)
+ continue;
+
+ /* skip done/error replies */
+ first = rcyn->reply.fields[0];
+ if (!strcmp(first, _done_)
+ || !strcmp(first, _error_))
+ continue;
+
+ /* ignore unexpected answers */
+ ar = rcyn->async.requests;
+ if (ar == NULL)
+ continue;
+
+ /* emit the asynchronous answer */
+ rcyn->async.requests = ar->next;
+ rc = status_check(rcyn);
+ ar->callback(ar->closure, rc);
+ free(ar);
+ }
+}
+
+int
+rcyn_async_check(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ bool simple,
+ void (*callback)(
+ void *closure,
+ int status),
+ void *closure
+) {
+ int rc;
+ asreq_t **pr, *ar;
+
+ /* allocate */
+ ar = malloc(sizeof *ar);
+ if (ar == NULL)
+ return -ENOMEM;
+
+ /* init */
+ ar->next = NULL;
+ ar->callback = callback;
+ ar->closure = closure;
+
+ /* send the request */
+ rc = putx(rcyn, simple ? _test_ : _check_,
+ client, session, user, permission, NULL);
+ if (rc >= 0)
+ rc = flushw(rcyn);
+ if (rc < 0) {
+ free(ar);
+ return rc;
+ }
+
+ /* record the request */
+ pr = &rcyn->async.requests;
+ while(*pr != NULL)
+ pr = &(*pr)->next;
+ *pr = ar;
+ return 0;
+}
+
+
diff --git a/src/rcyn-client.h b/src/rcyn-client.h
new file mode 100644
index 0000000..6706820
--- /dev/null
+++ b/src/rcyn-client.h
@@ -0,0 +1,149 @@
+
+#pragma once
+
+typedef enum rcyn_type {
+ rcyn_Check,
+ rcyn_Admin
+} rcyn_type_t;
+
+struct rcyn;
+typedef struct rcyn rcyn_t;
+
+extern
+int
+rcyn_open(
+ rcyn_t **rcyn,
+ rcyn_type_t type,
+ uint32_t cache_size
+);
+
+extern
+void
+rcyn_close(
+ rcyn_t *rcyn
+);
+
+extern
+int
+rcyn_enter(
+ rcyn_t *rcyn
+);
+
+extern
+int
+rcyn_leave(
+ rcyn_t *rcyn,
+ bool commit
+);
+
+extern
+int
+rcyn_check(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+);
+
+extern
+int
+rcyn_test(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+);
+
+extern
+int
+rcyn_set(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ int value
+);
+
+extern
+int
+rcyn_get(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ void (*callback)(
+ void *closure,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value
+ ),
+ void *closure
+);
+
+extern
+int
+rcyn_drop(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+);
+
+extern
+void
+rcyn_cache_clear(
+ rcyn_t *rcyn
+);
+
+extern
+int
+rcyn_cache_check(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission
+);
+
+typedef int (*rcyn_async_ctl_t)(
+ void *closure,
+ int op,
+ int fd,
+ uint32_t events);
+
+extern
+int
+rcyn_async_setup(
+ rcyn_t *rcyn,
+ rcyn_async_ctl_t controlcb,
+ void *closure
+);
+
+extern
+int
+rcyn_async_process(
+ rcyn_t *rcyn
+);
+
+extern
+int
+rcyn_async_check(
+ rcyn_t *rcyn,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ bool test,
+ void (*callback)(
+ void *closure,
+ int status),
+ void *closure
+);
+
diff --git a/src/rcyn-protocol.c b/src/rcyn-protocol.c
new file mode 100644
index 0000000..c656570
--- /dev/null
+++ b/src/rcyn-protocol.c
@@ -0,0 +1,33 @@
+#include "rcyn-protocol.h"
+
+const char
+ _check_[] = "check",
+ _drop_[] = "drop",
+ _enter_[] = "enter",
+ _get_[] = "get",
+ _leave_[] = "leave",
+ _rcyn_[] = "rcyn",
+ _set_[] = "set",
+ _test_[] = "test";
+
+const char
+ _commit_[] = "commit",
+ _rollback_[] = "rollback";
+
+const char
+ _clear_[] = "clear",
+ _done_[] = "done",
+ _error_[] = "error",
+ _item_[] = "item",
+ _no_[] = "no",
+ _yes_[] = "yes";
+
+#if !defined(RCYN_DEFAULT_CHECK_SOCKET_SPEC)
+# define RCYN_DEFAULT_CHECK_SOCKET_SPEC "unix:/run/platform/cynara.check"
+#endif
+#if !defined(RCYN_DEFAULT_ADMIN_SOCKET_SPEC)
+# define RCYN_DEFAULT_ADMIN_SOCKET_SPEC "unix:/run/platform/cynara.admin"
+#endif
+const char
+ rcyn_default_check_socket_spec[] = RCYN_DEFAULT_CHECK_SOCKET_SPEC,
+ rcyn_default_admin_socket_spec[] = RCYN_DEFAULT_ADMIN_SOCKET_SPEC;
diff --git a/src/rcyn-protocol.h b/src/rcyn-protocol.h
new file mode 100644
index 0000000..8906194
--- /dev/null
+++ b/src/rcyn-protocol.h
@@ -0,0 +1,28 @@
+#pragma once
+
+extern const char
+ _check_[],
+ _drop_[],
+ _enter_[],
+ _get_[],
+ _leave_[],
+ _rcyn_[],
+ _set_[],
+ _test_[];
+
+extern const char
+ _commit_[],
+ _rollback_[];
+
+extern const char
+ _clear_[],
+ _done_[],
+ _error_[],
+ _item_[],
+ _no_[],
+ _yes_[];
+
+extern const char
+ rcyn_default_check_socket_spec[],
+ rcyn_default_admin_socket_spec[];
+
diff --git a/src/rcyn-protocol.txt b/src/rcyn-protocol.txt
new file mode 100644
index 0000000..134d05e
--- /dev/null
+++ b/src/rcyn-protocol.txt
@@ -0,0 +1,79 @@
+protocol
+========
+
+hello:
+
+ c->s rcyn 1
+ s->c yes 1
+
+invalidate cache:
+
+ s->c clear
+
+test a permission:
+
+ c->s test CLIENT SESSION USER PERMISSION
+ s->c yes|no|VALUE [CACHING]
+
+check a permission:
+
+ c->s check CLIENT SESSION USER PERMISSION
+ s->c yes|no|VALUE [CACHING]
+
+erase (admin):
+
+ c->s drop CLIENT SESSION USER PERMISSION
+ s->c done|error ...
+
+set (admin):
+
+ c->s set CLIENT SESSION USER PERMISSION VALUE
+ s->c done|error ...
+
+list permissions (admin):
+
+ c->s get CLIENT SESSION USER PERMISSION
+ s->c item CLIENT SESSION USER PERMISSION VALUE
+ s->c ...
+ s->c done
+
+enter critical (admin)
+
+ c->s enter
+ s->c done
+
+leave critical (admin)
+
+ c->s leave [commit|rollback]
+ s->c done|error ...
+
+register agent (agent):
+
+ c->s agent NAME [ARGS...]
+ s->c done|error ...
+
+asking (agent ask CLIENT SESSION USER PERMISSION):
+
+ s->c ask CLIENT SESSION USER PERMISSION
+ c->s done | ([yes|no] [always|session|one-time])
+
+
+----------------------------------------------------------
+ c->s c(heck) CLIENT SESSION USER PERMISSION
+ c->s d(rop) CLIENT SESSION USER PERMISSION
+ c->s e(nter)
+ c->s g(et) CLIENT SESSION USER PERMISSION
+ c->s l(eave) [commit|rollback]
+ c->s r(cyn)
+ c->s s(et) CLIENT SESSION USER PERMISSION VALUE
+ c->s t(est) CLIENT SESSION USER PERMISSION
+
+ s->c clear
+ s->c done
+ s->c done [CLIENT SESSION USER PERMISSION VALUE]
+ s->c done|error ...
+ s->c item CLIENT SESSION USER PERMISSION VALUE
+ s->c yes|no
+ s->c yes|no|VALUE [CACHING]
+
+
diff --git a/src/rcyn-server.c b/src/rcyn-server.c
new file mode 100644
index 0000000..4fc365e
--- /dev/null
+++ b/src/rcyn-server.c
@@ -0,0 +1,669 @@
+#define _GNU_SOURCE
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <poll.h>
+#include <sys/epoll.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#if defined(WITH_SYSTEMD_ACTIVATION)
+#include <systemd/sd-daemon.h>
+#endif
+
+#include "prot.h"
+#include "cyn.h"
+#include "rcyn-protocol.h"
+#include "socket.h"
+
+typedef enum rcyn_type {
+ rcyn_Check,
+ rcyn_Admin
+} rcyn_type_t;
+
+/** structure for using epoll easily */
+typedef struct pollitem pollitem_t;
+struct pollitem
+{
+ /** callback on event */
+ void (*handler)(pollitem_t *pollitem, uint32_t events, int pollfd);
+
+ /** data */
+ union {
+ void *closure;
+ int fd;
+ };
+};
+
+static
+int
+pollitem_do(
+ pollitem_t *pollitem,
+ uint32_t events,
+ int fd,
+ int pollfd,
+ int op
+) {
+ struct epoll_event ev = { .events = events, .data.ptr = pollitem };
+ return epoll_ctl(pollfd, op, fd, &ev);
+}
+
+static
+int
+pollitem_add(
+ pollitem_t *pollitem,
+ uint32_t events,
+ int fd,
+ int pollfd
+) {
+ return pollitem_do(pollitem, events, fd, pollfd, EPOLL_CTL_ADD);
+}
+
+#if 0
+static
+int
+pollitem_mod(
+ pollitem_t *pollitem,
+ uint32_t events,
+ int fd,
+ int pollfd
+) {
+ return pollitem_do(pollitem, events, fd, pollfd, EPOLL_CTL_MOD);
+}
+#endif
+
+static
+int
+pollitem_del(
+ int fd,
+ int pollfd
+) {
+ return pollitem_do(NULL, 0, fd, pollfd, EPOLL_CTL_DEL);
+}
+
+
+/** structure that represents a rcyn client */
+struct client
+{
+ /** a protocol structure */
+ prot_t *prot;
+
+ /** the in/out file descriptor */
+ int fd;
+
+ /** type of client */
+ rcyn_type_t type;
+
+ /** the version of the protocol used */
+ unsigned version: 4;
+
+ /** is relaxed version of the protocol */
+ unsigned relax: 1;
+
+ /** is the actual link invalid or valid */
+ unsigned invalid: 1;
+
+ /** enter/leave status, record if entered */
+ unsigned entered: 1;
+
+ /** enter/leave status, record if entring pending */
+ unsigned entering: 1;
+
+ /** indicate if some check were made */
+ unsigned checked: 1;
+
+ /** polling callback */
+ pollitem_t pollitem;
+};
+typedef struct client client_t;
+
+/**
+ * Check 'arg' against 'value' beginning at offset accepting it if 'arg' prefixes 'value'
+ * Returns 1 if matching or 0 if not.
+ */
+static
+bool
+ckarg(
+ const char *arg,
+ const char *value,
+ unsigned offset
+) {
+ while(arg[offset])
+ if (arg[offset] == value[offset])
+ offset++;
+ else
+ return false;
+ return true;
+}
+
+/**
+ * Flush the write buffer
+ */
+static
+int
+flushw(
+ client_t *cli
+) {
+ int rc;
+ struct pollfd pfd;
+
+ rc = prot_should_write(cli->prot);
+ while (rc) {
+ rc = prot_write(cli->prot, cli->fd);
+ if (rc == -EAGAIN) {
+ pfd.fd = cli->fd;
+ pfd.events = POLLOUT;
+ do { rc = poll(&pfd, 1, 0); } while (rc < 0 && errno == EINTR);
+ }
+ if (rc < 0) {
+ break;
+ }
+ rc = prot_should_write(cli->prot);
+ }
+ return rc;
+}
+
+/**
+ * Send a reply to client
+ */
+static
+int
+putx(
+ client_t *cli,
+ ...
+) {
+ const char *p, *fields[MAXARGS];
+ unsigned n;
+ va_list l;
+ int rc;
+
+ va_start(l, cli);
+ n = 0;
+ p = va_arg(l, const char *);
+ while (p) {
+ if (n == MAXARGS)
+ return -EINVAL;
+ fields[n++] = p;
+ p = va_arg(l, const char *);
+ }
+ va_end(l);
+ rc = prot_put(cli->prot, n, fields);
+ if (rc == -ECANCELED) {
+ rc = flushw(cli);
+ if (rc == 0)
+ rc = prot_put(cli->prot, n, fields);
+ }
+ return rc;
+}
+
+/** emit a simple done reply and flush */
+static
+void
+send_done(
+ client_t *cli
+) {
+ putx(cli, _done_, NULL);
+ flushw(cli);
+}
+
+/** emit a simple error reply and flush */
+static
+void
+send_error(
+ client_t *cli,
+ const char *errorstr
+) {
+ putx(cli, _error_, errorstr, NULL);
+ flushw(cli);
+}
+
+/** emit a simple done/error reply */
+static
+void
+send_done_or_error(
+ client_t *cli,
+ int status
+) {
+ if (status >= 0)
+ send_done(cli);
+ else
+ send_error(cli, NULL);
+}
+
+/** callback of entering */
+static
+void
+entercb(
+ void *closure
+) {
+ client_t *cli = closure;
+
+ cli->entered = true;
+ cli->entering = false;
+ send_done(cli);
+}
+
+/** callback of checking */
+static
+void
+checkcb(
+ void *closure,
+ uint32_t value
+) {
+ client_t *cli = closure;
+ const char *a;
+ char val[30];
+
+ if (value == DENY)
+ a = _no_;
+ else if (value == ALLOW)
+ a = _yes_;
+ else {
+ snprintf(val, sizeof val, "%d", value);
+ a = val;
+ }
+ putx(cli, a, NULL);
+ flushw(cli);
+}
+
+/** callback of getting list of entries */
+static
+void
+getcb(
+ void *closure,
+ const char *client,
+ const char *session,
+ const char *user,
+ const char *permission,
+ uint32_t value
+) {
+ client_t *cli = closure;
+ char val[30];
+ snprintf(val, sizeof val, "%d", value);
+ putx(cli, _item_, client, session, user, permission, val, NULL);
+}
+
+/** handle a request */
+static
+void
+onrequest(
+ client_t *cli,
+ int count,
+ const char *args[]
+) {
+ int rc;
+ uint32_t value;
+
+ /* just ignore empty lines */
+ if (count == 0)
+ return;
+
+ /* version hand-shake */
+ if (!cli->version) {
+ if (!ckarg(args[0], _rcyn_, 0) || count != 2 || !ckarg(args[1], "1", 0))
+ goto invalid;
+ putx(cli, _yes_, "1", NULL);
+ flushw(cli);
+ cli->version = 1;
+ return;
+ }
+
+ switch(args[0][0]) {
+ case 'c': /* check */
+ if (ckarg(args[0], _check_, 1) && count == 5) {
+ cli->checked = 1;
+ cyn_check_async(checkcb, cli, args[1], args[2], args[3], args[4]);
+ return;
+ }
+ break;
+ case 'd': /* drop */
+ if (ckarg(args[0], _drop_, 1) && count == 5) {
+ if (cli->type != rcyn_Admin)
+ break;
+ if (!cli->entered)
+ break;
+ rc = cyn_drop(args[1], args[2], args[3], args[4]);
+ send_done_or_error(cli, rc);
+ return;
+ }
+ break;
+ case 'e': /* enter */
+ if (ckarg(args[0], _enter_, 1) && count == 1) {
+ if (cli->type != rcyn_Admin)
+ break;
+ if (cli->entered || cli->entering)
+ break;
+ cli->entering = true;
+ /* TODO: remove from polling until entered? */
+ cyn_enter_async(entercb, cli);
+ return;
+ }
+ break;
+ case 'g': /* get */
+ if (ckarg(args[0], _get_, 1) && count == 5) {
+ if (cli->type != rcyn_Admin)
+ break;
+ cyn_list(cli, getcb, args[1], args[2], args[3], args[4]);
+ send_done(cli);
+ return;
+ }
+ break;
+ case 'l': /* leave */
+ if (ckarg(args[0], _leave_, 1) && count <= 2) {
+ if (cli->type != rcyn_Admin)
+ break;
+ if (count == 2 && !ckarg(args[1], _commit_, 0) && !ckarg(args[1], _rollback_, 0))
+ break;
+ if (!cli->entered)
+ break;
+ rc = cyn_leave(cli, count == 2 && ckarg(args[1], _commit_, 0));
+ cli->entered = false;
+ send_done_or_error(cli, rc);
+ return;
+ }
+ break;
+ case 's': /* set */
+ if (ckarg(args[0], _set_, 1) && count == 6) {
+ if (cli->type != rcyn_Admin)
+ break;
+ if (!cli->entered)
+ break;
+ value = (uint32_t)atol(args[5]);
+ rc = cyn_set(args[1], args[2], args[3], args[4], value);
+ send_done_or_error(cli, rc);
+ return;
+ }
+ break;
+ case 't': /* test */
+ if (ckarg(args[0], _test_, 1) && count == 5) {
+ cyn_test(args[1], args[2], args[3], args[4], &value);
+ checkcb(cli, value);
+ return;
+ }
+ break;
+ }
+invalid: /* invalid rest detected */
+ send_error(cli, "invalid");
+ if (!cli->relax)
+ cli->invalid = 1;
+}
+
+/** on change callback, emits a clear for caching */
+static
+void
+onchange(
+ void *closure
+) {
+ client_t *cli = closure;
+ if (cli->checked) {
+ cli->checked = false;
+ putx(cli, _clear_, NULL);
+ flushw(cli);
+ }
+}
+
+/** destroy a client */
+static
+void
+destroy_client(
+ client_t *cli,
+ bool closefds
+) {
+ if (closefds)
+ close(cli->fd);
+ if (cli->entering)
+ cyn_enter_async_cancel(entercb, cli);
+ if (cli->entered)
+ cyn_leave(cli, false);
+ cyn_on_change_remove(onchange, cli);
+ prot_destroy(cli->prot);
+ free(cli);
+}
+
+/** handle client requests */
+static
+void
+on_client_event(
+ pollitem_t *pollitem,
+ uint32_t events,
+ int pollfd
+) {
+ int nargs, nr;
+ const char **args;
+ client_t *cli = pollitem->closure;
+
+ /* is it a hangup? */
+ if (events & EPOLLHUP)
+ goto terminate;
+
+ /* possible input */
+ if (events & EPOLLIN) {
+ nr = prot_read(cli->prot, cli->fd);
+ if (nr <= 0)
+ goto terminate;
+ nargs = prot_get(cli->prot, &args);
+ while (nargs >= 0) {
+ onrequest(cli, nargs, args);
+ if (cli->invalid && !cli->relax)
+ goto terminate;
+ prot_next(cli->prot);
+ nargs = prot_get(cli->prot, &args);
+ }
+ }
+ return;
+
+ /* terminate the client session */
+terminate:
+ pollitem_del(cli->fd, pollfd);
+ destroy_client(cli, true);
+}
+
+/** create a client */
+static
+int
+create_client(
+ client_t **pcli,
+ int fd,
+ rcyn_type_t type
+) {
+ client_t *cli;
+ int rc;
+
+ /* allocate the object */
+ *pcli = cli = calloc(1, sizeof *cli);
+ if (cli == NULL) {
+ rc = -ENOMEM;
+ goto error;
+ }
+
+ /* create protocol object */
+ rc = prot_create(&cli->prot);
+ if (rc < 0)
+ goto error2;
+
+ /* monitor change and caching */
+ rc = cyn_on_change_add(onchange, cli);
+ if (rc < 0)
+ goto error3;
+
+ /* records the file descriptor */
+ cli->fd = fd;
+ cli->type = type;
+ cli->version = 0; /* version not set */
+ cli->relax = true; /* relax on error */
+ cli->invalid = false; /* not invalid */
+ cli->entered = false; /* not entered */
+ cli->entering = false; /* not entering */
+ cli->checked = false; /* no check made */
+ cli->pollitem.handler = on_client_event;
+ cli->pollitem.closure = cli;
+ return 0;
+error3:
+ prot_destroy(cli->prot);
+error2:
+ free(cli);
+error:
+ *pcli = NULL;
+ return rc;
+}
+
+/** handle server events */
+static
+void
+on_server_event(
+ pollitem_t *pollitem,
+ uint32_t events,
+ int pollfd,
+ rcyn_type_t type
+) {
+ int servfd = pollitem->fd;
+ int fd, rc;
+ struct sockaddr saddr;
+ socklen_t slen;
+ client_t *cli;
+
+ /* is it a hangup? it shouldn't! */
+ if (events & EPOLLHUP) {
+ fprintf(stderr, "unexpected server socket closing\n");
+ exit(2);
+ }
+
+ /* EPOLLIN is the only expected event but asserting makes fear */
+ if (!(events & EPOLLIN))
+ return;
+
+ /* accept the connection */
+ slen = (socklen_t)sizeof saddr;
+ fd = accept(servfd, &saddr, &slen);
+ if (fd < 0) {
+ fprintf(stderr, "can't accept connection: %m\n");
+ return;
+ }
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ fcntl(fd, F_SETFL, O_NONBLOCK);
+
+ /* create a client for the connection */
+ rc = create_client(&cli, fd, type);
+ if (rc < 0) {
+ fprintf(stderr, "can't create client connection: %s\n", strerror(-rc));
+ close(fd);
+ return;
+ }
+
+ /* add the client to the epolling */
+ rc = pollitem_add(&cli->pollitem, EPOLLIN, fd, pollfd);
+ if (rc < 0) {
+ fprintf(stderr, "can't poll client connection: %s\n", strerror(-rc));
+ destroy_client(cli, 1);
+ return;
+ }
+}
+
+/** handle check server events */
+static
+void
+on_check_server_event(
+ pollitem_t *pollitem,
+ uint32_t events,
+ int pollfd
+) {
+ on_server_event(pollitem, events, pollfd, rcyn_Check);
+}
+
+/** handle admin server events */
+static
+void
+on_admin_server_event(
+ pollitem_t *pollitem,
+ uint32_t events,
+ int pollfd
+) {
+ on_server_event(pollitem, events, pollfd, rcyn_Admin);
+}
+
+int main(int ac, char **av)
+{
+ int rc;
+ pollitem_t pi_admin, pi_check, *pi;
+ int pollfd, fd;
+ struct epoll_event ev;
+ const char *check_spec = rcyn_default_check_socket_spec;
+ const char *admin_spec = rcyn_default_admin_socket_spec;
+
+ /*
+ * future possible options:
+ * - strict/relax
+ * - databse name(s)
+ * - socket name
+ * - policy
+ */
+
+ /* connection to the database */
+ rc = cyn_init();
+ if (rc < 0) {
+ fprintf(stderr, "can't initialise database: %m\n");
+ return 1;
+ }
+
+ /* create the polling fd */
+ pollfd = epoll_create1(EPOLL_CLOEXEC);
+ if (pollfd < 0) {
+ fprintf(stderr, "can't create polling: %m\n");
+ return 1;
+ }
+
+ /* create the admin server socket */
+ fd = socket_open(admin_spec, 1);
+ if (fd < 0) {
+ fprintf(stderr, "can't create admin server socket: %m\n");
+ return 1;
+ }
+
+ /* add the server to pollfd */
+ pi_admin.handler = on_admin_server_event;
+ pi_admin.fd = fd;
+ rc = pollitem_add(&pi_admin, EPOLLIN, fd, pollfd);
+ if (rc < 0) {
+ fprintf(stderr, "can't poll admin server: %m\n");
+ return 1;
+ }
+
+ /* create the server socket */
+ fd = socket_open(check_spec, 1);
+ if (fd < 0) {
+ fprintf(stderr, "can't create check server socket: %m\n");
+ return 1;
+ }
+
+ /* add the server to pollfd */
+ pi_check.handler = on_check_server_event;
+ pi_check.fd = fd;
+ rc = pollitem_add(&pi_check, EPOLLIN, fd, pollfd);
+ if (rc < 0) {
+ fprintf(stderr, "can't poll check server: %m\n");
+ return 1;
+ }
+
+ /* ready ! */
+#if defined(WITH_SYSTEMD_ACTIVATION)
+ sd_notify(0, "READY=1");
+#endif
+
+ /* process inputs */
+ for(;;) {
+ rc = epoll_wait(pollfd, &ev, 1, -1);
+ if (rc == 1) {
+ pi = ev.data.ptr;
+ pi->handler(pi, ev.events, pollfd);
+ }
+ }
+}
+
diff --git a/src/socket.c b/src/socket.c
new file mode 100644
index 0000000..3ab0408
--- /dev/null
+++ b/src/socket.c
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2015-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.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <endian.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#if defined(WITH_SYSTEMD_ACTIVATION)
+#include <systemd/sd-daemon.h>
+#endif
+
+#include "socket.h"
+
+#define BACKLOG 30
+
+/******************************************************************************/
+
+/**
+ * known types
+ */
+enum type {
+ /** type internet */
+ Type_Inet,
+
+ /** type systemd */
+ Type_Systemd,
+
+ /** type Unix */
+ Type_Unix
+};
+
+/**
+ * Structure for known entries
+ */
+struct entry
+{
+ /** the known prefix */
+ const char *prefix;
+
+ /** the type of the entry */
+ unsigned type: 2;
+
+ /** should not set SO_REUSEADDR for servers */
+ unsigned noreuseaddr: 1;
+
+ /** should not call listen for servers */
+ unsigned nolisten: 1;
+};
+
+/**
+ * The known entries with the default one at the first place
+ */
+static struct entry entries[] = {
+ {
+ .prefix = "tcp:",
+ .type = Type_Inet
+ },
+ {
+ .prefix = "sd:",
+ .type = Type_Systemd,
+ .noreuseaddr = 1,
+ .nolisten = 1
+ },
+ {
+ .prefix = "unix:",
+ .type = Type_Unix
+ }
+};
+
+/******************************************************************************/
+
+/**
+ * open a unix domain socket for client or server
+ *
+ * @param spec the specification of the path (prefix with @ for abstract)
+ * @param server 0 for client, server otherwise
+ *
+ * @return the file descriptor number of the socket or -1 in case of error
+ */
+static int open_unix(const char *spec, int server)
+{
+ int fd, rc, abstract;
+ struct sockaddr_un addr;
+ size_t length;
+
+ abstract = spec[0] == '@';
+
+ /* check the length */
+ length = strlen(spec);
+ if (length >= 108) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ /* remove the file on need */
+ if (server && !abstract)
+ unlink(spec);
+
+ /* create a socket */
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ return fd;
+
+ /* prepare address */
+ memset(&addr, 0, sizeof addr);
+ addr.sun_family = AF_UNIX;
+ strcpy(addr.sun_path, spec);
+ if (abstract)
+ addr.sun_path[0] = 0; /* implement abstract sockets */
+
+ if (server) {
+ rc = bind(fd, (struct sockaddr *) &addr, (socklen_t)(sizeof addr));
+ } else {
+ rc = connect(fd, (struct sockaddr *) &addr, (socklen_t)(sizeof addr));
+ }
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ return fd;
+}
+
+/**
+ * open a tcp socket for client or server
+ *
+ * @param spec the specification of the host:port/...
+ * @param server 0 for client, server otherwise
+ *
+ * @return the file descriptor number of the socket or -1 in case of error
+ */
+static int open_tcp(const char *spec, int server)
+{
+ int rc, fd;
+ const char *service, *host, *tail;
+ struct addrinfo hint, *rai, *iai;
+
+ /* scan the uri */
+ tail = strchrnul(spec, '/');
+ service = strchr(spec, ':');
+ if (service == NULL || tail < service) {
+ errno = EINVAL;
+ return -1;
+ }
+ host = strndupa(spec, service++ - spec);
+ service = strndupa(service, tail - service);
+
+ /* get addr */
+ memset(&hint, 0, sizeof hint);
+ hint.ai_family = AF_INET;
+ hint.ai_socktype = SOCK_STREAM;
+ rc = getaddrinfo(host, service, &hint, &rai);
+ if (rc != 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* get the socket */
+ iai = rai;
+ while (iai != NULL) {
+ fd = socket(iai->ai_family, iai->ai_socktype, iai->ai_protocol);
+ if (fd >= 0) {
+ if (server) {
+ rc = bind(fd, iai->ai_addr, iai->ai_addrlen);
+ } else {
+ rc = connect(fd, iai->ai_addr, iai->ai_addrlen);
+ }
+ if (rc == 0) {
+ freeaddrinfo(rai);
+ return fd;
+ }
+ close(fd);
+ }
+ iai = iai->ai_next;
+ }
+ freeaddrinfo(rai);
+ return -1;
+}
+
+/**
+ * open a systemd socket for server
+ *
+ * @param spec the specification of the systemd name
+ *
+ * @return the file descriptor number of the socket or -1 in case of error
+ */
+static int open_systemd(const char *spec)
+{
+#if defined(WITH_SYSTEMD_ACTIVATION)
+ char **names;
+ int fd = -1;
+ int c = sd_listen_fds_with_names(0, &names);
+ if (c < 0)
+ errno = -c;
+ else if (names) {
+ for (c = 0 ; names[c] ; c++) {
+ if (!strcmp(names[c], spec))
+ fd = SD_LISTEN_FDS_START + c;
+ free(names[c]);
+ }
+ free(names);
+ }
+ if (fd < 0 && '0' <= *spec && *spec <= '9')
+ fd = SD_LISTEN_FDS_START + atoi(spec);
+ return fd;
+#else
+ errno = EAFNOSUPPORT;
+ return -1;
+#endif
+}
+
+/******************************************************************************/
+
+/**
+ * Get the entry of the uri by searching to its prefix
+ *
+ * @param uri the searched uri
+ * @param offset where to store the prefix length
+ *
+ * @return the found entry or the default one
+ */
+static struct entry *get_entry(const char *uri, int *offset)
+{
+ int l, i = (int)(sizeof entries / sizeof * entries);
+
+ for (;;) {
+ if (!i) {
+ l = 0;
+ break;
+ }
+ i--;
+ l = (int)strlen(entries[i].prefix);
+ if (!strncmp(uri, entries[i].prefix, l))
+ break;
+ }
+
+ *offset = l;
+ return &entries[i];
+}
+
+/**
+ * open socket for client or server
+ *
+ * @param uri the specification of the socket
+ * @param server 0 for client, server otherwise
+ *
+ * @return the file descriptor number of the socket or -1 in case of error
+ */
+int socket_open(const char *uri, int server)
+{
+ int fd, rc, offset;
+ struct entry *e;
+
+ /* search for the entry */
+ e = get_entry(uri, &offset);
+
+ /* get the names */
+ uri += offset;
+
+ /* open the socket */
+ switch (e->type) {
+ case Type_Unix:
+ fd = open_unix(uri, server);
+ break;
+ case Type_Inet:
+ fd = open_tcp(uri, server);
+ break;
+ case Type_Systemd:
+ if (server)
+ fd = open_systemd(uri);
+ else {
+ errno = EINVAL;
+ fd = -1;
+ }
+ break;
+ default:
+ errno = EAFNOSUPPORT;
+ fd = -1;
+ break;
+ }
+ if (fd < 0)
+ return -1;
+
+ /* set it up */
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ fcntl(fd, F_SETFL, O_NONBLOCK);
+ if (server) {
+ if (!e->noreuseaddr) {
+ rc = 1;
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &rc, sizeof rc);
+ }
+ if (!e->nolisten)
+ listen(fd, BACKLOG);
+ }
+ return fd;
+}
+
diff --git a/src/socket.h b/src/socket.h
new file mode 100644
index 0000000..b5989e1
--- /dev/null
+++ b/src/socket.h
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+extern int socket_open(const char *uri, int server);
+
diff --git a/src/test-lib-compat.c b/src/test-lib-compat.c
new file mode 100644
index 0000000..627c208
--- /dev/null
+++ b/src/test-lib-compat.c
@@ -0,0 +1,256 @@
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+
+#include <cynara/cynara-admin.h>
+#include <cynara/cynara-client-async.h>
+#include <cynara/cynara-client.h>
+
+#define STD 0
+#define TEST 1
+#define CACHE 2
+
+#define STRFY(x) #x
+#define CKEX(x) ckex((x),STD,__LINE__,STRFY(x))
+#define TEEX(x) ckex((x),TEST,__LINE__,STRFY(x))
+#define CAEX(x) ckex((x),CACHE,__LINE__,STRFY(x))
+
+struct cynara_admin *admin;
+struct cynara_async_configuration *aconf;
+struct cynara_async *aclient;
+struct cynara_configuration *conf;
+struct cynara *client;
+char buffer[4000];
+char *str[40];
+int nstr;
+int pollfd;
+
+#define BUCKET "BUCK"
+
+void ckex(int rc, int type, int line, const char *x)
+{
+ int err = 1;
+ switch(type) {
+ case STD:
+ err = (rc != CYNARA_API_SUCCESS);
+ break;
+ case TEST:
+ err = (rc != CYNARA_API_ACCESS_DENIED
+ && rc != CYNARA_API_ACCESS_ALLOWED
+ && rc != CYNARA_API_ACCESS_NOT_RESOLVED);
+ break;
+ case CACHE:
+ err = (rc != CYNARA_API_ACCESS_DENIED
+ && rc != CYNARA_API_ACCESS_ALLOWED
+ && rc != CYNARA_API_ACCESS_NOT_RESOLVED
+ && rc != CYNARA_API_CACHE_MISS);
+ break;
+ }
+ if (err) {
+ char buffer[200];
+ cynara_strerror(rc, buffer, 200);
+ printf("ERROR(%d) %s by %s line %d\n", rc, buffer, x, line);
+ exit(1);
+ }
+ printf("SUCCESS %s\n", x);
+}
+
+int is(const char *first, const char *second, int mincount)
+{
+ return nstr >= mincount + 2
+ && !strcmp(first, str[0])
+ && !strcmp(second, str[1]);
+}
+
+void adm_list(char *cli, char *usr, char *perm)
+{
+ int i;
+ struct cynara_admin_policy **policies;
+ CKEX(cynara_admin_list_policies(admin, BUCKET, cli, usr, perm, &policies));
+ i = 0;
+ while(policies[i]) {
+ printf("%s %s %s %s %d %s\n",
+ policies[i]->bucket,
+ policies[i]->client,
+ policies[i]->user,
+ policies[i]->privilege,
+ policies[i]->result,
+ policies[i]->result_extra ?: "");
+ free(policies[i]->bucket);
+ free(policies[i]->client);
+ free(policies[i]->user);
+ free(policies[i]->privilege);
+ free(policies[i]->result_extra);
+ free(policies[i]);
+ i++;
+ }
+ free(policies);
+}
+
+void adm_check(char *cli, char *usr, char *perm)
+{
+ char *rs;
+ int ri;
+
+ CKEX(cynara_admin_check(admin, BUCKET, 1, cli, usr, perm, &ri, &rs));
+ printf("got %d %s \n", ri, rs ?: "NULL");
+}
+
+void adm_set()
+{
+ struct cynara_admin_policy **policies, *p;
+ int n, i;
+
+ n = (nstr - 2) / 4;
+ policies = malloc((1 + n) * sizeof *policies + n * sizeof **policies);
+ policies[n] = NULL;
+ p = (struct cynara_admin_policy*)(&policies[n + 1]);
+ for (i = 0 ; i < n ; i++, p++) {
+ policies[i] = p;
+ p->bucket = BUCKET;
+ p->client = str[2 + i * 4 + 0];
+ p->user = str[2 + i * 4 + 1];
+ p->privilege = str[2 + i * 4 + 2];
+ p->result = atoi(str[2 + i * 4 + 3]);
+ p->result_extra = NULL;
+ }
+ CKEX(cynara_admin_set_policies(admin, (const struct cynara_admin_policy *const *)policies));
+ free(policies);
+}
+
+void adm_erase(char *cli, char *usr, char *perm)
+{
+ CKEX(cynara_admin_erase(admin, BUCKET, 1, cli, usr, perm));
+}
+
+void adm_desc()
+{
+ int i;
+ struct cynara_admin_policy_descr **d;
+ CKEX(cynara_admin_list_policies_descriptions(admin, &d));
+ i = 0;
+ while(d[i]) {
+ printf("desc[%d] %d -> %s\n", i, d[i]->result, d[i]->name);
+ free(d[i]->name);
+ free(d[i]);
+ i++;
+ }
+ free(d);
+}
+
+void asy_cache(char *cli, char *ses, char *usr, char *perm)
+{
+ CAEX(cynara_async_check_cache(aclient, cli, ses, usr, perm));
+}
+
+void asyncb(cynara_check_id check_id, cynara_async_call_cause cause,
+ int response, void *user_response_data)
+{
+ printf("RECEIVE %d %d\n", cause, response);
+}
+
+void asy_check(char *cli, char *ses, char *usr, char *perm, int simple)
+{
+ if (simple)
+ CKEX(cynara_async_create_simple_request(aclient, cli, ses, usr, perm, NULL, asyncb, NULL));
+ else
+ CKEX(cynara_async_create_request(aclient, cli, ses, usr, perm, NULL, asyncb, NULL));
+}
+
+void syn_check(char *cli, char *ses, char *usr, char *perm, int simple)
+{
+ if (simple)
+ TEEX(cynara_simple_check(client, cli, ses, usr, perm));
+ else
+ TEEX(cynara_check(client, cli, ses, usr, perm));
+}
+
+void asyncstscb(int old_fd, int new_fd, cynara_async_status status, void *data)
+{
+ struct epoll_event ev;
+
+ ev.data.fd = new_fd;
+ ev.events = (status == CYNARA_STATUS_FOR_RW ? EPOLLOUT : 0)|EPOLLIN;
+ if (old_fd == new_fd) {
+ if (new_fd != -1)
+ epoll_ctl(pollfd, EPOLL_CTL_MOD, new_fd, &ev);
+ } else {
+ if (old_fd != -1)
+ epoll_ctl(pollfd, EPOLL_CTL_DEL, old_fd, &ev);
+ if (new_fd != -1)
+ epoll_ctl(pollfd, EPOLL_CTL_ADD, new_fd, &ev);
+ }
+}
+
+int main(int ac, char **av)
+{
+ struct epoll_event ev;
+
+ pollfd = epoll_create(10);
+ ev.data.fd = 0;
+ ev.events = EPOLLIN;
+ epoll_ctl(pollfd, EPOLL_CTL_ADD, 0, &ev);
+
+ CKEX(cynara_admin_initialize(&admin));
+
+ CKEX(cynara_async_configuration_create(&aconf));
+ CKEX(cynara_async_configuration_set_cache_size(aconf, 1000));
+ CKEX(cynara_async_initialize(&aclient, aconf, asyncstscb, NULL));
+ cynara_async_configuration_destroy(aconf);
+
+ CKEX(cynara_configuration_create(&conf));
+ CKEX(cynara_configuration_set_cache_size(conf, 1000));
+ CKEX(cynara_initialize(&client, conf));
+ cynara_configuration_destroy(conf);
+
+ for(;;) {
+ epoll_wait(pollfd, &ev, 1, -1);
+
+ if (ev.data.fd) {
+ cynara_async_process(aclient);
+ continue;
+ }
+
+ if (!fgets(buffer, sizeof buffer, stdin))
+ break;
+
+ str[nstr = 0] = strtok(buffer, " \t\n");
+ while(str[nstr])
+ str[++nstr] = strtok(NULL, " \t\n");
+
+ if (is("admin", "listall", 0))
+ adm_list("#", "#", "#");
+ else if (is("admin", "list", 3))
+ adm_list(str[2], str[3], str[4]);
+ else if (is("admin", "check", 3))
+ adm_check(str[2], str[3], str[4]);
+ else if (is("admin", "set", 4))
+ adm_set();
+ else if (is("admin", "erase", 3))
+ adm_erase(str[2], str[3], str[4]);
+ else if (is("admin", "desc", 0))
+ adm_desc();
+ else if (is("async", "cache", 4))
+ asy_cache(str[2], str[3], str[4], str[5]);
+ else if (is("async", "check", 4))
+ asy_check(str[2], str[3], str[4], str[5], 0);
+ else if (is("async", "test", 4))
+ asy_check(str[2], str[3], str[4], str[5], 1);
+ else if (is("sync", "check", 4))
+ syn_check(str[2], str[3], str[4], str[5], 0);
+ else if (is("sync", "test", 4))
+ syn_check(str[2], str[3], str[4], str[5], 1);
+ else if (nstr > 0 && !strcmp(str[0], "exit"))
+ break;
+ else if (nstr > 0 && str[0][0] != '#')
+ printf("ERROR bad input\n");
+ }
+
+ cynara_finish(client);
+ cynara_async_finish(aclient);
+ cynara_admin_finish(admin);
+}
+