+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);
+}
+