]> mj.ucw.cz Git - subauth.git/commitdiff
Database of users
authorMartin Mares <mj@ucw.cz>
Wed, 19 Jul 2017 12:02:21 +0000 (14:02 +0200)
committerMartin Mares <mj@ucw.cz>
Wed, 19 Jul 2017 12:02:21 +0000 (14:02 +0200)
server/Makefile
server/auth.c [new file with mode: 0644]
server/cmd.c
server/subauthd.c
server/subauthd.h

index 54b613f36e30dcd9ab5ee13a8158a360198eaf36..a447a0c2b6f43e7fefc9f6aa55313286ab0a0517 100644 (file)
@@ -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 (file)
index 0000000..f34039b
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ *     Sub-authentication Daemon: Authentication database
+ *
+ *     (c) 2017 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/gary.h>
+#include <ucw/stkstring.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#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 <ucw/hashtable.h>
+
+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();
+}
index a53622e8522f498e433f51bf746f6315fb47427e..125422a93614ded3623c43991bb1dbd35f5479f5 100644 (file)
@@ -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);
index cd6376f7e575be792b6f956a4f762f73d8a6f3a4..d9892b4e15f6dac63c8567d9ea5b2e3cfcfda475 100644 (file)
 
 #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();
 
index 66d792e92cf568919de27a2d7e7f0f8153867962..e807ef326d3b1a011a795cd72e88629de69417fc 100644 (file)
@@ -6,6 +6,9 @@
 
 #include "autoconf.h"
 
+#include <sys/types.h>
+
+#include <ucw/clists.h>
 #include <ucw/mainloop.h>
 #include <ucw-json/json.h>
 
@@ -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);