From: Costa Tsaousis Date: Tue, 10 May 2016 01:01:55 +0000 (+0300) Subject: almost operational registry - still needs testing X-Git-Tag: v1.2.0~7^2~39 X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=86d20a49c6bf8b3eab54bbddc227e0a06e8bf267;p=netdata.git almost operational registry - still needs testing --- diff --git a/src/Makefile.am b/src/Makefile.am index ef7d20fd..69d1a2fe 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -59,6 +59,7 @@ netdata_SOURCES = \ sys_kernel_mm_ksm.c \ sys_fs_cgroup.c \ procfile.c procfile.h \ + registry.c registry.h \ rrd.c rrd.h \ rrd2json.c rrd2json.h \ storage_number.c storage_number.h \ diff --git a/src/registry.c b/src/registry.c index 0944585c..6ab9e6a5 100644 --- a/src/registry.c +++ b/src/registry.c @@ -19,6 +19,9 @@ #include "dictionary.h" #include "appconfig.h" +#include "web_client.h" +#include "registry.h" + #define REGISTRY_URL_FLAGS_DEFAULT 0x00 #define REGISTRY_URL_FLAGS_EXPIRED 0x01 @@ -28,6 +31,9 @@ // COMMON structures struct registry { + int enabled; + + // counters / statistics unsigned long long persons_count; unsigned long long machines_count; unsigned long long usages_count; @@ -35,26 +41,38 @@ struct registry { unsigned long long persons_urls_count; unsigned long long machines_urls_count; + // files char *pathname; char *db_filename; char *log_filename; FILE *registry_log_fp; + // the database DICTIONARY *persons; // dictionary of PERSON *, with key the PERSON.guid DICTIONARY *machines; // dictionary of MACHINE *, with key the MACHINE.guid DICTIONARY *urls; // dictionary of URL *, with key the URL.url + // concurrency locking + // we keep different locks for different things + // so that many tasks can be completed in parallel + pthread_mutex_t persons_lock; + pthread_mutex_t machines_lock; + pthread_mutex_t urls_lock; + pthread_mutex_t person_urls_lock; + pthread_mutex_t machine_urls_lock; + pthread_mutex_t log_lock; } registry; - // ---------------------------------------------------------------------------- // URL structures // Save memory by de-duplicating URLs +// so instead of storing URLs all over the place +// we store them here and we keep pointers elsewhere struct url { - uint32_t links; - uint16_t len; - char url[1]; + uint32_t links; // the number of links to this URL - when none is left, we free it + uint16_t len; // the length of the URL in bytes + char url[1]; // the URL - dynamically allocated to more size }; typedef struct url URL; @@ -76,7 +94,7 @@ typedef struct machine_url MACHINE_URL; // A machine struct machine { - char guid[36 + 1]; + char guid[36 + 1]; // the GUID DICTIONARY *urls; // MACHINE_URL * @@ -92,21 +110,24 @@ typedef struct machine MACHINE; // for each PERSON-URL pair we keep this struct person_url { - URL *url; // de-duplicated URL - MACHINE *machine; + URL *url; // de-duplicated URL + MACHINE *machine; // link the MACHINE of this URL uint8_t flags; uint32_t first_t; // the first time we saw this uint32_t last_t; // the last time we saw this uint32_t usages; // how many times this has been accessed + + char name[1]; // the name of the URL, as known by the user + // dynamically allocated to fit properly }; typedef struct person_url PERSON_URL; // A person struct person { - char guid[36 + 1]; + char guid[36 + 1]; // the person GUID - DICTIONARY *urls; // PERSON_URL * + DICTIONARY *urls; // dictionary of PERSON_URL * uint32_t first_t; // the first time we saw this uint32_t last_t; // the last time we saw this @@ -114,54 +135,191 @@ struct person { }; typedef struct person PERSON; -extern PERSON *registry_request(const char *person_guid, const char *machine_guid, const char *url, time_t when); + +// ---------------------------------------------------------------------------- +// REGISTRY concurrency locking + +static inline void registry_persons_lock(void) { + pthread_mutex_lock(®istry.persons_lock); +} + +static inline void registry_persons_unlock(void) { + pthread_mutex_unlock(®istry.persons_lock); +} + +static inline void registry_machines_lock(void) { + pthread_mutex_lock(®istry.machines_lock); +} + +static inline void registry_machines_unlock(void) { + pthread_mutex_unlock(®istry.machines_lock); +} + +static inline void registry_urls_lock(void) { + pthread_mutex_lock(®istry.urls_lock); +} + +static inline void registry_urls_unlock(void) { + pthread_mutex_unlock(®istry.urls_lock); +} + +// ideally, we should not lock the whole registry for +// updating a person's urls. +// however, to save the memory required for keeping a +// mutex (40 bytes) per person, we do... +static inline void registry_person_urls_lock(PERSON *p) { + (void)p; + pthread_mutex_lock(®istry.person_urls_lock); +} + +static inline void registry_person_urls_unlock(PERSON *p) { + (void)p; + pthread_mutex_unlock(®istry.person_urls_lock); +} + +// ideally, we should not lock the whole registry for +// updating a machine's urls. +// however, to save the memory required for keeping a +// mutex (40 bytes) per machine, we do... +static inline void registry_machine_urls_lock(MACHINE *m) { + (void)m; + pthread_mutex_lock(®istry.machine_urls_lock); +} + +static inline void registry_machine_urls_unlock(MACHINE *m) { + (void)m; + pthread_mutex_unlock(®istry.machine_urls_lock); +} + +static inline void registry_log_lock(void) { + pthread_mutex_lock(®istry.log_lock); +} + +static inline void registry_log_unlock(void) { + pthread_mutex_unlock(®istry.log_lock); +} + + +// ---------------------------------------------------------------------------- +// common functions + +// parse a GUID and re-generated to be always lower case +// this is used as a protection against the variations of GUIDs +static inline int registry_regenerate_guid(const char *guid, char *result) { + uuid_t uuid; + if(unlikely(uuid_parse(guid, uuid) == -1)) { + info("Registry: GUID '%s' is not a valid GUID.", guid); + return -1; + } + else { + uuid_unparse_lower(uuid, result); + +#ifdef NETDATA_INTERNAL_CHECKS + if(strcmp(guid, result)) + info("Registry: source GUID '%s' and re-generated GUID '%s' differ!", guid, result); +#endif /* NETDATA_INTERNAL_CHECKS */ + } + + return 0; +} + +// make sure the names of the machines / URLs do not contain any tabs +// (which are used as our separator in the database files) +// and are properly trimmed (before and after) +static inline char *registry_fix_machine_name(char *name, size_t *len) { + char *s = name?name:""; + + // skip leading spaces + while(*s && isspace(*s)) s++; + + // make sure all spaces are a SPACE + char *t = s; + while(*t) { + if(unlikely(isspace(*t))) + *t = ' '; + + t++; + } + + // remove trailing spaces + while(--t >= s) { + if(*t == ' ') + *t = '\0'; + else + break; + } + t++; + + if(likely(len)) + *len = (t - s); + + return s; +} + +static inline char *registry_fix_url(char *url, size_t *len) { + return registry_fix_machine_name(url, len); +} + + +// ---------------------------------------------------------------------------- +// forward definition of functions + +extern PERSON *registry_request_access(char *person_guid, char *machine_guid, char *url, char *name, time_t when); +extern PERSON *registry_request_delete(char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when); + // ---------------------------------------------------------------------------- // URL -static inline URL *registry_url_allocate(const char *url) { - size_t len = strlen(url); +static inline URL *registry_url_allocate_nolock(const char *url, size_t urllen) { + debug(D_REGISTRY, "Registry: registry_url_allocate_nolock('%s'): allocating %zu bytes", url, sizeof(URL) + urllen); + URL *u = malloc(sizeof(URL) + urllen); + if(!u) fatal("Cannot allocate %zu bytes for URL '%s'", sizeof(URL) + urllen); - debug(D_REGISTRY, "Registry: registry_url_allocate('%s'): allocating %zu bytes", url, sizeof(URL) + len); - URL *u = malloc(sizeof(URL) + len); - if(!u) fatal("Cannot allocate %zu bytes for URL '%s'", sizeof(URL) + len); + // a simple strcpy() should do the job + // but I prefer to be safe, since the caller specified urllen + strncpy(u->url, url, urllen); + u->url[urllen] = '\0'; - strcpy(u->url, url); - u->len = len; + u->len = urllen; u->links = 0; - debug(D_REGISTRY, "Registry: registry_url_allocate('%s'): indexing it", url); + debug(D_REGISTRY, "Registry: registry_url_allocate_nolock('%s'): indexing it", url); dictionary_set(registry.urls, u->url, u, sizeof(URL)); return u; } -static inline URL *registry_url_get(const char *url) { +static inline URL *registry_url_get(const char *url, size_t urllen) { debug(D_REGISTRY, "Registry: registry_url_get('%s')", url); + registry_urls_lock(); + URL *u = dictionary_get(registry.urls, url); if(!u) { - u = registry_url_allocate(url); + u = registry_url_allocate_nolock(url, urllen); registry.urls_count++; } + registry_urls_unlock(); + return u; } -static inline void registry_url_link(URL *u) { +static inline void registry_url_link_nolock(URL *u) { u->links++; - debug(D_REGISTRY, "Registry: registry_url_unlink('%s'): This URL has now %u links", u->url, u->links); + debug(D_REGISTRY, "Registry: registry_url_link_nolock('%s'): URL has now %u links", u->url, u->links); } -static inline void registry_url_unlink(URL *u) { +static inline void registry_url_unlink_nolock(URL *u) { u->links--; if(!u->links) { - debug(D_REGISTRY, "Registry: registry_url_unlink('%s'): No more links for this URL", u->url); + debug(D_REGISTRY, "Registry: registry_url_unlink_nolock('%s'): No more links for this URL", u->url); dictionary_del(registry.urls, u->url); free(u); } else - debug(D_REGISTRY, "Registry: registry_url_unlink('%s'): This URL has %u links left", u->url, u->links); + debug(D_REGISTRY, "Registry: registry_url_unlink_nolock('%s'): URL has %u links left", u->url, u->links); } @@ -189,7 +347,7 @@ static inline MACHINE_URL *registry_machine_url_allocate(MACHINE *m, URL *u, tim debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s'): indexing URL in machine", m->guid, u->url); dictionary_set(m->urls, u->url, mu, sizeof(MACHINE_URL)); - registry_url_link(u); + registry_url_link_nolock(u); return mu; } @@ -219,18 +377,14 @@ static inline MACHINE *registry_machine_allocate(const char *machine_guid, time_ static inline MACHINE *registry_machine_get(const char *machine_guid, time_t when) { MACHINE *m = NULL; + registry_machines_lock(); + if(likely(machine_guid && *machine_guid)) { // validate it is a GUID - uuid_t uuid; - if(uuid_parse(machine_guid, uuid) == -1) { + char buf[36 + 1]; + if(unlikely(registry_regenerate_guid(machine_guid, buf) == -1)) info("Registry: machine guid '%s' is not a valid guid. Ignoring it.", machine_guid); - } else { - char buf[36 + 1]; - uuid_unparse_lower(uuid, buf); - if(strcmp(machine_guid, buf)) - info("Registry: machine guid '%s' and re-generated '%s' differ!", machine_guid, buf); - machine_guid = buf; m = registry_machine_find(machine_guid); if(!m) { @@ -240,6 +394,8 @@ static inline MACHINE *registry_machine_get(const char *machine_guid, time_t whe } } + registry_machines_unlock(); + return m; } @@ -252,10 +408,15 @@ static inline PERSON *registry_person_find(const char *person_guid) { return dictionary_get(registry.persons, person_guid); } -static inline PERSON_URL *registry_person_url_allocate(PERSON *p, MACHINE *m, URL *u, time_t when) { - debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): allocating %zu bytes", p->guid, m->guid, u->url, sizeof(PERSON_URL)); - PERSON_URL *pu = malloc(sizeof(PERSON_URL)); - if(!pu) fatal("registry_person_link_to_url('%s', '%s', '%s'): cannot allocate %zu bytes.", p->guid, m->guid, u->url, sizeof(PERSON_URL)); +static inline PERSON_URL *registry_person_url_allocate(PERSON *p, MACHINE *m, URL *u, char *name, size_t namelen, time_t when) { + debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): allocating %zu bytes", p->guid, m->guid, u->url, sizeof(PERSON_URL) + namelen); + PERSON_URL *pu = malloc(sizeof(PERSON_URL) + namelen); + if(!pu) fatal("registry_person_link_to_url('%s', '%s', '%s'): cannot allocate %zu bytes.", p->guid, m->guid, u->url, sizeof(PERSON_URL) + namelen); + + // a simple strcpy() should do the job + // but I prefer to be safe, since the caller specified urllen + strncpy(pu->name, name, namelen); + pu->name[namelen] = '\0'; pu->machine = m; pu->first_t = pu->last_t = when; @@ -265,7 +426,7 @@ static inline PERSON_URL *registry_person_url_allocate(PERSON *p, MACHINE *m, UR debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): indexing URL in person", p->guid, m->guid, u->url); dictionary_set(p->urls, u->url, pu, sizeof(PERSON_URL)); - registry_url_link(u); + registry_url_link_nolock(u); return pu; } @@ -318,18 +479,14 @@ static inline PERSON *registry_person_allocate(const char *person_guid, time_t w static inline PERSON *registry_person_get(const char *person_guid, time_t when) { PERSON *p = NULL; + registry_persons_lock(); + if(person_guid && *person_guid) { + char buf[36 + 1]; // validate it is a GUID - uuid_t uuid; - if(uuid_parse(person_guid, uuid) == -1) { + if(unlikely(registry_regenerate_guid(person_guid, buf) == -1)) info("Registry: person guid '%s' is not a valid guid. Ignoring it.", person_guid); - } else { - char buf[36 + 1]; - uuid_unparse_lower(uuid, buf); - if(strcmp(person_guid, buf)) - info("Registry: person guid '%s' and re-generated '%s' differ!", person_guid, buf); - person_guid = buf; p = registry_person_find(person_guid); if(!p) person_guid = NULL; @@ -341,19 +498,23 @@ static inline PERSON *registry_person_get(const char *person_guid, time_t when) registry.persons_count++; } + registry_persons_unlock(); + return p; } // ---------------------------------------------------------------------------- // LINKING OF OBJECTS -static inline PERSON_URL *registry_person_link_to_url(PERSON *p, MACHINE *m, URL *u, time_t when) { +static inline PERSON_URL *registry_person_link_to_url(PERSON *p, MACHINE *m, URL *u, char *name, size_t namelen, time_t when) { debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): searching for URL in person", p->guid, m->guid, u->url); + registry_person_urls_lock(p); + PERSON_URL *pu = dictionary_get(p->urls, u->url); if(!pu) { debug(D_REGISTRY, "registry_person_link_to_url('%s', '%s', '%s'): not found", p->guid, m->guid, u->url); - pu = registry_person_url_allocate(p, m, u, when); + pu = registry_person_url_allocate(p, m, u, name, namelen, when); registry.persons_urls_count++; } else { @@ -375,8 +536,12 @@ static inline PERSON_URL *registry_person_link_to_url(PERSON *p, MACHINE *m, URL p->usages++; p->last_t = when; - if(pu->flags & REGISTRY_URL_FLAGS_EXPIRED) - info("registry_person_link_to_url('%s', '%s', '%s'): accessing an expired URL.", p->guid, m->guid, u->url); + if(pu->flags & REGISTRY_URL_FLAGS_EXPIRED) { + info("registry_person_link_to_url('%s', '%s', '%s'): accessing an expired URL. Re-enabling URL.", p->guid, m->guid, u->url); + pu->flags &= ~REGISTRY_URL_FLAGS_EXPIRED; + } + + registry_person_urls_unlock(p); return pu; } @@ -384,6 +549,8 @@ static inline PERSON_URL *registry_person_link_to_url(PERSON *p, MACHINE *m, URL static inline MACHINE_URL *registry_machine_link_to_url(PERSON *p, MACHINE *m, URL *u, time_t when) { debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): searching for URL in machine", p->guid, m->guid, u->url); + registry_machine_urls_lock(m); + MACHINE_URL *mu = dictionary_get(m->urls, u->url); if(!mu) { debug(D_REGISTRY, "registry_machine_link_to_url('%s', '%s', '%s'): not found", p->guid, m->guid, u->url); @@ -401,8 +568,12 @@ static inline MACHINE_URL *registry_machine_link_to_url(PERSON *p, MACHINE *m, U m->usages++; m->last_t = when; - if(mu->flags & REGISTRY_URL_FLAGS_EXPIRED) - info("registry_person_link_to_url('%s', '%s', '%s'): accessing an expired URL.", p->guid, m->guid, u->url); + if(mu->flags & REGISTRY_URL_FLAGS_EXPIRED) { + info("registry_machine_link_to_url('%s', '%s', '%s'): accessing an expired URL.", p->guid, m->guid, u->url); + mu->flags &= ~REGISTRY_URL_FLAGS_EXPIRED; + } + + registry_machine_urls_unlock(m); return mu; } @@ -410,18 +581,26 @@ static inline MACHINE_URL *registry_machine_link_to_url(PERSON *p, MACHINE *m, U // ---------------------------------------------------------------------------- // REGISTRY LOG LOAD/SAVE -static inline void registry_log(const char action, PERSON *p, MACHINE *m, URL *u) { - if(likely(registry.registry_log_fp)) - fprintf(registry.registry_log_fp, "%c\t%08x\t%s\t%s\t%s\n", +static inline void registry_log(const char action, PERSON *p, MACHINE *m, URL *u, char *name) { + if(likely(registry.registry_log_fp)) { + // we lock only if the file is open + // to allow replaying the log at registry_log_load() + registry_log_lock(); + + fprintf(registry.registry_log_fp, "%c\t%08x\t%s\t%s\t%s\t%s\n", action, p->last_t, p->guid, m->guid, + name, u->url ); + + registry_log_unlock(); + } } -void registry_log_open(void) { +void registry_log_open_nolock(void) { registry.registry_log_fp = fopen(registry.log_filename, "a"); if(registry.registry_log_fp) { @@ -430,85 +609,342 @@ void registry_log_open(void) { } } -void registry_log_close(void) { +void registry_log_close_nolock(void) { if(registry.registry_log_fp) fclose(registry.registry_log_fp); registry.registry_log_fp = NULL; } -void registry_log_recreate(void) { +void registry_log_recreate_nolock(void) { if(registry.registry_log_fp != NULL) { - fclose(registry.registry_log_fp); + registry_log_close_nolock(); registry.registry_log_fp = fopen(registry.log_filename, "w"); if(registry.registry_log_fp) fclose(registry.registry_log_fp); registry.registry_log_fp = NULL; - registry_log_open(); + registry_log_open_nolock(); } + } int registry_log_load(void) { char *s, buf[4096 + 1]; - size_t line = 0; + size_t line = -1; - registry_log_close(); + // closing the log is required here + // otherwise we will append to it the values we read + registry_log_close_nolock(); debug(D_REGISTRY, "Registry: loading active db from: %s", registry.log_filename); FILE *fp = fopen(registry.log_filename, "r"); - if(!fp) { + if(!fp) error("Registry: cannot open registry file: %s", registry.log_filename); - return 0; - } - - size_t len = 0; - while((s = fgets_trim_len(buf, 4096, fp, &len))) { - line++; - - switch(s[0]) { - case 'A': - // verify it is valid - if(unlikely(len < 85 || s[1] != '\t' || s[10] != '\t' || s[47] != '\t' || s[84] != '\t')) - error("Registry log line %u is wrong (len = %zu).", line, len); + else { + line = 0; + size_t len = 0; + while ((s = fgets_trim_len(buf, 4096, fp, &len))) { + line++; + + switch (s[0]) { + case 'A': // accesses + case 'D': // deletes + + // verify it is valid + if (unlikely(len < 85 || s[1] != '\t' || s[10] != '\t' || s[47] != '\t' || s[84] != '\t')) { + error("Registry: log line %u is wrong (len = %zu).", line, len); + continue; + } + + s[1] = s[10] = s[47] = s[84] = '\0'; + + // skip the name to find the url + char *url = &s[85]; + while(*url && *url != '\t') url++; + if(!*url) { + error("Registry: log line %u does not have a url.", line); + continue; + } + *url++ = '\0'; + + if(s[0] == 'A') + registry_request_access(&s[11], &s[48], url, &s[85], strtoul(&s[2], NULL, 16)); + else + registry_request_delete(&s[11], &s[48], url, &s[85], strtoul(&s[2], NULL, 16)); - s[1] = s[10] = s[47] = s[84] = '\0'; - registry_request(&s[11], &s[48], &s[85], strtoul(&s[2], NULL, 16)); - break; + break; - default: - error("Registry: ignoring line %zu of filename '%s': %s.", line, registry.log_filename, s); - break; + default: + error("Registry: ignoring line %zu of filename '%s': %s.", line, registry.log_filename, s); + break; + } } } - registry_log_open(); + // open the log again + registry_log_open_nolock(); - return 0; + return line; } // ---------------------------------------------------------------------------- // REGISTRY REQUESTS -PERSON *registry_request(const char *person_guid, const char *machine_guid, const char *url, time_t when) { - debug(D_REGISTRY, "registry_request('%s', '%s', '%s'): NEW REQUEST", (person_guid)?person_guid:"", machine_guid, url); +PERSON *registry_request_access(char *person_guid, char *machine_guid, char *url, char *name, time_t when) { + debug(D_REGISTRY, "registry_request_access('%s', '%s', '%s'): NEW REQUEST", (person_guid)?person_guid:"", machine_guid, url); MACHINE *m = registry_machine_get(machine_guid, when); if(!m) return NULL; - URL *u = registry_url_get(url); + // make sure the name is valid + size_t namelen; + name = registry_fix_machine_name(name, &namelen); + + size_t urllen; + url = registry_fix_url(url, &urllen); + + URL *u = registry_url_get(url, urllen); PERSON *p = registry_person_get(person_guid, when); - registry_person_link_to_url(p, m, u, when); + registry_person_link_to_url(p, m, u, name, namelen, when); registry_machine_link_to_url(p, m, u, when); - registry_log('A', p, m, u); + registry_log('A', p, m, u, name); registry.usages_count++; return p; } +// verify the person, the machine and the URL exist in our DB +PERSON_URL *registry_verify_request(char *person_guid, char *machine_guid, char *url, PERSON **pp, MACHINE **mm) { + char pbuf[36 + 1], mbuf[36 + 1]; + + if(!person_guid || !*person_guid || !machine_guid || !*machine_guid || !url || !*url) { + info("Registry Request Verification: invalid request! person: '%s', machine '%s', url '%s'", person_guid?person_guid:"UNSET", machine_guid?machine_guid:"UNSET", url?url:"UNSET"); + return NULL; + } + + // normalize the url + url = registry_fix_url(url, NULL); + + // make sure the person GUID is valid + if(registry_regenerate_guid(person_guid, pbuf) == -1) { + info("Registry Request Verification: invalid person GUID, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url); + return NULL; + } + person_guid = pbuf; + + // make sure the machine GUID is valid + if(registry_regenerate_guid(machine_guid, mbuf) == -1) { + info("Registry Request Verification: invalid machine GUID, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url); + return NULL; + } + machine_guid = mbuf; + + // make sure the machine exists + MACHINE *m = registry_machine_find(machine_guid); + if(!m) { + info("Registry Request Verification: machine not found, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url); + return NULL; + } + if(mm) *mm = m; + + // make sure the person exist + PERSON *p = registry_person_find(person_guid); + if(!p) { + info("Registry Request Verification: person not found, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url); + return NULL; + } + if(pp) *pp = p; + + PERSON_URL *pu = dictionary_get(p->urls, url); + if(!pu) { + info("Registry Request Verification: URL not found for person, person: '%s', machine '%s', url '%s'", person_guid, machine_guid, url); + return NULL; + } + return pu; +} + +PERSON *registry_request_delete(char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when) { + (void)when; + + PERSON *p = NULL; + MACHINE *m = NULL; + PERSON_URL *pu = registry_verify_request(person_guid, machine_guid, url, &p, &m); + if(!pu || !p || !m) return NULL; + + // normalize the url + delete_url = registry_fix_url(delete_url, NULL); + + // make sure the user is not deleting the url it uses + if(!strcmp(delete_url, pu->url->url)) { + info("Registry Delete Request: delete URL is the one currently accessing, person: '%s', machine '%s', url '%s', delete url '%s'", p->guid, m->guid, pu->url->url, delete_url); + return NULL; + } + + registry_person_urls_lock(p); + + PERSON_URL *dpu = dictionary_get(p->urls, delete_url); + if(!dpu) { + info("Registry Delete Request: URL not found for person, person: '%s', machine '%s', url '%s', delete url '%s'", p->guid, m->guid, pu->url->url, delete_url); + registry_person_urls_unlock(p); + return NULL; + } + + registry_log('D', p, m, pu->url, dpu->url->url); + + dictionary_del(p->urls, dpu->url->url); + registry_url_unlink_nolock(dpu->url); + free(dpu); + + registry_person_urls_unlock(p); + return p; +} + +MACHINE *registry_request_machine(char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when) { + (void)when; + + char mbuf[36 + 1]; + + PERSON *p = NULL; + MACHINE *m = NULL; + PERSON_URL *pu = registry_verify_request(person_guid, machine_guid, url, &p, &m); + if(!pu || !p || !m) return NULL; + + // make sure the machine GUID is valid + if(registry_regenerate_guid(request_machine, mbuf) == -1) { + info("Registry Machine URLs request: invalid machine GUID, person: '%s', machine '%s', url '%s', request machine '%s'", p->guid, m->guid, pu->url->url, request_machine); + return NULL; + } + request_machine = mbuf; + + // make sure the machine exists + m = registry_machine_find(request_machine); + if(!m) { + info("Registry Machine URLs request: machine not found, person: '%s', machine '%s', url '%s', request machine '%s'", p->guid, m->guid, pu->url->url, request_machine); + return NULL; + } + + // Verify the user has in the past accessed this machine + // We will walk through the PERSON_URLs to find the machine + // linking to our machine + + // C magic ! + + // a structure to pass to the dictionary_get_all() callback handler + struct machine_request_callback_data { + MACHINE *find_this_machine; + PERSON_URL *result; + } rdata = { m, NULL }; + + // the callback function + // this will be run for every PERSON_URL of this PERSON + int machine_request_callback(void *entry, void *data) { + PERSON_URL *mypu = (PERSON_URL *)entry; + struct machine_request_callback_data *myrdata = (struct machine_request_callback_data *)data; + + if(mypu->machine == myrdata->find_this_machine) { + myrdata->result = mypu; + return -1; // this will also stop the walk through + } + + return 0; // continue + } + + // request a walk through on the dictionary + // no need for locking here, the underlying dictionary has its own + dictionary_get_all(p->urls, machine_request_callback, &rdata); + + if(rdata.result) + return m; + + return NULL; +} + + +// ---------------------------------------------------------------------------- +// REGISTRY JSON generation + +#ifndef REGISTRY_STANDALONE_TESTS + +static inline void registry_json_header(struct web_client *w, int status) { + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, "{\n\"success\": %s", status?"true":"false"); +} + +static inline void registry_json_footer(struct web_client *w) { + buffer_strcat(w->response.data, "\n}\n"); +} + +static inline int registry_json_redirect(struct web_client *w) { + registry_json_header(w, 0); + buffer_sprintf(w->response.data, "\"registries\": \"%s\"", config_get("global", "users registries", "reg1.mynetdata.io reg2.mynetdata.io reg3.mynetdata.io")); + registry_json_footer(w); + return 200; +} + +int registry_request_access_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *name, time_t when) { + if(!registry.enabled) + return registry_json_redirect(w); + + PERSON *p = registry_request_access(person_guid, machine_guid, url, name, when); + if(!p) { + registry_json_header(w, 0); + registry_json_footer(w); + return 400; + } + + // FIXME -- make this a permanent cookie + strncpy(w->cookie, p->guid, COOKIE_MAX); + w->cookie[COOKIE_MAX] = '\0'; + + registry_json_header(w, 1); + + // FIXME - print an array of all URLs / Machines / Names + + buffer_strcat(w->response.data, "\n}\n"); + return 200; +} + +int registry_request_delete_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when) { + if(!registry.enabled) + return registry_json_redirect(w); + + PERSON *p = registry_request_delete(person_guid, machine_guid, url, delete_url, when); + if(!p) { + registry_json_header(w, 0); + registry_json_footer(w); + return 400; + } + + registry_json_header(w, 1); + registry_json_footer(w); + return 200; +} + +int registry_request_search_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when) { + if(!registry.enabled) + return registry_json_redirect(w); + + MACHINE *m = registry_request_machine(person_guid, machine_guid, url, request_machine, when); + if(!m) { + registry_json_header(w, 0); + registry_json_footer(w); + return 400; + } + + registry_json_header(w, 1); + + // FIXME - print an array of all URLs the machine is accessible + + registry_json_footer(w); + return 200; +} + +#endif /* REGISTRY_STANDALONE_TESTS */ + // ---------------------------------------------------------------------------- // REGISTRY LOAD/SAVE @@ -527,6 +963,8 @@ int registry_machine_save_url(void *entry, void *file) { mu->url->url ); + // error handling is done at registry_save() + return ret; } @@ -549,6 +987,8 @@ int registry_machine_save(void *entry, void *file) { ret += ret2; } + // error handling is done at registry_save() + return ret; } @@ -558,15 +998,18 @@ static inline int registry_person_save_url(void *entry, void *file) { debug(D_REGISTRY, "Registry: registry_person_save_url('%s')", pu->url->url); - int ret = fprintf(fp, "U\t%08x\t%08x\t%08x\t%02x\t%s\t%s\n", + int ret = fprintf(fp, "U\t%08x\t%08x\t%08x\t%02x\t%s\t%s\t%s\n", pu->first_t, pu->last_t, pu->usages, pu->flags, pu->machine->guid, + pu->name, pu->url->url ); + // error handling is done at registry_save() + return ret; } @@ -589,20 +1032,28 @@ static inline int registry_person_save(void *entry, void *file) { ret += ret2; } + // error handling is done at registry_save() + return ret; } -static int registry_save(void) { +int registry_save(void) { + if(!registry.enabled) return -1; + char tmp_filename[FILENAME_MAX + 1]; char old_filename[FILENAME_MAX + 1]; snprintf(old_filename, FILENAME_MAX, "%s.old", registry.db_filename); snprintf(tmp_filename, FILENAME_MAX, "%s.tmp", registry.db_filename); + // make sure the log is not updated + registry_log_lock(); + debug(D_REGISTRY, "Registry: Creating file '%s'", tmp_filename); FILE *fp = fopen(tmp_filename, "w"); if(!fp) { error("Registry: Cannot create file: %s", tmp_filename); + registry_log_unlock(); return -1; } @@ -611,6 +1062,7 @@ static int registry_save(void) { if(bytes1 < 0) { error("Registry: Cannot save registry machines - return value %d", bytes1); fclose(fp); + registry_log_unlock(); return bytes1; } debug(D_REGISTRY, "Registry: saving machines took %d bytes", bytes1); @@ -620,10 +1072,21 @@ static int registry_save(void) { if(bytes2 < 0) { error("Registry: Cannot save registry persons - return value %d", bytes2); fclose(fp); + registry_log_unlock(); return bytes2; } debug(D_REGISTRY, "Registry: saving persons took %d bytes", bytes2); + // save the totals + fprintf(fp, "T\t%016llx\t%016llx\t%016llx\t%016llx\t%016llx\t%016llx\n", + registry.persons_count, + registry.machines_count, + registry.usages_count, + registry.urls_count, + registry.persons_urls_count, + registry.machines_urls_count + ); + fclose(fp); errno = 0; @@ -662,10 +1125,13 @@ static int registry_save(void) { // it has been moved successfully // discard the current registry log - registry_log_recreate(); + registry_log_recreate_nolock(); } } + // continue operations + registry_log_unlock(); + return -1; } @@ -688,13 +1154,27 @@ static inline size_t registry_load(void) { line++; debug(D_REGISTRY, "Registry: read line %zu to length %zu: %s", line, len, s); - switch(*s) { + case 'T': // totals + if(unlikely(len != 103 || s[1] != '\t' || s[18] != '\t' || s[35] != '\t' || s[52] != '\t' || s[69] != '\t' || s[86] != '\t' || s[103] != '\0')) { + error("Registry totals line %u is wrong (len = %zu).", line, len); + continue; + } + registry.persons_count = strtoull(&s[2], NULL, 16); + registry.machines_count = strtoull(&s[19], NULL, 16); + registry.usages_count = strtoull(&s[36], NULL, 16); + registry.urls_count = strtoull(&s[53], NULL, 16); + registry.persons_urls_count = strtoull(&s[70], NULL, 16); + registry.machines_urls_count = strtoull(&s[87], NULL, 16); + break; + case 'P': // person m = NULL; // verify it is valid - if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0')) + if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0')) { error("Registry person line %u is wrong (len = %zu).", line, len); + continue; + } s[1] = s[10] = s[19] = s[28] = '\0'; p = registry_person_allocate(&s[29], strtoul(&s[2], NULL, 16)); @@ -706,8 +1186,10 @@ static inline size_t registry_load(void) { case 'M': // machine p = NULL; // verify it is valid - if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0')) + if(unlikely(len != 65 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[65] != '\0')) { error("Registry person line %u is wrong (len = %zu).", line, len); + continue; + } s[1] = s[10] = s[19] = s[28] = '\0'; m = registry_machine_allocate(&s[29], strtoul(&s[2], NULL, 16)); @@ -719,40 +1201,54 @@ static inline size_t registry_load(void) { case 'U': // person URL if(unlikely(!p)) { error("Registry: ignoring line %zu, no person loaded: %s", line, s); - break; + continue; } // verify it is valid - if(len < 69 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t' || s[68] != '\t') + if(len < 69 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t' || s[68] != '\t') { error("Registry person URL line %u is wrong (len = %zu).", line, len); + continue; + } s[1] = s[10] = s[19] = s[28] = s[31] = s[68] = '\0'; - u = registry_url_allocate(&s[69]); + + // skip the name to find the url + char *url = &s[69]; + while(*url && *url != '\t') url++; + if(!*url) { + error("Registry person URL line %u does not have a url.", line); + continue; + } + *url++ = '\0'; + + u = registry_url_allocate_nolock(url, strlen(url)); time_t first_t = strtoul(&s[2], NULL, 16); m = registry_machine_find(&s[32]); if(!m) m = registry_machine_allocate(&s[32], first_t); - PERSON_URL *pu = registry_person_url_allocate(p, m, u, first_t); + PERSON_URL *pu = registry_person_url_allocate(p, m, u, &s[69], strlen(&s[69]), first_t); pu->last_t = strtoul(&s[11], NULL, 16); pu->usages = strtoul(&s[20], NULL, 16); pu->flags = strtoul(&s[29], NULL, 16); - debug(D_REGISTRY, "Registry loaded person URL '%s', machine '%s', first: %u, last: %u, usages: %u, flags: %02x", u->url, m->guid, pu->first_t, pu->last_t, pu->usages, pu->flags); + debug(D_REGISTRY, "Registry loaded person URL '%s' with name '%s' of machine '%s', first: %u, last: %u, usages: %u, flags: %02x", u->url, pu->name, m->guid, pu->first_t, pu->last_t, pu->usages, pu->flags); break; case 'V': // machine URL if(unlikely(!m)) { error("Registry: ignoring line %zu, no machine loaded: %s", line, s); - break; + continue; } // verify it is valid - if(len < 32 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t') + if(len < 32 || s[1] != '\t' || s[10] != '\t' || s[19] != '\t' || s[28] != '\t' || s[31] != '\t') { error("Registry person URL line %u is wrong (len = %zu).", line, len); + continue; + } s[1] = s[10] = s[19] = s[28] = s[31] = '\0'; - u = registry_url_allocate(&s[32]); + u = registry_url_allocate_nolock(&s[32], strlen(&s[32])); MACHINE_URL *mu = registry_machine_url_allocate(m, u, strtoul(&s[2], NULL, 16)); mu->last_t = strtoul(&s[11], NULL, 16); @@ -778,7 +1274,6 @@ static inline size_t registry_load(void) { void registry_init(void) { char filename[FILENAME_MAX + 1]; - registry.pathname = config_get("registry", "registry db directory", VARLIB_DIR); if(mkdir(registry.pathname, 0644) == -1 && errno != EEXIST) error("Cannot create directory '%s'", registry.pathname); @@ -805,11 +1300,24 @@ void registry_init(void) { debug(D_REGISTRY, "Registry: creating global registry dictionary for urls."); registry.urls = dictionary_create(DICTIONARY_FLAGS); - registry_log_open(); + pthread_mutex_init(®istry.persons_lock, NULL); + pthread_mutex_init(®istry.machines_lock, NULL); + pthread_mutex_init(®istry.urls_lock, NULL); + pthread_mutex_init(®istry.person_urls_lock, NULL); + pthread_mutex_init(®istry.machine_urls_lock, NULL); + + registry_log_open_nolock(); registry_load(); + + registry.enabled = 1; } void registry_free(void) { + if(!registry.enabled) return; + + // we need to destroy the dictionaries ourselves + // since the dictionaries use memory we allocated + while(registry.persons->values_index.root) { PERSON *p = ((NAME_VALUE *)registry.persons->values_index.root)->value; @@ -824,7 +1332,7 @@ void registry_free(void) { dictionary_del(p->urls, pu->url->url); debug(D_REGISTRY, "Registry: unlinking url '%s' from person", pu->url->url); - registry_url_unlink(pu->url); + registry_url_unlink_nolock(pu->url); debug(D_REGISTRY, "Registry: freeing person url"); free(pu); @@ -857,7 +1365,7 @@ void registry_free(void) { dictionary_del(m->urls, mu->url->url); debug(D_REGISTRY, "Registry: unlinking url '%s' from machine", mu->url->url); - registry_url_unlink(mu->url); + registry_url_unlink_nolock(mu->url); debug(D_REGISTRY, "Registry: freeing machine url"); free(mu); @@ -873,6 +1381,8 @@ void registry_free(void) { free(m); } + // and free the memory of remaining dictionary structures + debug(D_REGISTRY, "Registry: destroying persons dictionary"); dictionary_destroy(registry.persons); @@ -884,6 +1394,8 @@ void registry_free(void) { } +#ifdef REGISTRY_STANDALONE_TESTS + // ---------------------------------------------------------------------------- // TESTS @@ -933,7 +1445,7 @@ int test1(int argc, char **argv) { for(u = 0; u < users ; u++) { if(++m == machines) m = 0; - PERSON *p = registry_request(NULL, machines_guids[m], machines_urls[m], now); + PERSON *p = registry_request_access(NULL, machines_guids[m], machines_urls[m], "test", now); users_guids[u] = p->guid; } print_stats(u, start, timems()); @@ -945,7 +1457,7 @@ int test1(int argc, char **argv) { for(u = 0; u < users ; u++) { if(++m == machines) m = 0; - PERSON *p = registry_request(users_guids[u], machines_guids[m], machines_urls[m], now); + PERSON *p = registry_request_access(users_guids[u], machines_guids[m], machines_urls[m], "test", now); if(p->guid != users_guids[u]) fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[u], p->guid); @@ -959,7 +1471,7 @@ int test1(int argc, char **argv) { for(u = 0; u < users ; u++) { if(++m == machines) m = 0; - PERSON *p = registry_request(users_guids[u], machines_guids[m], machines_urls[m], now); + PERSON *p = registry_request_access(users_guids[u], machines_guids[m], machines_urls[m], "test", now); if(p->guid != users_guids[u]) fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[u], p->guid); @@ -973,7 +1485,7 @@ int test1(int argc, char **argv) { uint32_t tu = random() * users / RAND_MAX; uint32_t tm = random() * machines / RAND_MAX; - PERSON *p = registry_request(users_guids[tu], machines_guids[tm], machines_urls[tm], now); + PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], machines_urls[tm], "test", now); if(p->guid != users_guids[tu]) fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid); @@ -987,7 +1499,7 @@ int test1(int argc, char **argv) { uint32_t tu = random() * users / RAND_MAX; uint32_t tm = random() * machines2 / RAND_MAX; - PERSON *p = registry_request(users_guids[tu], machines_guids[tm], machines_urls[tm], now); + PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], machines_urls[tm], "test", now); if(p->guid != users_guids[tu]) fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid); @@ -1013,7 +1525,7 @@ int test1(int argc, char **argv) { else if (random() % 1000 == 123) url = machines_urls[random() * machines2 / RAND_MAX]; - PERSON *p = registry_request(users_guids[tu], machines_guids[tm], url, now); + PERSON *p = registry_request_access(users_guids[tu], machines_guids[tm], url, "test", now); if (p->guid != users_guids[tu]) fprintf(stderr, "ERROR: expected to get user guid '%s' but git '%s'", users_guids[tu], p->guid); @@ -1037,9 +1549,9 @@ int test1(int argc, char **argv) { // TESTING int main(int argc, char **argv) { - debug_flags = 0xFFFFFFFF; - //test1(argc, argv); - //exit(0); + // debug_flags = 0xFFFFFFFF; + // test1(argc, argv); + // exit(0); (void)argc; (void)argv; @@ -1054,39 +1566,39 @@ int main(int argc, char **argv) { int i = 2; fprintf(stderr, "\n\nADDING ENTRY\n"); - // p1 = registry_request("2c95abd0-1542-11e6-8c66-00508db7e9c9", "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", time(NULL)); + p1 = registry_request_access("2c95abd0-1542-11e6-8c66-00508db7e9c9", "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", "test", time(NULL)); if(0) while(i--) { #ifdef REGISTRY_STDOUT_DUMP fprintf(stderr, "\n\nADDING ENTRY\n"); #endif /* REGISTRY_STDOUT_DUMP */ - p1 = registry_request(NULL, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", time(NULL)); + p1 = registry_request_access(NULL, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://localhost:19999/", "test", time(NULL)); #ifdef REGISTRY_STDOUT_DUMP fprintf(stderr, "\n\nADDING ANOTHER URL\n"); #endif /* REGISTRY_STDOUT_DUMP */ - p1 = registry_request(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://127.0.0.1:19999/", time(NULL)); + p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://127.0.0.1:19999/", "test", time(NULL)); #ifdef REGISTRY_STDOUT_DUMP fprintf(stderr, "\n\nADDING ANOTHER URL\n"); #endif /* REGISTRY_STDOUT_DUMP */ - p1 = registry_request(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", time(NULL)); + p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", "test", time(NULL)); #ifdef REGISTRY_STDOUT_DUMP fprintf(stderr, "\n\nADDING ANOTHER MACHINE\n"); #endif /* REGISTRY_STDOUT_DUMP */ - p1 = registry_request(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", time(NULL)); + p1 = registry_request_access(p1->guid, "7c173980-145c-11e6-b86f-00508db7e9c1", "http://my.server:19999/", "test", time(NULL)); #ifdef REGISTRY_STDOUT_DUMP fprintf(stderr, "\n\nADDING ANOTHER PERSON\n"); #endif /* REGISTRY_STDOUT_DUMP */ - p2 = registry_request(NULL, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", time(NULL)); + p2 = registry_request_access(NULL, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", "test", time(NULL)); #ifdef REGISTRY_STDOUT_DUMP fprintf(stderr, "\n\nADDING ANOTHER MACHINE\n"); #endif /* REGISTRY_STDOUT_DUMP */ - p2 = registry_request(p2->guid, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", time(NULL)); + p2 = registry_request_access(p2->guid, "7c173980-145c-11e6-b86f-00508db7e9c3", "http://localhost:19999/", "test", time(NULL)); } fprintf(stderr, "\n\nSAVE\n"); @@ -1096,3 +1608,5 @@ int main(int argc, char **argv) { registry_free(); return 0; } + +#endif /* REGISTRY_STANDALONE_TESTS */ diff --git a/src/registry.h b/src/registry.h new file mode 100644 index 00000000..3a35c868 --- /dev/null +++ b/src/registry.h @@ -0,0 +1,16 @@ +#include "web_client.h" + +#ifndef NETDATA_REGISTRY_H +#define NETDATA_REGISTRY_H 1 + +#define NETDATA_REGISTRY_COOKIE_NAME "netdata_registry_id" + +extern int registry_request_access_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *name, time_t when); +extern int registry_request_delete_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when); +extern int registry_request_search_json(struct web_client *w, char *person_guid, char *machine_guid, char *url, char *request_machine, time_t when); + +extern void registry_init(void); +extern void registry_free(void); +extern int registry_save(void); + +#endif /* NETDATA_REGISTRY_H */ diff --git a/src/web_client.c b/src/web_client.c index a98ddbd3..77396dfd 100644 --- a/src/web_client.c +++ b/src/web_client.c @@ -26,6 +26,7 @@ #include "global_statistics.h" #include "rrd.h" #include "rrd2json.h" +#include "registry.h" #include "web_client.h" #include "../config.h" @@ -173,6 +174,7 @@ void web_client_reset(struct web_client *w) } w->last_url[0] = '\0'; + w->cookie[0] = '\0'; w->mode = WEB_CLIENT_MODE_NORMAL; @@ -644,7 +646,7 @@ int web_client_api_request_v1_data(struct web_client *w, char *url) if(!name || !*name) continue; if(!value || !*value) continue; - debug(D_WEB_CLIENT, "%llu: API v1 query param '%s' with value '%s'", w->id, name, value); + debug(D_WEB_CLIENT, "%llu: API v1 data query param '%s' with value '%s'", w->id, name, value); // name and value are now the parameters // they are not null and not empty @@ -784,19 +786,111 @@ cleanup: return ret; } +int web_client_api_request_v1_registry(struct web_client *w, char *url) +{ + char person_guid[36 + 1] = ""; + + debug(D_WEB_CLIENT, "%llu: API v1 registry with URL '%s'", w->id, url); + + char *cookie = strstr(w->response.data->buffer, " " NETDATA_REGISTRY_COOKIE_NAME "="); + if(cookie) { + strncpy(person_guid, &cookie[sizeof(NETDATA_REGISTRY_COOKIE_NAME) + 2], 36); + person_guid[36] = '\0'; + } + + char action = '\0'; + char *machine_guid = NULL, + *machine_url = NULL, + *url_name = NULL, + *search_machine_guid = NULL, + *delete_url = NULL; + + while(url) { + char *value = mystrsep(&url, "?&[]"); + if (!value || !*value) continue; + + char *name = mystrsep(&value, "="); + if (!name || !*name) continue; + if (!value || !*value) continue; + + debug(D_WEB_CLIENT, "%llu: API v1 registry query param '%s' with value '%s'", w->id, name, value); + + if(!strcmp(name, "action")) { + if(!strcmp(value, "access")) action = 'A'; + else if(!strcmp(value, "delete")) action = 'D'; + else if(!strcmp(value, "search")) action = 'S'; + } + else if(!strcmp(name, "machine")) + machine_guid = value; + + else if(!strcmp(name, "url")) + machine_url = value; + + else if(action == 'A') { + if(!strcmp(name, "name")) + url_name = value; + } + else if(action == 'D') { + if(!strcmp(name, "delete_url")) + delete_url = value; + } + else if(action == 'S') { + if(!strcmp(name, "for")) + search_machine_guid = value; + } + } + + if((!action || !machine_guid || !machine_url) || (action == 'A' && !url_name) || (action == 'D' && !delete_url) || (action == 'S' && !search_machine_guid)) { + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, "Invalid registry request - required parameters missing."); + return 400; + } + + switch(action) { + case 'A': + return registry_request_access_json(w, person_guid, machine_guid, machine_url, url_name, time(NULL)); + + case 'D': + return registry_request_delete_json(w, person_guid, machine_guid, machine_url, delete_url, time(NULL)); + + case 'S': + return registry_request_search_json(w, person_guid, machine_guid, machine_url, search_machine_guid, time(NULL)); + } + + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, "Invalid or no registry action."); + return 400; +} + int web_client_api_request_v1(struct web_client *w, char *url) { + static uint32_t data_hash = 0, chart_hash = 0, charts_hash = 0, registry_hash = 0; + + if(unlikely(data_hash == 0)) { + data_hash = simple_hash("data"); + chart_hash = simple_hash("chart"); + charts_hash = simple_hash("charts"); + registry_hash = simple_hash("registry"); + } + // get the command char *tok = mystrsep(&url, "/?&"); if(tok && *tok) { debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, tok); + uint32_t hash = simple_hash(tok); - if(strcmp(tok, "data") == 0) + if(hash == data_hash && !strcmp(tok, "data")) return web_client_api_request_v1_data(w, url); - else if(strcmp(tok, "chart") == 0) + + else if(hash == chart_hash && !strcmp(tok, "chart")) return web_client_api_request_v1_chart(w, url); - else if(strcmp(tok, "charts") == 0) + + else if(hash == charts_hash && !strcmp(tok, "charts")) return web_client_api_request_v1_charts(w, url); + + else if(hash == registry_hash && !strcmp(tok, "registry")) + return web_client_api_request_v1_registry(w, url); + else { buffer_flush(w->response.data); buffer_sprintf(w->response.data, "Unsupported v1 API command: %s", tok); @@ -1043,6 +1137,32 @@ cleanup: } */ +// get the request buffer, just after the GET or OPTIONS +// and find the url to be decoded, decode it and return +// a newly allocated buffer with it +static inline char *find_url_and_decode_it(char *request) { + char *e = request, *url = NULL; + + // find the SPACE + "HTTP/" + while(*e) { + // find the space + while (*e && *e != ' ') e++; + + // is it SPACE + "HTTP/" ? + if(*e && !strncmp(e, " HTTP/", 6)) + break; + } + + if(*e) { + // we have the end + *e = '\0'; + url = url_decode(request); + *e = ' '; + } + + return url; +} + void web_client_process(struct web_client *w) { int code = 500; ssize_t bytes; @@ -1069,33 +1189,24 @@ void web_client_process(struct web_client *w) { enable_gzip = 1; #endif // NETDATA_WITH_ZLIB - int datasource_type = DATASOURCE_DATATABLE_JSONP; - //if(strstr(w->response.data->buffer, "X-DataSource-Auth")) - // datasource_type = DATASOURCE_GOOGLE_JSON; + w->mode = WEB_CLIENT_MODE_NORMAL; - char *buf = (char *)buffer_tostring(w->response.data); - char *tok = strsep(&buf, " \r\n"); - char *url = NULL; + char *tok = (char *)buffer_tostring(w->response.data); + char *url = NULL, *encoded_url = NULL; char *pointer_to_free = NULL; // keep url_decode() allocated buffer - w->mode = WEB_CLIENT_MODE_NORMAL; - - if(buf && strcmp(tok, "GET") == 0) { - tok = strsep(&buf, " \r\n"); - pointer_to_free = url = url_decode(tok); - debug(D_WEB_CLIENT, "%llu: Processing HTTP GET on url '%s'.", w->id, url); - } - else if(buf && strcmp(tok, "OPTIONS") == 0) { - tok = strsep(&buf, " \r\n"); - pointer_to_free = url = url_decode(tok); - debug(D_WEB_CLIENT, "%llu: Processing HTTP OPTIONS on url '%s'.", w->id, url); + if(!strncmp(tok, "GET ", 4)) + encoded_url = &tok[4]; + else if(!strncmp(tok, "OPTIONS ", 8)) { + encoded_url = &tok[8]; w->mode = WEB_CLIENT_MODE_OPTIONS; } - else if (buf && strcmp(tok, "POST") == 0) { - w->keepalive = 0; - tok = strsep(&buf, " \r\n"); - pointer_to_free = url = url_decode(tok); - debug(D_WEB_CLIENT, "%llu: I don't know how to handle POST with form data. Assuming it is a GET on url '%s'.", w->id, url); + + if(encoded_url) { + pointer_to_free = url = find_url_and_decode_it(encoded_url); + + if(url) debug(D_WEB_CLIENT, "%llu: Processing url '%s'.", w->id, url); + else debug(D_WEB_CLIENT, "%llu: Cannot find a valid URL in '%s'", w->id, encoded_url); } w->last_url[0] = '\0'; @@ -1124,26 +1235,29 @@ void web_client_process(struct web_client *w) { if(strcmp(tok, "api") == 0) { // the client is requesting api access - datasource_type = DATASOURCE_JSON; code = web_client_api_request(w, url); } #ifdef NETDATA_INTERNAL_CHECKS else if(strcmp(tok, "exit") == 0) { - netdata_exit = 1; code = 200; w->response.data->contenttype = CT_TEXT_PLAIN; buffer_flush(w->response.data); - buffer_strcat(w->response.data, "will do"); + + if(!netdata_exit) + buffer_strcat(w->response.data, "ok, will do..."); + else + buffer_strcat(w->response.data, "I am doing it already"); + + netdata_exit = 1; } #endif else if(strcmp(tok, WEB_PATH_DATA) == 0) { // "data" // the client is requesting rrd data - datasource_type = DATASOURCE_JSON; - code = web_client_data_request(w, url, datasource_type); + code = web_client_data_request(w, url, DATASOURCE_JSON); } else if(strcmp(tok, WEB_PATH_DATASOURCE) == 0) { // "datasource" // the client is requesting google datasource - code = web_client_data_request(w, url, datasource_type); + code = web_client_data_request(w, url, DATASOURCE_DATATABLE_JSONP); } else if(strcmp(tok, WEB_PATH_GRAPH) == 0) { // "graph" // the client is requesting an rrd graph @@ -1269,7 +1383,7 @@ void web_client_process(struct web_client *w) { else { strcpy(w->last_url, "not a valid response"); - if(buf) debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, buf); + debug(D_WEB_CLIENT_ACCESS, "%llu: Cannot understand '%s'.", w->id, w->response.data->buffer); code = 500; buffer_flush(w->response.data); @@ -1437,10 +1551,17 @@ void web_client_process(struct web_client *w) { , date ); + if(w->cookie[0]) { + buffer_sprintf(w->response.header_output, + "Set-Cookie: %s\r\n", + w->cookie); + } + if(w->mode == WEB_CLIENT_MODE_OPTIONS) { buffer_strcat(w->response.header_output, "Access-Control-Allow-Methods: GET, OPTIONS\r\n" - "Access-Control-Allow-Headers: accept, x-requested-with\r\n" + "Access-Control-Allow-Credentials: true\r\n" + "Access-Control-Allow-Headers: Accept, X-Requested-With, Content-Type, Cookie\r\n" "Access-Control-Max-Age: 1209600\r\n" // 86400 * 14 ); } diff --git a/src/web_client.h b/src/web_client.h index 3823dbc9..e6540c9f 100644 --- a/src/web_client.h +++ b/src/web_client.h @@ -11,6 +11,7 @@ #include #include "web_buffer.h" +#include "dictionary.h" #define DEFAULT_DISCONNECT_IDLE_WEB_CLIENTS_AFTER_SECONDS 60 extern int web_client_timeout; @@ -26,6 +27,7 @@ extern int web_enable_gzip; #define URL_MAX 8192 #define ZLIB_CHUNK 16384 #define HTTP_RESPONSE_HEADER_SIZE 4096 +#define COOKIE_MAX 200 struct response { BUFFER *header; // our response header @@ -58,6 +60,8 @@ struct web_client { struct timeval tv_in, tv_ready; + char cookie[COOKIE_MAX+1]; + int mode; int keepalive;