]> arthur.barton.de Git - netdata.git/blobdiff - src/registry.c
make sure disconnected_time is updated before decrementing senders
[netdata.git] / src / registry.c
index 6fbaadee83ef26c3b49228de9a8ec08302a86328..ed9be984881d770d29c873285207cb2f076ca8f8 100644 (file)
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-// gcc -O3 -Wall -Wextra -I ../src/ -I ../ -o registry ../src/registry.c ../src/dictionary.o ../src/log.o ../src/avl.o ../src/common.o -pthread -luuid -DHAVE_CONFIG_H
-
-#include <uuid/uuid.h>
-#include <inttypes.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "log.h"
 #include "common.h"
-#include "dictionary.h"
 
-struct machine {
-       char guid[36 + 1];
+#include "registry_internals.h"
 
-       time_t first_t;
-       time_t last_t;
-       size_t usages;
-       
-       DICTIONARY *urls;
-};
-typedef struct machine MACHINE;
+#define REGISTRY_STATUS_OK "ok"
+#define REGISTRY_STATUS_FAILED "failed"
+#define REGISTRY_STATUS_DISABLED "disabled"
 
+// ----------------------------------------------------------------------------
+// REGISTRY concurrency locking
 
-struct person {
-       char guid[36 + 1];
+static inline void registry_lock(void) {
+    netdata_mutex_lock(&registry.lock);
+}
 
-       time_t first_t;
-       time_t last_t;
-       size_t usages;
+static inline void registry_unlock(void) {
+    netdata_mutex_unlock(&registry.lock);
+}
 
-       DICTIONARY *urls;
-};
-typedef struct person PERSON;
 
+// ----------------------------------------------------------------------------
+// COOKIES
 
