diff options
author | 2018-09-10 12:00:18 +0200 | |
---|---|---|
committer | 2018-09-10 12:00:18 +0200 | |
commit | 11654afcb5753a54a033db12e1ed4a19b3f7c86e (patch) | |
tree | 0d493c80584392eec2c5dc0f1c1c68c9057cf043 /src |
Initial commit
Signed-off-by: Jose Bollo <jose.bollo@iot.bzh>
Diffstat (limited to 'src')
-rw-r--r-- | src/.gitignore | 5 | ||||
-rw-r--r-- | src/CMakeLists.txt | 95 | ||||
-rw-r--r-- | src/Makefile | 26 | ||||
-rw-r--r-- | src/cache.c | 163 | ||||
-rw-r--r-- | src/cache.h | 48 | ||||
-rw-r--r-- | src/cyn.c | 267 | ||||
-rw-r--r-- | src/cyn.h | 113 | ||||
-rw-r--r-- | src/db.c | 661 | ||||
-rw-r--r-- | src/db.h | 102 | ||||
-rw-r--r-- | src/export.map | 9 | ||||
-rw-r--r-- | src/fbuf.c | 279 | ||||
-rw-r--r-- | src/fbuf.h | 111 | ||||
-rw-r--r-- | src/lib-compat.c | 651 | ||||
-rw-r--r-- | src/prot.c | 440 | ||||
-rw-r--r-- | src/prot.h | 85 | ||||
-rw-r--r-- | src/queue.c | 192 | ||||
-rw-r--r-- | src/queue.h | 31 | ||||
-rw-r--r-- | src/rcyn-client.c | 722 | ||||
-rw-r--r-- | src/rcyn-client.h | 149 | ||||
-rw-r--r-- | src/rcyn-protocol.c | 33 | ||||
-rw-r--r-- | src/rcyn-protocol.h | 28 | ||||
-rw-r--r-- | src/rcyn-protocol.txt | 79 | ||||
-rw-r--r-- | src/rcyn-server.c | 669 | ||||
-rw-r--r-- | src/socket.c | 320 | ||||
-rw-r--r-- | src/socket.h | 21 | ||||
-rw-r--r-- | src/test-lib-compat.c | 256 |
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); +} + |