]> mj.ucw.cz Git - subauth.git/blobdiff - server/cmd.c
Debian packaging: init
[subauth.git] / server / cmd.c
index a53622e8522f498e433f51bf746f6315fb47427e..28ec103baf920f7bd3a089c526c40aec4167ff44 100644 (file)
@@ -5,20 +5,42 @@
  */
 
 #include <ucw/lib.h>
+#include <ucw/trans.h>
+
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
 
 #include "subauthd.h"
 
-void cmd_error(struct client *c, const char *err)
+static void set_string(struct client *c, struct json_node *n, const char *key, const char *val)
+{
+  if (val)
+    json_object_set(n, key, json_new_string(c->json, val));
+}
+
+static void set_uint(struct client *c, struct json_node *n, const char *key, uint val)
+{
+  json_object_set(n, key, json_new_number(c->json, val));
+}
+
+static void NONRET cmd_error(struct client *c, const char *err, ...)
 {
-  json_object_set(c->reply, "error", json_new_string(c->json, err));
+  va_list args;
+  char msg[1024];
+  va_start(args, err);
+  vsnprintf(msg, sizeof(msg), err, args);
+  set_string(c, c->reply, "error", msg);
+  trans_throw("adhoc", NULL, "Error caught");
+  va_end(args);
 }
 
 static void cmd_ok(struct client *c)
 {
-  cmd_error(c, "");
+  set_string(c, c->reply, "error", "");
 }
 
-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,11 +49,454 @@ 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 const char *cmd_need_string(struct client *c, const char *key)
+{
+  const char *val = get_string(c->request, key);
+  if (!val)
+    cmd_error(c, "Missing %s", key);
+  return val;
+}
+
+static struct auth_zone *cmd_need_zone(struct client *c)
+{
+  const char *name = cmd_need_string(c, "zone");
+  struct auth_zone *az = auth_find_zone(name);
+  if (!az)
+    cmd_error(c, "No such zone");
+  return az;
+}
+
+static bool cmd_is_admin(struct client *c)
+{
+  return (c->uid == 0);
+}
+
+static void cmd_require_admin(struct client *c)
+{
+  if (!cmd_is_admin(c))
+    cmd_error(c, "Permission denied");
+}
+
+static const char *cmd_need_target_login(struct client *c)
+{
+  const char *l = get_string(c->request, "login");
+  if (l)
+    {
+      cmd_require_admin(c);
+      return l;
+    }
+  else
+    {
+      struct passwd *pw = getpwuid(c->uid);
+      if (!pw)
+       cmd_error(c, "You do not exist");
+      return json_strdup(c->json, pw->pw_name);
+    }
+}
+
+static struct auth_acct *cmd_need_target_acct(struct client *c)
+{
+  const char *login = cmd_need_target_login(c);
+  struct auth_zone *az = cmd_need_zone(c);
+
+  struct auth_user *au = auth_find_user(login, 0);
+  struct auth_acct *aa = au ? auth_find_acct(au, az, 0) : NULL;
+  if (aa)
+    return aa;
+
+  if (!az->auto_create_acct)
+    cmd_error(c, "No such account");
+
+  if (!au)
+    {
+      msg(L_INFO, "Automatically creating user: login=<%s>", login);
+      au = auth_find_user(login, 1);
+    }
+  msg(L_INFO, "Automatically creating account: login=<%s> zone=<%s>", login, az->name);
+  return auth_find_acct(au, az, 1);
+}
+
 static void cmd_nop(struct client *c)
 {
   cmd_ok(c);
 }
 
