]> mj.ucw.cz Git - subauth.git/commitdiff
Account management using password authentication
authorJan Hadrava <had@kam.mff.cuni.cz>
Fri, 6 Sep 2019 21:26:24 +0000 (23:26 +0200)
committerMartin Mares <mj@ucw.cz>
Tue, 10 Sep 2019 08:29:28 +0000 (10:29 +0200)
PROTOCOL
client/subauth.1.txt
client/subauth.c
etc/subauthd
server/auth.c
server/cmd.c
server/subauthd.c
server/subauthd.h

index 9b910c4524e1628ec33ae842ef4f1b0f47bf803a..edf13691f2dfe41891487596a23c57ed0eef72dc 100644 (file)
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -12,8 +12,10 @@ Replies always have the "error" attribute set; it contains an error
 message or an empty string to indicate success.
 
 Some operations require root privileges. Other operations are
-unprivileged if no login name present, or if it matches the UID
-of the requesting user.
+unprivileged if no login name present, or if the correct "auth-passwd"
+is provided and target user has allowed administration of their
+account using a password authentication. Only regular password can be
+used for such authentication, tokens are not accepted.
 
 # No operation (unprivileged)
 {
@@ -38,6 +40,7 @@ of the requesting user.
 {
        "cmd": "create-token",
        "login": "login name",
+       "auth-passwd": "current password",
        "zone": "auth zone",
        "comment": "optional comment"
 }
@@ -51,6 +54,7 @@ of the requesting user.
 {
        "cmd": "delete-token",
        "login": "login name",
+       "auth-passwd": "current password",
        "zone": "auth zone",
        "ident": "token id"                     # "*" for all tokens for the login+zone
 }
@@ -59,6 +63,7 @@ of the requesting user.
 {
        "cmd": "change-token"
        "login": "login name",
+       "auth-passwd": "current password",
        "zone": "auth zone",
        "ident": "token id",
        "comment": "new comment"                # optional
@@ -68,6 +73,7 @@ of the requesting user.
 {
        "cmd": "set-passwd",
        "login": "login name",
+       "auth-passwd": "current password",
        "zone": "auth zone",
        "passwd": "new password"
 }
@@ -76,6 +82,7 @@ of the requesting user.
 {
        "cmd": "delete-passwd",
        "login": "login name",
+       "auth-passwd": "current password",
        "zone": "auth zone"
 }
 
@@ -83,6 +90,7 @@ of the requesting user.
 {
        "cmd": "create-temp",
        "login": "login name",
+       "auth-passwd": "current password",
        "zone": "auth zone",
        "validity": seconds             # Requested token validity
 }
@@ -99,6 +107,15 @@ of the requesting user.
        "passwd": "password or token"
 }
 
+# Allow/disallow management of selected account using password
+{
+       "cmd": "allow-passwd-auth",
+       "login": "login name",
+       "auth-passwd": "current password",
+       "zone": "auth zone",
+       "allow": integer
+}
+
 # List user's accounts and tokens
 {
        "cmd": "list-accts",
@@ -110,6 +127,7 @@ of the requesting user.
        "accounts": [
                {
                        "zone": "auth zone",
+                       "allow-passwd-auth": integer,           # Can anybody manage this account using its password?
                        "tokens": [
                                {
                                        "type": "token type",   # passwd/token
@@ -135,6 +153,7 @@ of the requesting user.
                        "desc": "human-readable description",
                        "allow-passwd": integer,                # Does the zone support passwords?
                        "allow-tokens": integer,                # Does the zone support auth tokens?
+                       "allow-passwd-auth": integer,           # Does the zone support password authentication for account management?
                        "max-temp-validity": seconds            # Maximum validity of temp tokens
                                                                # (if no temp tokens supported)
                }
index d047f55b9e7f97f9722ea45bfdcd9579468a38ca..9db0e2d24fa564d0967b93c5a0b551ba629558e2 100644 (file)
@@ -96,6 +96,15 @@ OPERATIONS
        in the given zone. Requires *--zone*. Optionally, you can specify a *--user*
        to test somebody else's credentials.
 
+*--allow-passwd-auth*::
+       Allow administration of our account in a given zone to anybody, who
+       knows our password. Requires *--zone*. Depending on the local setup,
+       this may for example enable passowrd changes from webmail interface.
+
+*--disallow-passwd-auth*::
+       Prevent other users from administration of our account in a given zone
+       using password authentication. Requires *--zone*.
+
 *Administrator commands*
 
 *--create-acct*::
@@ -131,6 +140,11 @@ OPTIONS
        which is either "*h*", "*m*", or "*s*". Maximum lifetime can be limited
        by zone configuration.
 
+*--auth-passwd*::
+       Ask for target user's account passowrd to authorize the operation.
+       Note that password authentication has to be explicitly enabled on
+       target account. Requires *--zone* and *--user*.
+
 *--socket=*'path'::
        Communicate with the subauth daemon through the given socket instead
        of the default one.
index 6b73750ee2125c27f4f4b4bd4a8c27a1562eb5c0..160c547e5925176e50694fb3cc70e6d80f7c620d 100644 (file)
@@ -30,6 +30,7 @@ static char *arg_ident;
 static char *arg_comment;
 static int debug;
 static char *arg_expire;
+static int arg_auth_passwd;
 
 /*** Communication with the daemon ***/
 
@@ -72,6 +73,11 @@ static void op_new(const char *op)
   set_string(rq, "cmd", op);
   if (arg_user)
     set_string(rq, "login", arg_user);
+  if (arg_auth_passwd)
+    {
+      char *auth_passwd = xstrdup(getpass("Current password: "));
+      set_string(rq, "auth-passwd", auth_passwd);
+    }
 }
 
 static void op_debug(const char *msg, struct json_node *n)
@@ -138,12 +144,12 @@ static void cmd_set_passwd(void)
   if (!arg_zone)
     opt_failure("--zone must be given");
 
+  op_new("set-passwd");
   char *passwd = xstrdup(getpass("New password: "));
   char *again = xstrdup(getpass("Confirm password: "));
   if (strcmp(passwd, again))
     die("Passwords do not match");
 
-  op_new("set-passwd");
   set_string(rq, "zone", arg_zone);
   set_string(rq, "passwd", passwd);
 
@@ -248,17 +254,20 @@ static void cmd_list(void)
       return;
     }
 
-  printf("%-16s %-6s %-5s %-19s  %s\n",
-    "Zone", "Type", "Ident", "Last modified", "Comment");
+  printf("%-16s %-6s %-5s %-19s %-8s  %s\n",
+    "Zone", "Type", "Ident", "Last modified", "PassAuth", "Comment");
   for (uint i=0; i < GARY_SIZE(jas); i++)
     {
       struct json_node *ja = jas[i];
       struct json_node *jz = need_child(ja, "zone", JSON_STRING);
       struct json_node **jts = need_child(ja, "tokens", JSON_ARRAY)->elements;
+      struct json_node *jpassauth = get_child(ja, "allow-passwd-auth", JSON_NUMBER);
 
       if (!GARY_SIZE(jts))
        {
-         printf("%-16s (no tokens defined)\n", jz->string);
+         printf("%-16s (no tokens defined%s)\n",
+           jz->string,
+           jpassauth ? (jpassauth->number ? ", password auth allowed" : ", password auth disabled") : "");
          continue;
        }
 
@@ -271,12 +280,13 @@ static void cmd_list(void)
          struct json_node *jlastmod = need_child(lt, "lastmod", JSON_NUMBER);
          time_t lm = jlastmod->number;
          struct tm *tm = localtime(&lm);
-         printf("%-16s %-6s %-5s %04d-%02d-%02d %02d:%02d:%02d  %s\n",
+         printf("%-16s %-6s %-5s %04d-%02d-%02d %02d:%02d:%02d %8s  %s\n",
            jz->string,
            jtype->string,
            (jident ? jident->string : "-"),
            tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
            tm->tm_hour, tm->tm_min, tm->tm_sec,
+           (jpassauth && jpassauth->number ? "yes" : "-"),
            (jcomment ? jcomment->string : ""));
        }
     }
@@ -288,7 +298,7 @@ static void cmd_zones(void)
   op_run();
 
   struct json_node **jzs = need_child(rp, "zones", JSON_ARRAY)->elements;
-  printf("%-16s %6s %6s %6s  %s\n", "Zone", "Passwd", "Tokens", "MaxTmp", "Description");
+  printf("%-16s %6s %6s %6s %8s  %s\n", "Zone", "Passwd", "Tokens", "MaxTmp", "PassAuth", "Description");
   for (uint i=0; i < GARY_SIZE(jzs); i++)
     {
       struct json_node *jz = jzs[i];
@@ -296,12 +306,14 @@ static void cmd_zones(void)
       struct json_node *jdesc = get_child(jz, "desc", JSON_STRING);
       struct json_node *jpass = get_child(jz, "allow-passwd", JSON_NUMBER);
       struct json_node *jtokens = get_child(jz, "allow-tokens", JSON_NUMBER);
+      struct json_node *jpassauth = get_child(jz, "allow-passwd-auth", JSON_NUMBER);
       struct json_node *jtemp = get_child(jz, "max-temp-validity", JSON_NUMBER);
-      printf("%-16s %6s %6d %6d  %s\n",
+      printf("%-16s %6s %6d %6d %8s  %s\n",
        jname->string,
        (jpass && jpass->number ? "yes" : "-"),
        (jtokens ? (uint) jtokens->number : 0),
        (jtemp ? (uint) jtemp->number : 0),
+       (jpassauth && jpassauth->number ? "yes" : "-"),
        (jdesc ? jdesc->string : "(no description)"));
     }
 }
@@ -368,6 +380,30 @@ static void cmd_temp_token(void)
   printf("%s\n", jt->string);
 }
 
+static void cmd_allow_passwd_auth(void)
+{
+  if (!arg_zone)
+    opt_failure("--zone must be given");
+
+  op_new("allow-passwd-auth");
+  set_string(rq, "zone", arg_zone);
+  set_uint(rq, "allow", 1);
+
+  op_run();
+}
+
+static void cmd_disallow_passwd_auth(void)
+{
+  if (!arg_zone)
+    opt_failure("--zone must be given");
+
+  op_new("allow-passwd-auth");
+  set_string(rq, "zone", arg_zone);
+  json_object_set(rq, "allow", json_new_number(js, 0));
+
+  op_run();
+}
+
 static void cmd_raw(void)
 {
   op_new("");
@@ -392,6 +428,8 @@ enum command {
   CMD_ZONES,
   CMD_LOGIN,
   CMD_TEMP_TOKEN,
+  CMD_ALLOW_PASSWD_AUTH,
+  CMD_DISALLOW_PASSWD_AUTH,
   CMD_RAW,
   CMD_MAX
 };
@@ -409,6 +447,8 @@ void (* const command_handlers[CMD_MAX])(void) = {
   [CMD_ZONES] = cmd_zones,
   [CMD_LOGIN] = cmd_login,
   [CMD_TEMP_TOKEN] = cmd_temp_token,
+  [CMD_ALLOW_PASSWD_AUTH] = cmd_allow_passwd_auth,
+  [CMD_DISALLOW_PASSWD_AUTH] = cmd_disallow_passwd_auth,
   [CMD_RAW] = cmd_raw,
 };
 
@@ -427,15 +467,17 @@ static const struct opt_section options = {
     OPT_HELP_OPTION,
     OPT_HELP(""),
     OPT_HELP("User commands:"),
-    OPT_SWITCH('l', "list",            command, CMD_LIST,              OPT_SINGLE, "\tList tokens for all zones"),
-    OPT_SWITCH(0,   "zones",           command, CMD_ZONES,             OPT_SINGLE, "\tList all known zones"),
-    OPT_SWITCH(0,   "passwd",          command, CMD_SET_PASSWD,        OPT_SINGLE, "\tSet password"),
-    OPT_SWITCH(0,   "delete-passwd",   command, CMD_DELETE_PASSWD,     OPT_SINGLE, "\tRemove password"),
-    OPT_SWITCH(0,   "create-token",    command, CMD_CREATE_TOKEN,      OPT_SINGLE, "\tCreate a new token"),
-    OPT_SWITCH(0,   "delete-token",    command, CMD_DELETE_TOKEN,      OPT_SINGLE, "\tRemove an existing token"),
-    OPT_SWITCH(0,   "change-token",    command, CMD_CHANGE_TOKEN,      OPT_SINGLE, "\tChange the comment of an existing token"),
-    OPT_SWITCH(0,   "login",           command, CMD_LOGIN,             OPT_SINGLE, "\tTry to log in"),
-    OPT_SWITCH(0,   "temp-token",      command, CMD_TEMP_TOKEN,        OPT_SINGLE, "\tCreate a temporary token"),
+    OPT_SWITCH('l', "list",                    command, CMD_LIST,                      OPT_SINGLE, "\tList tokens for all zones"),
+    OPT_SWITCH(0,   "zones",                   command, CMD_ZONES,                     OPT_SINGLE, "\tList all known zones"),
+    OPT_SWITCH(0,   "passwd",                  command, CMD_SET_PASSWD,                OPT_SINGLE, "\tSet password"),
+    OPT_SWITCH(0,   "delete-passwd",           command, CMD_DELETE_PASSWD,             OPT_SINGLE, "\tRemove password"),
+    OPT_SWITCH(0,   "create-token",            command, CMD_CREATE_TOKEN,              OPT_SINGLE, "\tCreate a new token"),
+    OPT_SWITCH(0,   "delete-token",            command, CMD_DELETE_TOKEN,              OPT_SINGLE, "\tRemove an existing token"),
+    OPT_SWITCH(0,   "change-token",            command, CMD_CHANGE_TOKEN,              OPT_SINGLE, "\tChange the comment of an existing token"),
+    OPT_SWITCH(0,   "login",                   command, CMD_LOGIN,                     OPT_SINGLE, "\tTry to log in"),
+    OPT_SWITCH(0,   "temp-token",              command, CMD_TEMP_TOKEN,                OPT_SINGLE, "\tCreate a temporary token"),
+    OPT_SWITCH(0,   "allow-passwd-auth",       command, CMD_ALLOW_PASSWD_AUTH,         OPT_SINGLE, "\tAllow management using password authentication"),
+    OPT_SWITCH(0,   "disallow-passwd-auth",    command, CMD_DISALLOW_PASSWD_AUTH,      OPT_SINGLE, "\tDisallow account management"),
     OPT_HELP(""),
     OPT_HELP("Administrative commands:"),
     OPT_SWITCH(0,   "create-acct",     command, CMD_CREATE_ACCT,       OPT_SINGLE, "\tCreate an account"),
@@ -449,6 +491,7 @@ static const struct opt_section options = {
     OPT_STRING('i', "ident", arg_ident, OPT_REQUIRED_VALUE, "id\tToken ID ('*' for all tokens in zone)"),
     OPT_STRING('c', "comment", arg_comment, OPT_REQUIRED_VALUE, "string\tHuman-readable description of token"),
     OPT_STRING('x', "expire", arg_expire, OPT_REQUIRED_VALUE, "n(h|m|s)\tSet expiration time of a temporary token (default: 5m)"),
+    OPT_BOOL(0, "auth-passwd", arg_auth_passwd, 0, "\tAsk for current password to authorize the operation"),
     OPT_END
   }
 };
index 4c70930782edd5dabaa2504a9e1fcdff03fabd85..2c50b4363dec5251ef229d6a58b9c5b182935258 100644 (file)
@@ -42,6 +42,9 @@ SubauthD {
                # Allow users to set a password
                AllowPasswd     1
 
+               # Allow users to manage account without shell access
+               AllowPasswdAuth 0
+
                # Allow users to create a token, set maximum number of tokens per user
                AllowTokens     16
 
index 2205c2e9e52a0ca5e5ce7d368b7b03683debb7fc..257b560ce2c39726a95743a98f33a8afad48fa16 100644 (file)
@@ -264,6 +264,11 @@ static void db_parse_user(struct json_node *ju)
        die("Database defines accounts in zone %s, which is not configured", zone_name);
       clist_init(&aa->tokens);
 
+      uint allow_passwd_auth;
+      if (!get_uint(ja, "p", &allow_passwd_auth))
+       allow_passwd_auth = 0;
+      aa->allow_passwd_auth = allow_passwd_auth;
+
       struct json_node **jts = get_array(ja, "t");
       if (jts)
        {
@@ -375,6 +380,7 @@ void db_write(void)
          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));
+         json_object_set(ja, "p", json_new_number(js, aa->allow_passwd_auth));
          struct json_node *jts = json_new_array(js);
          CLIST_FOR_EACH(struct auth_token *, at, aa->tokens)
            {
index 8959d078c8d94e52871e5b28c646be9bfbc0d43f..45873eda3b3ed48ba625c71c9508a82313b3ffa5 100644 (file)
@@ -91,6 +91,14 @@ static const char *cmd_need_string(struct client *c, const char *key)
   return val;
 }
 
+static uint cmd_need_uint(struct client *c, const char *key)
+{
+  uint val = 0;
+  if (!get_uint(c->request, key, &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");
@@ -128,8 +136,64 @@ static const char *cmd_need_target_login(struct client *c)
     }
 }
 
+static void cmd_passwd_auth_fake(struct client *c, const char *passwd)
+{
+  auth_check_token(auth_fake_token, passwd);
+  cmd_error(c, "Invalid password");
+}
+
+static struct auth_acct *cmd_need_passwd_auth_acct(struct client *c, const char *passwd)
+{
+  struct auth_zone *az = cmd_need_zone(c);
+  const char *login = cmd_need_string(c, "login");
+  if (!az->allow_passwd_auth || !az->allow_passwd)
+    {
+      msg(L_INFO, "Password authentication denied by zone configuration: login=<%s> zone=<%s> uid=<%u>", login, az->name, c->uid);
+      cmd_error(c, "This zone does not allow password authentication for management");
+    }
+
+  struct auth_user *au = auth_find_user(login, 0);
+  if (!au)
+    {
+      msg(L_INFO, "Password authentication failed: No user=<%s> uid=<%u>", login, c->uid);
+      cmd_passwd_auth_fake(c, passwd);
+    }
+
+  struct auth_acct *aa = auth_find_acct(au, az, 0);
+  if (!aa)
+    {
+      msg(L_INFO, "Password authentication failed: No account user=<%s> zone=<%s> uid=<%u>", login, az->name, c->uid);
+      cmd_passwd_auth_fake(c, passwd);
+    }
+  if (!aa->allow_passwd_auth)
+    {
+      msg(L_INFO, "Password authentication failed: Not allowed by user=<%s> zone=<%s> uid=<%u>", login, az->name, c->uid);
+      cmd_passwd_auth_fake(c, passwd);
+    }
+
+  struct auth_token *at = auth_find_token_passwd(aa);
+  if (!at)
+    {
+      msg(L_INFO, "Password authentication failed: No password user=<%s> zone=<%s> uid=<%u>", login, az->name, c->uid);
+      cmd_passwd_auth_fake(c, passwd);
+    }
+
+  if (!auth_check_token(at, passwd))
+    {
+      msg(L_INFO, "Password authentication failed: Wrong password for user=<%s> zone=<%s> uid=<%u>", login, az->name, c->uid);
+      cmd_error(c, "Invalid password");
+    }
+
+  msg(L_INFO, "Password authentication successful: user=<%s> zone=<%s> uid=<%u>", login, az->name, c->uid);
+  return aa;
+}
+
 static struct auth_acct *cmd_need_target_acct(struct client *c)
 {
+  const char *auth_passwd = get_string(c->request, "auth-passwd");
+  if (auth_passwd)
+    return cmd_need_passwd_auth_acct(c, auth_passwd);
+
   const char *login = cmd_need_target_login(c);
   struct auth_zone *az = cmd_need_zone(c);
 
@@ -453,6 +517,21 @@ static void cmd_login(struct client *c)
   cmd_ok(c);
 }
 
+static void cmd_allow_passwd_auth(struct client *c)
+{
+  struct auth_acct *aa = cmd_need_target_acct(c);
+  uint allow = cmd_need_uint(c, "allow");
+  if (!aa->zone->allow_passwd_auth)
+    cmd_error(c, "This zone does not allow password authentication for management");
+
+  msg(L_INFO, "Setting account password authentication: allow-passwd-auth=<%u> login=<%s> zone=<%s>", !!allow, aa->user->login, aa->zone->name);
+
+  aa->allow_passwd_auth = !!allow;
+
+  db_write();
+  cmd_ok(c);
+}
+
 static void cmd_list_accts(struct client *c)
 {
   const char *login = cmd_need_target_login(c);
@@ -474,6 +553,7 @@ static void cmd_list_accts(struct client *c)
       struct json_node *ja = json_new_object(js);
       json_array_append(jas, ja);
       set_string(c, ja, "zone", aa->zone->name);
+      set_uint(c, ja, "allow-passwd-auth", aa->zone->allow_passwd_auth && aa->allow_passwd_auth);
 
       struct json_node *jts = json_new_array(js);
       json_object_set(ja, "tokens", jts);
@@ -516,6 +596,7 @@ static void cmd_list_zones(struct client *c)
       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);
+      set_uint(c, jz, "allow-passwd-auth", az->allow_passwd_auth);
       set_uint(c, jz, "max-temp-validity", az->max_temp_validity);
     }
 
@@ -528,18 +609,19 @@ 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 },
-  { "change-token",    cmd_change_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 },
+  { "nop",                     cmd_nop },
+  { "create-acct",             cmd_create_acct },
+  { "delete-acct",             cmd_delete_acct },
+  { "create-token",            cmd_create_token },
+  { "delete-token",            cmd_delete_token },
+  { "change-token",            cmd_change_token },
+  { "set-passwd",              cmd_set_passwd },
+  { "delete-passwd",           cmd_delete_passwd },
+  { "create-temp",             cmd_create_temp },
+  { "login",                   cmd_login },
+  { "allow-passwd-auth",       cmd_allow_passwd_auth },
+  { "list-accts",              cmd_list_accts },
+  { "list-zones",              cmd_list_zones },
 };
 
 void cmd_dispatch(struct client *c)
index a8576123df292cee4c8e2068b5ef7c2fb05a3dcf..31753c97a98dafbbfaeaed417c82df01832f9298 100644 (file)
@@ -324,6 +324,7 @@ static struct cf_section zone_config = {
     CF_STRING("Description", PTR_TO(struct auth_zone, desc)),
     CF_UINT("AutoCreateAcct", PTR_TO(struct auth_zone, auto_create_acct)),
     CF_UINT("AllowPasswd", PTR_TO(struct auth_zone, allow_passwd)),
+    CF_UINT("AllowPasswdAuth", PTR_TO(struct auth_zone, allow_passwd_auth)),
     CF_UINT("AllowTokens", PTR_TO(struct auth_zone, allow_tokens)),
     CF_UINT("MaxTempValidity", PTR_TO(struct auth_zone, max_temp_validity)),
     CF_END
index a2ca09957f0ec95c30d5690f288752d0927dbe28..3af859a47dd3eb249bf2447b47c6030c1f3ede70 100644 (file)
@@ -56,6 +56,7 @@ struct auth_zone {
   uint auto_create_acct;
   uint allow_passwd;
   uint allow_tokens;
+  uint allow_passwd_auth;
   uint max_temp_validity;
 };
 
@@ -69,6 +70,7 @@ struct auth_acct {
   struct auth_user *user;
   struct auth_zone *zone;
   clist tokens;                                // of struct auth_token
+  uint allow_passwd_auth;
 };
 
 enum token_type {