From: Martin Mares Date: Wed, 19 Jul 2017 12:02:21 +0000 (+0200) Subject: Database of users X-Git-Tag: v0.9~46 X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=1e840c341770bcb3b84eb5aef4df2ac2b0ca6cd3;p=subauth.git Database of users --- diff --git a/server/Makefile b/server/Makefile index 54b613f..a447a0c 100644 --- a/server/Makefile +++ b/server/Makefile @@ -3,4 +3,4 @@ DIRS+=server PROGS+=$(o)/server/subauthd CONFIGS+=subauthd -$(o)/server/subauthd: $(addprefix $(o)/server/, subauthd.o cmd.o) +$(o)/server/subauthd: $(addprefix $(o)/server/, subauthd.o cmd.o auth.o) diff --git a/server/auth.c b/server/auth.c new file mode 100644 index 0000000..f34039b --- /dev/null +++ b/server/auth.c @@ -0,0 +1,217 @@ +/* + * Sub-authentication Daemon: Authentication database + * + * (c) 2017 Martin Mares + */ + +#include +#include +#include + +#include +#include +#include + +#include "subauthd.h" + +#define HASH_NODE struct auth_user +#define HASH_PREFIX(x) user_hash_##x +#define HASH_KEY_ENDSTRING login +//#define HASH_WANT_FIND +#define HASH_WANT_LOOKUP +// #define HASH_WANT_REMOVE +#define HASH_GIVE_INIT_DATA + +static void user_hash_init_data(struct auth_user *au) +{ + clist_init(&au->accounts); +} + +static struct auth_zone *find_zone_by_name(const char *name) +{ + CLIST_FOR_EACH(struct auth_zone *, z, zone_list) + if (!strcmp(z->name, name)) + return z; + return NULL; +} + +#include + +static void NONRET db_corrupted(const char *reason) +{ + die("Database file corrupted: %s", reason); +} + +static void db_parse_user(struct json_node *ju) +{ + if (ju->type != JSON_OBJECT) + db_corrupted("Expected object"); + + const char *login = get_string(ju, "l"); + if (!login) + db_corrupted("User has no login"); + + struct json_node **jas = get_array(ju, "a"); + if (!jas) + db_corrupted("User has no accounts"); + + struct auth_user *au = user_hash_lookup((char *) login); + if (clist_head(&au->accounts)) + db_corrupted("Multiple users with the same login"); + + for (uint i=0; i < GARY_SIZE(jas); i++) + { + struct json_node *ja = jas[i]; + + const char *zone_name = get_string(ja, "z"); + if (!zone_name) + db_corrupted("Account has no zone"); + + struct auth_acct *aa = xmalloc_zero(sizeof(*aa)); + clist_add_tail(&au->accounts, &aa->n); + aa->zone = find_zone_by_name(zone_name); + if (!aa->zone) + die("Database defines accounts in zone %s, which is not configured", zone_name); + clist_init(&aa->tokens); + + struct json_node **jts = get_array(ja, "t"); + if (jts) + { + for (uint j=0; j < GARY_SIZE(jts); j++) + { + struct json_node *jt = jts[j]; + if (jt->type != JSON_OBJECT) + db_corrupted("Token is not an object"); + + uint type; + if (!get_uint(jt, "t", &type) || type >= TOKEN_NUM_TYPES) + db_corrupted("Token has no valid type"); + + const char *salt = get_string(jt, "s"); + const char *hash = get_string(jt, "h"); + const char *cmt = get_string(jt, "c"); + if (!salt || !hash) + db_corrupted("Token must have salt and hash"); + + uint lastmod; + if (!get_uint(jt, "m", &lastmod)) + db_corrupted("Token has invalid last modified time"); + + struct auth_token *at = xmalloc(sizeof(*at)); + clist_add_tail(&aa->tokens, &at->n); + at->type = type; + at->salt = xstrdup(salt); + at->hash = xstrdup(hash); + at->comment = xstrdup(cmt); + at->last_modified = lastmod; + } + } + } +} + +static void db_read(void) +{ + struct fastbuf *fb = bopen_try(database_name, O_RDONLY, 65536); + if (!fb) + { + msg(L_WARN, "No database file found, starting with no users"); + return; + } + + struct json_context *js = json_new(); + json_set_input(js, fb); + + struct json_node *sep = json_next_token(js); + if (sep->type != JSON_BEGIN_ARRAY) + db_corrupted("Does not start with '['"); + + for (;;) + { + json_push(js); + + sep = json_peek_token(js); + if (sep->type == JSON_END_ARRAY) + { + json_next_token(js); + json_pop(js); + break; + } + else if (sep->type == JSON_VALUE_SEP) + json_next_token(js); + + struct json_node *ju = json_next_value(js); + db_parse_user(ju); + json_pop(js); + } + + sep = json_next_token(js); + if (sep->type != JSON_EOF) + db_corrupted("Garbage at the end"); + + json_delete(js); + bclose(fb); +} + +void db_write(void) +{ + char *temp_name = stk_strcat(database_name, ".new"); + struct fastbuf *fb = bopen(temp_name, O_WRONLY | O_CREAT | O_TRUNC, 65536); + struct json_context *js = json_new(); + json_set_output(js, fb); + bputs(fb, "[\n"); + uint cnt = 0; + + HASH_FOR_ALL(user_hash, au) + { + json_push(js); + if (cnt) + bputs(fb, ",\n"); + struct json_node *ju = json_new_object(js); + json_object_set(ju, "l", json_new_string_ref(js, au->login)); + struct json_node *jas = json_new_array(js); + json_object_set(ju, "a", jas); + CLIST_FOR_EACH(struct auth_acct *, aa, au->accounts) + { + struct json_node *ja = json_new_object(js); + json_array_append(jas, ja); + json_object_set(ja, "z", json_new_string_ref(js, aa->zone->name)); + struct json_node *jts = json_new_array(js); + CLIST_FOR_EACH(struct auth_token *, at, aa->tokens) + { + struct json_node *jt = json_new_object(js); + json_array_append(jts, jt); + json_object_set(jt, "t", json_new_number(js, at->type)); + if (at->salt) + json_object_set(jt, "s", json_new_string_ref(js, at->salt)); + if (at->hash) + json_object_set(jt, "h", json_new_string_ref(js, at->hash)); + if (at->comment) + json_object_set(jt, "c", json_new_string_ref(js, at->comment)); + json_object_set(jt, "m", json_new_number(js, at->last_modified)); + } + if (GARY_SIZE(jts->elements)) + json_object_set(ja, "t", jts); + } + json_write_value(js, ju); + json_pop(js); + cnt++; + } + HASH_END_FOR; + + bputs(fb, "\n]\n"); + json_delete(js); + bclose(fb); + + char *old_name = stk_strcat(database_name, ".old"); + if (rename(database_name, old_name) < 0 && errno != ENOENT) + die("Cannot rename %s to %s: %m", database_name, old_name); + + if (rename(temp_name, database_name) < 0) + die("Cannot rename %s to %s: %m", temp_name, database_name); +} + +void auth_init(void) +{ + user_hash_init(); + db_read(); +} diff --git a/server/cmd.c b/server/cmd.c index a53622e..125422a 100644 --- a/server/cmd.c +++ b/server/cmd.c @@ -18,7 +18,7 @@ static void cmd_ok(struct client *c) cmd_error(c, ""); } -static const char *get_string(struct json_node *n, const char *key) +const char *get_string(struct json_node *n, const char *key) { struct json_node *s = json_object_get(n, key); if (s && s->type == JSON_STRING) @@ -27,6 +27,40 @@ static const char *get_string(struct json_node *n, const char *key) return NULL; } +bool get_uint(struct json_node *n, const char *key, uint *dest) +{ + struct json_node *s = json_object_get(n, key); + if (s && s->type == JSON_NUMBER) + { + uint u = (uint) s->number; + if ((double) u == s->number) + { + *dest = u; + return 1; + } + } + *dest = 0; + return 0; +} + +struct json_node **get_array(struct json_node *n, const char *key) +{ + struct json_node *s = json_object_get(n, key); + if (s && s->type == JSON_ARRAY) + return s->elements; + else + return NULL; +} + +struct json_node *get_object(struct json_node *n, const char *key) +{ + struct json_node *s = json_object_get(n, key); + if (s && s->type == JSON_OBJECT) + return s; + else + return NULL; +} + static void cmd_nop(struct client *c) { cmd_ok(c); diff --git a/server/subauthd.c b/server/subauthd.c index cd6376f..d9892b4 100644 --- a/server/subauthd.c +++ b/server/subauthd.c @@ -18,8 +18,11 @@ #include "subauthd.h" +// Configuration static char *socket_path = "subauthd.socket"; static uint max_connections = ~0U; +clist zone_list; +char *database_name = "subauthd.db"; static struct main_file listen_socket; static uint num_connections; @@ -271,10 +274,32 @@ static void init_socket(void) msg(L_INFO, "Listening on %s", socket_path); } +static char *zone_commit(void *z_) +{ + struct auth_zone *z = z_; + if (!z->name) + return "A zone must have a name"; + return NULL; +} + +static struct cf_section zone_config = { + CF_TYPE(struct auth_zone), + CF_COMMIT(zone_commit), + CF_ITEMS { + CF_STRING("Name", PTR_TO(struct auth_zone, name)), + CF_UINT("AutoCreateAcct", PTR_TO(struct auth_zone, auto_create_acct)), + CF_UINT("AllowPasswd", PTR_TO(struct auth_zone, allow_passwd)), + CF_UINT("AllowTokens", PTR_TO(struct auth_zone, allow_tokens)), + CF_END + } +}; + static struct cf_section daemon_config = { CF_ITEMS { CF_STRING("SocketPath", &socket_path), CF_UINT("MaxConnections", &max_connections), + CF_LIST("Zone", &zone_list, &zone_config), + CF_STRING("Database", &database_name), CF_END } }; @@ -297,6 +322,7 @@ int main(int argc UNUSED, char **argv) cf_declare_section("SubauthD", &daemon_config, 0); opt_parse(&options, argv+1); + auth_init(); main_init(); init_socket(); diff --git a/server/subauthd.h b/server/subauthd.h index 66d792e..e807ef3 100644 --- a/server/subauthd.h +++ b/server/subauthd.h @@ -6,6 +6,9 @@ #include "autoconf.h" +#include + +#include #include #include @@ -22,7 +25,54 @@ struct client { struct json_node *reply; }; +extern clist zone_list; // of struct auth_zone +extern char *database_name; + /* cmd.c */ void cmd_error(struct client *c, const char *err); void cmd_dispatch(struct client *c); + +const char *get_string(struct json_node *n, const char *key); +bool get_uint(struct json_node *n, const char *key, uint *dest); +struct json_node **get_array(struct json_node *n, const char *key); +struct json_node *get_object(struct json_node *n, const char *key); + +/* auth.c */ + +struct auth_zone { + cnode n; + char *name; + uint auto_create_acct; + uint allow_passwd; + uint allow_tokens; +}; + +struct auth_user { + clist accounts; // of struct auth_acct + char login[1]; +}; + +struct auth_acct { + cnode n; + struct auth_zone *zone; + clist tokens; // of struct auth_token +}; + +enum token_type { + TOKEN_PASSWORD, + TOKEN_GENERATED, + TOKEN_NUM_TYPES, +}; + +struct auth_token { + cnode n; + enum token_type type; + char *salt; + char *hash; + char *comment; + time_t last_modified; +}; + +void auth_init(void); +void db_write(void);