+static void cmd_create_acct(struct client *c)
+{
+  cmd_require_admin(c);
+
+  const char *login = get_string(c->request, "login");
+  if (!login)
+    cmd_error(c, "Login required");
+
+  struct auth_zone *az = cmd_need_zone(c);
+  struct auth_user *au = auth_find_user(login, 1);
+  if (!auth_find_acct(au, az, 0))
+    {
+      msg(L_INFO, "Creating account: login=<%s> zone=<%s>", login, az->name);
+      auth_find_acct(au, az, 1);
+      db_write();
+    }
+
+  cmd_ok(c);
+}
+
+static void cmd_delete_acct(struct client *c)
+{
+  cmd_require_admin(c);
+
+  const char *login = get_string(c->request, "login");
+  if (!login)
+    cmd_error(c, "Login required");
+  const char *zone_name = get_string(c->request, "zone");
+
+  struct auth_user *au = auth_find_user(login, 0);
+  if (!au)
+    return cmd_ok(c);
+
+  if (zone_name && !strcmp(zone_name, "*"))
+    {
+      msg(L_INFO, "Deleting user: login=<%s>", login);
+      auth_delete_user(au);
+    }
+  else
+    {
+      struct auth_zone *az = cmd_need_zone(c);
+      struct auth_acct *aa = auth_find_acct(au, az, 0);
+      if (!aa)
+       return cmd_ok(c);
+      msg(L_INFO, "Deleting account: login=<%s> zone=<%s>", login, az->name);
+      auth_delete_acct(aa);
+      if (!clist_head(&au->accounts))
+       {
+         msg(L_INFO, "Deleting user with no accounts: login=<%s>", login);
+         auth_delete_user(au);
+       }
+    }
+
+  db_write();
+  cmd_ok(c);
+}
+
+static void cmd_set_passwd(struct client *c)
+{
+  struct auth_acct *aa = cmd_need_target_acct(c);
+  const char *passwd = cmd_need_string(c, "passwd");
+
+  if (!aa->zone->allow_passwd)
+    cmd_error(c, "This zone does not allow authentication by password");
+
+  if (strchr(passwd, '-'))
+    cmd_error(c, "The minus sign is forbidden in passwords");
+
+  msg(L_INFO, "Set password: login=<%s> zone=<%s>", aa->user->login, aa->zone->name);
+
+  struct auth_token *at_old = auth_find_token_passwd(aa);
+  if (at_old)
+    auth_delete_token(at_old);
+
+  struct auth_token *at = auth_create_token(aa);
+  auth_set_token_passwd(at, passwd);
+
+  db_write();
+  cmd_ok(c);
+}
+
+static void cmd_delete_passwd(struct client *c)
+{
+  struct auth_acct *aa = cmd_need_target_acct(c);
+  struct auth_token *at = auth_find_token_passwd(aa);
+  if (!at)
+    cmd_error(c, "No password set");
+
+  msg(L_INFO, "Deleted password: login=<%s> zone=<%s>", aa->user->login, aa->zone->name);
+  auth_delete_token(at);
+
+  db_write();
+  cmd_ok(c);
+}
+
+static void cmd_create_token(struct client *c)
+{
+  struct auth_acct *aa = cmd_need_target_acct(c);
+
+  if (!aa->zone->allow_tokens)
+    cmd_error(c, "This zone does not allow authentication by tokens");
+
+  if (clist_size(&aa->tokens) >= aa->zone->allow_tokens)
+    cmd_error(c, "Maximum number of tokens was reached");
+
+  struct auth_token *at = auth_create_token(aa);
+  char *tok = auth_set_token_generated(at, get_string(c->request, "comment"), c->pool);
+  set_string(c, c->reply, "token", tok);
+
+  msg(L_INFO, "Created token: login=<%s> zone=<%s> id=<%s>", aa->user->login, aa->zone->name, at->ident);
+
+  db_write();
+  cmd_ok(c);
+}
+
+static void cmd_delete_token(struct client *c)
+{
+  struct auth_acct *aa = cmd_need_target_acct(c);
+  const char *ident = cmd_need_string(c, "ident");
+  bool all = !strcmp(ident, "*");
+  bool matched = 0;
+
+  struct auth_token *tmp;
+  CLIST_FOR_EACH_DELSAFE(struct auth_token *, at, aa->tokens, tmp)
+    if (at->type == TOKEN_GENERATED && (all || !strcmp(at->ident, ident)))
+      {
+       matched = 1;
+       msg(L_INFO, "Deleted token: login=<%s> zone=<%s> id=<%s>", aa->user->login, aa->zone->name, at->ident);
+       auth_delete_token(at);
+      }
+
+  if (!all && !matched)
+    cmd_error(c, "No such token");
+
+  db_write();
+  cmd_ok(c);
+}
+
+static void cmd_create_temp(struct client *c)
+{
+  struct auth_acct *aa = cmd_need_target_acct(c);
+
+  uint validity;
+  if (!get_uint(c->request, "validity", &validity))
+    cmd_error(c, "Validity must be given");
+
+  if (!aa->zone->max_temp_validity)
+    cmd_error(c, "This zone does not allow temporary tokens");
+
+  if (validity > aa->zone->max_temp_validity)
+    cmd_error(c, "This zone limits temporary token validity to %d seconds", aa->zone->max_temp_validity);
+
+  char *tok = temp_generate(aa->zone->name, aa->user->login, validity, c->pool);
+  set_string(c, c->reply, "token", tok);
+
+  const char *shortened = temp_shorten(tok, c->pool);
+
+  msg(L_INFO, "Created temp token: login=<%s> zone=<%s> temp=<%s> validity=%u", aa->user->login, aa->zone->name, shortened, validity);
+
+  cmd_ok(c);
+}
+
+static void cmd_login_fake(struct client *c, const char *passwd)
+{
+  auth_check_token(auth_fake_token, passwd);
+  cmd_error(c, "Invalid password");
+}
+
+static void cmd_login_by_temp(struct client *c, struct auth_zone *az, const char *given_passwd)
+{
+  const char *login = cmd_need_string(c, "login");
+  const char *shortened = temp_shorten(given_passwd, c->pool);
+
+  const char *reason = temp_check(az->name, login, given_passwd, c->pool);
+  if (reason)
+    {
+      msg(L_INFO, "Login failed: %s user=<%s> zone=<%s> temp=<%s>", reason, login, az->name, shortened);
+      goto reject;
+    }
+
+  /*
+   * The following checks test for improbable things like user
+   * disappearing since the token has been issued.
+   */
+
+  if (!az->max_temp_validity)
+    {
+      msg(L_INFO, "Login failed: Temporary tokens no longer accepted for zone=<%s>", az->name);
+      goto reject;
+    }
+
+  struct auth_user *au = auth_find_user(login, 0);
+  if (!au)
+    {
+      msg(L_INFO, "Login failed: No user=<%s> temp=<%s>", login, shortened);
+      goto reject;
+    }
+
+  struct auth_acct *aa = auth_find_acct(au, az, 0);
+  if (!aa)
+    {
+      msg(L_INFO, "Login failed: No account user=<%s> zone=<%s> temp=<%s>", login, az->name, shortened);
+      goto reject;
+    }
+
+  msg(L_INFO, "Login successful: user=<%s> zone=<%s> temp=<%s>", login, az->name, shortened);
+  cmd_ok(c);
+  return;
+
+reject:
+  cmd_error(c, "Temporary token refused");
+}
+
+static void cmd_login(struct client *c)
+{
+  struct auth_zone *az = cmd_need_zone(c);
+  const char *given_passwd = cmd_need_string(c, "passwd");
+
+  // Split password string to token ident and the password proper
+  char passbuf[strlen(given_passwd) + 1];
+  strcpy(passbuf, given_passwd);
+  char *ident, *passwd;
+  char *sep = strchr(passbuf, '-');
+  if (sep)
+    {
+      *sep = 0;
+      ident = passbuf;
+      passwd = sep + 1;
+    }
+  else
+    {
+      ident = NULL;
+      passwd = passbuf;
+    }
+
+  if (ident && !strcmp(ident, "t"))
+    return cmd_login_by_temp(c, az, given_passwd);
+
+  const char *login = cmd_need_string(c, "login");
+  struct auth_user *au = auth_find_user(login, 0);
+  if (!au)
+    {
+      msg(L_INFO, "Login failed: No user=<%s>", login);
+      return cmd_login_fake(c, passwd);
+    }
+
+  struct auth_acct *aa = auth_find_acct(au, az, 0);
+  if (!aa)
+    {
+      msg(L_INFO, "Login failed: No account user=<%s> zone=<%s>", login, az->name);
+      return cmd_login_fake(c, passwd);
+    }
+
+  struct auth_token *at;
+  if (ident)
+    at = auth_find_token_generated(aa, ident);
+  else
+    at = auth_find_token_passwd(aa);
+  if (!at)
+    {
+      msg(L_INFO, "Login failed: No token user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : ""));
+      return cmd_login_fake(c, passwd);
+    }
+
+  if (!auth_check_token(at, passwd))
+    {
+      msg(L_INFO, "Login failed: Wrong password for user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : ""));
+      cmd_error(c, "Invalid password");
+    }
+
+  msg(L_INFO, "Login successful: user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : ""));
+  cmd_ok(c);
+}
+
+static void cmd_list_accts(struct client *c)
+{
+  const char *login = cmd_need_target_login(c);
+
+  struct json_context *js = c->json;
+  set_string(c, c->reply, "login", login);
+  struct json_node *jas = json_new_array(js);
+  json_object_set(c->reply, "accounts", jas);
+
+  struct auth_user *au = auth_find_user(login, 0);
+  if (!au)
+    {
+      cmd_ok(c);
+      return;
+    }
+
+  CLIST_FOR_EACH(struct auth_acct *, aa, au->accounts)
+    {
+      struct json_node *ja = json_new_object(js);
+      json_array_append(jas, ja);
+      set_string(c, ja, "zone", aa->zone->name);
+
+      struct json_node *jts = json_new_array(js);
+      json_object_set(ja, "tokens", jts);
+
+      CLIST_FOR_EACH(struct auth_token *, at, aa->tokens)
+       {
+         struct json_node *jt = json_new_object(js);
+         json_array_append(jts, jt);
+
+         const char *type;
+         switch (at->type)
+           {
+           case TOKEN_PASSWORD:        type = "passwd"; break;
+           case TOKEN_GENERATED:       type = "token"; break;
+           default:                    type = "unknown"; break;
+           }
+         set_string(c, jt, "type", type);
+
+         set_string(c, jt, "ident", at->ident);
+         set_string(c, jt, "comment", at->comment);
+         set_uint(c, jt, "lastmod", at->last_modified);
+       }
+    }
+
+  cmd_ok(c);
+}
+
+static void cmd_list_zones(struct client *c)
+{
+  struct json_context *js = c->json;
+
+  struct json_node *jzs = json_new_array(js);
+  json_object_set(c->reply, "zones", jzs);
+
+  CLIST_FOR_EACH(struct auth_zone *, az, zone_list)
+    {
+      struct json_node *jz = json_new_object(js);
+      json_array_append(jzs, jz);
+      set_string(c, jz, "name", az->name);
+      set_string(c, jz, "desc", az->desc);
+      set_uint(c, jz, "allow-passwd", az->allow_passwd);
+      set_uint(c, jz, "allow-tokens", az->allow_tokens);
+    }
+
+  cmd_ok(c);
+}
+
 struct command {
   const char *cmd;
   void (*handler)(struct client *c);
@@ -39,6 +504,16 @@ struct command {
 
 static const struct command command_table[] = {
   { "nop",             cmd_nop },
+  { "create-acct",     cmd_create_acct },
+  { "delete-acct",     cmd_delete_acct },
+  { "create-token",    cmd_create_token },
+  { "delete-token",    cmd_delete_token },
+  { "set-passwd",      cmd_set_passwd },
+  { "delete-passwd",   cmd_delete_passwd },
+  { "create-temp",     cmd_create_temp },
+  { "login",           cmd_login },
+  { "list-accts",      cmd_list_accts },
+  { "list-zones",      cmd_list_zones },
 };
 
 void cmd_dispatch(struct client *c)
@@ -48,16 +523,30 @@ void cmd_dispatch(struct client *c)
 
   if (rq->type != JSON_OBJECT || !(cmd = get_string(rq, "cmd")))
     {
-      cmd_error(c, "Malformed request");
+      set_string(c, c->reply, "error", "Malformed request");
       return;
     }
 
+  const struct command *command = NULL;
   for (uint i=0; i < ARRAY_SIZE(command_table); i++)
     if (!strcmp(cmd, command_table[i].cmd))
       {
-       command_table[i].handler(c);
-       return;
+       command = &command_table[i];
+       break;
       }
+  if (!command)
+    {
+      set_string(c, c->reply, "error", "No such command");
+      return;
+    }
 
-  cmd_error(c, "No such command");
+  TRANS_TRY
+    {
+      if (command)
+       command->handler(c);
+    }
+  TRANS_CATCH(x)
+    {
+    }
+  TRANS_END;
 }