-struct url {
-       MACHINE *machine;
-       PERSON *person;
+static void registry_set_cookie(struct web_client *w, const char *guid) {
+    char edate[100];
+    time_t et = now_realtime_sec() + registry.persons_expiration;
+    struct tm etmbuf, *etm = gmtime_r(&et, &etmbuf);
+    strftime(edate, sizeof(edate), "%a, %d %b %Y %H:%M:%S %Z", etm);
 
-       time_t first_t;
-       time_t last_t;
-       size_t usages;
-
-       size_t url_length;
-       char url[];
-};
-typedef struct url URL;
+    snprintfz(w->cookie1, COOKIE_MAX, NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s", guid, edate);
 
+    if(registry.registry_domain && registry.registry_domain[0])
+        snprintfz(w->cookie2, COOKIE_MAX, NETDATA_REGISTRY_COOKIE_NAME "=%s; Domain=%s; Expires=%s", guid, registry.registry_domain, edate);
+}
 
-struct registry {
-       DICTIONARY *persons;
-       DICTIONARY *machines;
-} registry;
+static inline void registry_set_person_cookie(struct web_client *w, REGISTRY_PERSON *p) {
+    registry_set_cookie(w, p->guid);
+}
 
 
 // ----------------------------------------------------------------------------
-// MACHINE
+// JSON GENERATION
 
-MACHINE *registry_machine_load(const char *machine_guid) {
-       (void)machine_guid;
+static inline void registry_json_header(RRDHOST *host, struct web_client *w, const char *action, const char *status) {
+    buffer_flush(w->response.data);
+    w->response.data->contenttype = CT_APPLICATION_JSON;
+    buffer_sprintf(w->response.data, "{\n\t\"action\": \"%s\",\n\t\"status\": \"%s\",\n\t\"hostname\": \"%s\",\n\t\"machine_guid\": \"%s\"",
+            action, status, (host == localhost)?registry.hostname:host->hostname, host->machine_guid);
+}
 
-       return NULL;
+static inline void registry_json_footer(struct web_client *w) {
+    buffer_strcat(w->response.data, "\n}\n");
 }
 
-int registry_machine_save(MACHINE *m) {
-       (void)m;
+static inline int registry_json_disabled(RRDHOST *host, struct web_client *w, const char *action) {
+    registry_json_header(host, w, action, REGISTRY_STATUS_DISABLED);
 
-       return -1;
-}
+    buffer_sprintf(w->response.data, ",\n\t\"registry\": \"%s\"",
+            registry.registry_to_announce);
 
-MACHINE *registry_machine_find(const char *machine_guid) {
-       MACHINE *m = dictionary_get(registry.machines, machine_guid);
-       if(!m) m = registry_machine_load(machine_guid);
-       return m;
+    registry_json_footer(w);
+    return 200;
 }
 
 
-MACHINE *registry_machine_get(const char *machine_guid) {
-       MACHINE *m = registry_machine_find(machine_guid);
-       if(!m) {
-               debug(D_REGISTRY, "Registry: creating new machine '%s'", machine_guid);
+// ----------------------------------------------------------------------------
+// CALLBACKS FOR WALKING THROUGH REGISTRY OBJECTS
+
+// structure used be the callbacks below
+struct registry_json_walk_person_urls_callback {
+    REGISTRY_PERSON *p;
+    REGISTRY_MACHINE *m;
+    struct web_client *w;
+    int count;
+};
 
-               m = calloc(1, sizeof(MACHINE));
-               if(!m) fatal("Registry: cannot allocate memory for new machine '%s'", machine_guid);
+// callback for rendering PERSON_URLs
+static int registry_json_person_url_callback(void *entry, void *data) {
+    REGISTRY_PERSON_URL *pu = (REGISTRY_PERSON_URL *)entry;
+    struct registry_json_walk_person_urls_callback *c = (struct registry_json_walk_person_urls_callback *)data;
+    struct web_client *w = c->w;
 
-               strncpy(m->guid, machine_guid, 36);
+    if(unlikely(c->count++))
+        buffer_strcat(w->response.data, ",");
 
-               dictionary_set(registry.machines, m->guid, m, sizeof(MACHINE));
-               
-               m->first_t = time(NULL);
-               m->usages = 0;
-       }
+    buffer_sprintf(w->response.data, "\n\t\t[ \"%s\", \"%s\", %u000, %u, \"%s\" ]",
+            pu->machine->guid, pu->url->url, pu->last_t, pu->usages, pu->machine_name);
 
-       return m;
+    return 0;
 }
 
+// callback for rendering MACHINE_URLs
+static int registry_json_machine_url_callback(void *entry, void *data) {
+    REGISTRY_MACHINE_URL *mu = (REGISTRY_MACHINE_URL *)entry;
+    struct registry_json_walk_person_urls_callback *c = (struct registry_json_walk_person_urls_callback *)data;
+    struct web_client *w = c->w;
+    REGISTRY_MACHINE *m = c->m;
 
-// ----------------------------------------------------------------------------
-// PERSON
+    if(unlikely(c->count++))
+        buffer_strcat(w->response.data, ",");
 
-PERSON *registry_person_load(const char *person_guid) {
-       (void)person_guid;
+    buffer_sprintf(w->response.data, "\n\t\t[ \"%s\", \"%s\", %u000, %u ]",
+            m->guid, mu->url->url, mu->last_t, mu->usages);
 
-       return NULL;
+    return 1;
 }
 
-int registry_person_save(PERSON *p) {
-       (void)p;
-
-       return -1;
-}
+// ----------------------------------------------------------------------------
 
-PERSON *registry_person_find(const char *person_guid) {
-       PERSON *p = dictionary_get(registry.persons, person_guid);
-       if(!p) p = registry_person_load(person_guid);
-       return p;
-}
+// structure used be the callbacks below
+struct registry_person_url_callback_verify_machine_exists_data {
+    REGISTRY_MACHINE *m;
+    int count;
+};
 
-PERSON *registry_person_get(const char *person_guid) {
-       PERSON *p = NULL;
+static inline int registry_person_url_callback_verify_machine_exists(void *entry, void *data) {
+    struct registry_person_url_callback_verify_machine_exists_data *d = (struct registry_person_url_callback_verify_machine_exists_data *)data;
+    REGISTRY_PERSON_URL *pu = (REGISTRY_PERSON_URL *)entry;
+    REGISTRY_MACHINE *m = d->m;
 
-       if(person_guid && *person_guid)
-               p = registry_person_find(person_guid);
+    if(pu->machine == m)
+        d->count++;
 
-       if(!p) {
-               if(person_guid && *person_guid)
-                       error("Registry: discarding unknown person guid '%s'. Will give a new PERSONID.", person_guid);
+    return 0;
+}
 
-               debug(D_REGISTRY, "Registry: creating new person");
+// ----------------------------------------------------------------------------
+// public HELLO request
 
-               p = calloc(1, sizeof(PERSON));
-               if(!p) fatal("Registry: cannot allocate memory for new person.");
+int registry_request_hello_json(RRDHOST *host, struct web_client *w) {
+    registry_json_header(host, w, "hello", REGISTRY_STATUS_OK);
 
-               uuid_t uuid;
-               if(uuid_generate_time_safe(uuid) == -1)
-                       info("Registry: uuid_generate_time_safe() reports UUID generation is not safe for uniqueness.");
+    buffer_sprintf(w->response.data, ",\n\t\"registry\": \"%s\"",
+            registry.registry_to_announce);
 
-               uuid_unparse_lower(uuid, p->guid);
+    registry_json_footer(w);
+    return 200;
+}
 
-               dictionary_set(registry.persons, p->guid, p, sizeof(PERSON));
+// ----------------------------------------------------------------------------
+//public ACCESS request
 
-               p->first_t = time(NULL);
-               p->usages = 0;
-       }
+#define REGISTRY_VERIFY_COOKIES_GUID "give-me-back-this-cookie-now--please"
 
-       return p;
-}
+// the main method for registering an access
+int registry_request_access_json(RRDHOST *host, struct web_client *w, char *person_guid, char *machine_guid, char *url, char *name, time_t when) {
+    if(unlikely(!registry.enabled))
+        return registry_json_disabled(host, w, "access");
 
+    // ------------------------------------------------------------------------
+    // verify the browser supports cookies
 
-// ----------------------------------------------------------------------------
-// URL
+    if(registry.verify_cookies_redirects > 0 && !person_guid[0]) {
+        buffer_flush(w->response.data);
+        registry_set_cookie(w, REGISTRY_VERIFY_COOKIES_GUID);
+        w->response.data->contenttype = CT_APPLICATION_JSON;
+        buffer_sprintf(w->response.data, "{ \"status\": \"redirect\", \"registry\": \"%s\" }", registry.registry_to_announce);
+        return 200;
+    }
 
-URL *registry_url_update(PERSON *p, MACHINE *m, const char *url) {
-       URL *pu = dictionary_get(p->urls, url);
-       URL *mu = dictionary_get(m->urls, url);
-       URL *u = NULL;
+    if(unlikely(person_guid[0] && !strcmp(person_guid, REGISTRY_VERIFY_COOKIES_GUID)))
+        person_guid[0] = '\0';
 
-       if(pu != mu || pu == NULL || mu == NULL)
-               error("Registry: person/machine discrepancy on url '%s', for person '%s' and machine '%s'", url, p->guid, m->guid);
-       else
-               u = pu;
+    // ------------------------------------------------------------------------
 
-       if(!u) {
-               size_t len = strlen(url);
+    registry_lock();
 
-               URL *u = calloc(1, sizeof(URL) + len);
-               if(!u) fatal("Registry: cannot allocate memory for person '%s', machine '%s', url '%s'.", p->guid, m->guid, url);
+    REGISTRY_PERSON *p = registry_request_access(person_guid, machine_guid, url, name, when);
+    if(!p) {
+        registry_json_header(host, w, "access", REGISTRY_STATUS_FAILED);
+        registry_json_footer(w);
+        registry_unlock();
+        return 412;
+    }
 
-               strcpy(u->url, url);
-               u->url_length = len;
-               u->person = p;
-               u->machine = m;
-               u->first_t = time(NULL);
-               u->usages = 1;
+    // set the cookie
+    registry_set_person_cookie(w, p);
 
-               dictionary_set(p->urls, url, u, sizeof(URL) + u->url_length);
-               dictionary_set(m->urls, url, u, sizeof(URL) + u->url_length);
-               if(pu) free(pu);
-               if(mu) free(mu);
-       }
-       else
-               u->usages++;
+    // generate the response
+    registry_json_header(host, w, "access", REGISTRY_STATUS_OK);
 
-       p->usages++;
-       m->usages++;
-       p->last_t = m->last_t = u->last_t = time(NULL);
+    buffer_sprintf(w->response.data, ",\n\t\"person_guid\": \"%s\",\n\t\"urls\": [", p->guid);
+    struct registry_json_walk_person_urls_callback c = { p, NULL, w, 0 };
+    avl_traverse(&p->person_urls, registry_json_person_url_callback, &c);
+    buffer_strcat(w->response.data, "\n\t]\n");
 
-       return u;
+    registry_json_footer(w);
+    registry_unlock();
+    return 200;
 }
 
-
 // ----------------------------------------------------------------------------
-// REGISTRY
-
-int registry_save(void) {
-       return -1;
+// public DELETE request
+
+// the main method for deleting a URL from a person
+int registry_request_delete_json(RRDHOST *host, struct web_client *w, char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when) {
+    if(!registry.enabled)
+        return registry_json_disabled(host, w, "delete");
+
+    registry_lock();
+
+    REGISTRY_PERSON *p = registry_request_delete(person_guid, machine_guid, url, delete_url, when);
+    if(!p) {
+        registry_json_header(host, w, "delete", REGISTRY_STATUS_FAILED);
+        registry_json_footer(w);
+        registry_unlock();
+        return 412;
+    }
+
+    // generate the response
+    registry_json_header(host, w, "delete", REGISTRY_STATUS_OK);
+    registry_json_footer(w);
+    registry_unlock();
+    return 200;
 }
 
-char *registry_request(const char *person_guid, const char *machine_guid, const char *url) {
-       PERSON *p = NULL;
-       MACHINE *m = NULL;
-
-       // --- PERSON ---
-       p = registry_person_get(person_guid);
-       person_guid = p->guid;
+// ----------------------------------------------------------------------------
+// public SEARCH request
 
-       // --- MACHINE ---
-       m = registry_machine_get(machine_guid);
-       machine_guid = m->guid;
+// the main method for searching the URLs of a netdata
+int registry_request_search_json(RRDHOST *host, struct web_client *w, char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when) {
+    if(!registry.enabled)
+        return registry_json_disabled(host, w, "search");
 
-       // --- URL ---
-       registry_url_update(p, m, url);
+    registry_lock();
 
-       registry_person_save(p);
-       registry_machine_save(m);
-       registry_save();
+    REGISTRY_MACHINE *m = registry_request_machine(person_guid, machine_guid, url, request_machine, when);
+    if(!m) {
+        registry_json_header(host, w, "search", REGISTRY_STATUS_FAILED);
+        registry_json_footer(w);
+        registry_unlock();
+        return 404;
+    }
 
-       return NULL;
-}
+    registry_json_header(host, w, "search", REGISTRY_STATUS_OK);
 
-void registry_init(void) {
-       registry.persons = dictionary_create(DICTIONARY_FLAG_DEFAULT);
-       if(!registry.persons)
-               fatal("Registry: cannot create persons registry");
+    buffer_strcat(w->response.data, ",\n\t\"urls\": [");
+    struct registry_json_walk_person_urls_callback c = { NULL, m, w, 0 };
+    dictionary_get_all(m->machine_urls, registry_json_machine_url_callback, &c);
+    buffer_strcat(w->response.data, "\n\t]\n");
 
-       registry.machines = dictionary_create(DICTIONARY_FLAG_DEFAULT);
-       if(!registry.machines)
-               fatal("Registry: cannot create machines registry");
+    registry_json_footer(w);
+    registry_unlock();
+    return 200;
 }
 
-int main(int argc, char **argv) {
-       (void)argc;
-       (void)argv;
-
-       registry_init();
+// ----------------------------------------------------------------------------
+// SWITCH REQUEST
+
+// the main method for switching user identity
+int registry_request_switch_json(RRDHOST *host, struct web_client *w, char *person_guid, char *machine_guid, char *url, char *new_person_guid, time_t when) {
+    if(!registry.enabled)
+        return registry_json_disabled(host, w, "switch");
+
+    (void)url;
+    (void)when;
+
+    registry_lock();
+
+    REGISTRY_PERSON *op = registry_person_find(person_guid);
+    if(!op) {
+        registry_json_header(host, w, "switch", REGISTRY_STATUS_FAILED);
+        registry_json_footer(w);
+        registry_unlock();
+        return 430;
+    }
+
+    REGISTRY_PERSON *np = registry_person_find(new_person_guid);
+    if(!np) {
+        registry_json_header(host, w, "switch", REGISTRY_STATUS_FAILED);
+        registry_json_footer(w);
+        registry_unlock();
+        return 431;
+    }
+
+    REGISTRY_MACHINE *m = registry_machine_find(machine_guid);
+    if(!m) {
+        registry_json_header(host, w, "switch", REGISTRY_STATUS_FAILED);
+        registry_json_footer(w);
+        registry_unlock();
+        return 432;
+    }
+
+    struct registry_person_url_callback_verify_machine_exists_data data = { m, 0 };
+
+    // verify the old person has access to this machine
+    avl_traverse(&op->person_urls, registry_person_url_callback_verify_machine_exists, &data);
+    if(!data.count) {
+        registry_json_header(host, w, "switch", REGISTRY_STATUS_FAILED);
+        registry_json_footer(w);
+        registry_unlock();
+        return 433;
+    }
+
+    // verify the new person has access to this machine
+    data.count = 0;
+    avl_traverse(&np->person_urls, registry_person_url_callback_verify_machine_exists, &data);
+    if(!data.count) {
+        registry_json_header(host, w, "switch", REGISTRY_STATUS_FAILED);
+        registry_json_footer(w);
+        registry_unlock();
+        return 434;
+    }
+
+    // set the cookie of the new person
+    // the user just switched identity
+    registry_set_person_cookie(w, np);
+
+    // generate the response
+    registry_json_header(host, w, "switch", REGISTRY_STATUS_OK);
+    buffer_sprintf(w->response.data, ",\n\t\"person_guid\": \"%s\"", np->guid);
+    registry_json_footer(w);
+
+    registry_unlock();
+    return 200;
+}
 
-       return 0;
+// ----------------------------------------------------------------------------
+// STATISTICS
+
+void registry_statistics(void) {
+    if(!registry.enabled) return;
+
+    static RRDSET *sts = NULL, *stc = NULL, *stm = NULL;
+
+    if(!sts) sts = rrdset_find_localhost("netdata.registry_sessions");
+    if(!sts) {
+        sts = rrdset_create_localhost("netdata", "registry_sessions", NULL, "registry", NULL
+                                      , "NetData Registry Sessions", "session", 131000, localhost->rrd_update_every
+                                      , RRDSET_TYPE_LINE);
+
+        rrddim_add(sts, "sessions",  NULL,  1, 1, RRD_ALGORITHM_ABSOLUTE);
+    }
+    else rrdset_next(sts);
+
+    rrddim_set(sts, "sessions", registry.usages_count);
+    rrdset_done(sts);
+
+    // ------------------------------------------------------------------------
+
+    if(!stc) stc = rrdset_find_localhost("netdata.registry_entries");
+    if(!stc) {
+        stc = rrdset_create_localhost("netdata", "registry_entries", NULL, "registry", NULL, "NetData Registry Entries"
+                                      , "entries", 131100, localhost->rrd_update_every, RRDSET_TYPE_LINE);
+
+        rrddim_add(stc, "persons",        NULL,  1, 1, RRD_ALGORITHM_ABSOLUTE);
+        rrddim_add(stc, "machines",       NULL,  1, 1, RRD_ALGORITHM_ABSOLUTE);
+        rrddim_add(stc, "urls",           NULL,  1, 1, RRD_ALGORITHM_ABSOLUTE);
+        rrddim_add(stc, "persons_urls",   NULL,  1, 1, RRD_ALGORITHM_ABSOLUTE);
+        rrddim_add(stc, "machines_urls",  NULL,  1, 1, RRD_ALGORITHM_ABSOLUTE);
+    }
+    else rrdset_next(stc);
+
+    rrddim_set(stc, "persons",       registry.persons_count);
+    rrddim_set(stc, "machines",      registry.machines_count);
+    rrddim_set(stc, "urls",          registry.urls_count);
+    rrddim_set(stc, "persons_urls",  registry.persons_urls_count);
+    rrddim_set(stc, "machines_urls", registry.machines_urls_count);
+    rrdset_done(stc);
+
+    // ------------------------------------------------------------------------
+
+    if(!stm) stm = rrdset_find_localhost("netdata.registry_mem");
+    if(!stm) {
+        stm = rrdset_create_localhost("netdata", "registry_mem", NULL, "registry", NULL, "NetData Registry Memory", "KB"
+                                      , 131300, localhost->rrd_update_every, RRDSET_TYPE_STACKED);
+
+        rrddim_add(stm, "persons",        NULL,  1, 1024, RRD_ALGORITHM_ABSOLUTE);
+        rrddim_add(stm, "machines",       NULL,  1, 1024, RRD_ALGORITHM_ABSOLUTE);
+        rrddim_add(stm, "urls",           NULL,  1, 1024, RRD_ALGORITHM_ABSOLUTE);
+        rrddim_add(stm, "persons_urls",   NULL,  1, 1024, RRD_ALGORITHM_ABSOLUTE);
+        rrddim_add(stm, "machines_urls",  NULL,  1, 1024, RRD_ALGORITHM_ABSOLUTE);
+    }
+    else rrdset_next(stm);
+
+    rrddim_set(stm, "persons",       registry.persons_memory + registry.persons_count * sizeof(NAME_VALUE) + sizeof(DICTIONARY));
+    rrddim_set(stm, "machines",      registry.machines_memory + registry.machines_count * sizeof(NAME_VALUE) + sizeof(DICTIONARY));
+    rrddim_set(stm, "urls",          registry.urls_memory);
+    rrddim_set(stm, "persons_urls",  registry.persons_urls_memory);
+    rrddim_set(stm, "machines_urls", registry.machines_urls_memory + registry.machines_count * sizeof(DICTIONARY) + registry.machines_urls_count * sizeof(NAME_VALUE));
+    rrdset_done(stm);
 }