From 180e3805ab5eaa0aabad4a8830c6388041d4a30b Mon Sep 17 00:00:00 2001 From: Jan Hadrava Date: Fri, 6 Sep 2019 23:26:24 +0200 Subject: [PATCH] Account management using password authentication --- PROTOCOL | 23 +++++++++- client/subauth.1.txt | 14 ++++++ client/subauth.c | 75 +++++++++++++++++++++++------- etc/subauthd | 3 ++ server/auth.c | 6 +++ server/cmd.c | 106 ++++++++++++++++++++++++++++++++++++++----- server/subauthd.c | 1 + server/subauthd.h | 2 + 8 files changed, 200 insertions(+), 30 deletions(-) diff --git a/PROTOCOL b/PROTOCOL index 9b910c4..edf1369 100644 --- 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) } diff --git a/client/subauth.1.txt b/client/subauth.1.txt index d047f55..9db0e2d 100644 --- a/client/subauth.1.txt +++ b/client/subauth.1.txt @@ -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. diff --git a/client/subauth.c b/client/subauth.c index 6b73750..160c547 100644 --- a/client/subauth.c +++ b/client/subauth.c @@ -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 } }; diff --git a/etc/subauthd b/etc/subauthd index 4c70930..2c50b43 100644 --- a/etc/subauthd +++ b/etc/subauthd @@ -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 diff --git a/server/auth.c b/server/auth.c index 2205c2e..257b560 100644 --- a/server/auth.c +++ b/server/auth.c @@ -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) { diff --git a/server/cmd.c b/server/cmd.c index 8959d07..45873ed 100644 --- a/server/cmd.c +++ b/server/cmd.c @@ -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) diff --git a/server/subauthd.c b/server/subauthd.c index a857612..31753c9 100644 --- a/server/subauthd.c +++ b/server/subauthd.c @@ -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 diff --git a/server/subauthd.h b/server/subauthd.h index a2ca099..3af859a 100644 --- a/server/subauthd.h +++ b/server/subauthd.h @@ -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 { -- 2.39.2