+/* user database *************************************************************/
+
+/** @brief Return true if @p user is trusted */
+static int trusted(const char *user) {
+ int n;
+
+ for(n = 0; (n < config->trust.n
+ && strcmp(config->trust.s[n], user)); ++n)
+ ;
+ return n < config->trust.n;
+}
+
+static const struct {
+ rights_type bit;
+ const char *name;
+} rights_names[] = {
+ { RIGHT_READ, "read" },
+ { RIGHT_PLAY, "play" },
+ { RIGHT_MOVE_ANY, "move any" },
+ { RIGHT_MOVE_MINE, "move mine" },
+ { RIGHT_MOVE_RANDOM, "move random" },
+ { RIGHT_REMOVE_ANY, "remove any" },
+ { RIGHT_REMOVE_MINE, "remove mine" },
+ { RIGHT_REMOVE_RANDOM, "remove random" },
+ { RIGHT_SCRATCH_ANY, "scratch any" },
+ { RIGHT_SCRATCH_MINE, "scratch mine" },
+ { RIGHT_SCRATCH_RANDOM, "scratch random" },
+ { RIGHT_VOLUME, "volume" },
+ { RIGHT_ADMIN, "admin" },
+ { RIGHT_RESCAN, "rescan" },
+ { RIGHT_REGISTER, "register" },
+ { RIGHT_USERINFO, "userinfo" },
+ { RIGHT_PREFS, "prefs" },
+ { RIGHT_GLOBAL_PREFS, "global prefs" }
+};
+#define NRIGHTS (sizeof rights_names / sizeof *rights_names)
+
+/** @brief Convert a rights word to a string */
+static char *rights_string(rights_type r) {
+ struct dynstr d[1];
+ size_t n;
+
+ dynstr_init(d);
+ for(n = 0; n < NRIGHTS; ++n) {
+ if(r & rights_names[n].bit) {
+ if(d->nvec)
+ dynstr_append(d, ',');
+ dynstr_append_string(d, rights_names[n].name);
+ }
+ }
+ dynstr_terminate(d);
+ return d->vec;
+}
+
+/** @brief Compute default rights for a new user */
+rights_type default_rights(void) {
+ /* TODO get rights from config. This is probably in the wrong place but it
+ * will do for now... */
+ rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
+ |RIGHT_MOVE__MASK
+ |RIGHT_SCRATCH__MASK
+ |RIGHT_REMOVE__MASK);
+ if(config->restrictions & RESTRICT_SCRATCH)
+ r |= RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_RANDOM;
+ else
+ r |= RIGHT_SCRATCH_ANY;
+ if(!(config->restrictions & RESTRICT_MOVE))
+ r |= RIGHT_MOVE_ANY;
+ if(config->restrictions & RESTRICT_REMOVE)
+ r |= RIGHT_REMOVE_MINE;
+ else
+ r |= RIGHT_REMOVE_ANY;
+ return r;
+}
+
+/** @brief Add a user */
+static int create_user(const char *user,
+ const char *password,
+ const char *rights,
+ const char *email,
+ DB_TXN *tid,
+ uint32_t flags) {
+ struct kvp *k = 0;
+ char s[64];
+
+ /* data for this user */
+ if(password)
+ kvp_set(&k, "password", password);
+ kvp_set(&k, "rights", rights);
+ if(email)
+ kvp_set(&k, "email", email);
+ snprintf(s, sizeof s, "%jd", (intmax_t)time(0));
+ kvp_set(&k, "created", s);
+ return trackdb_putdata(trackdb_usersdb, user, k, tid, flags);
+}
+
+/** @brief Add one pre-existing user */
+static int one_old_user(const char *user, const char *password,
+ DB_TXN *tid) {
+ const char *rights;
+
+ /* www-data doesn't get added */
+ if(!strcmp(user, "www-data")) {
+ info("not adding www-data to user database");
+ return 0;
+ }
+ /* pick rights */
+ if(!strcmp(user, "root"))
+ rights = "all";
+ else if(trusted(user))
+ rights = rights_string(default_rights()|RIGHT_ADMIN);
+ else
+ rights = rights_string(default_rights());
+ return create_user(user, password, rights, 0/*email*/, tid, DB_NOOVERWRITE);
+}
+
+static int trackdb_old_users_tid(DB_TXN *tid) {
+ int n;
+
+ for(n = 0; n < config->allow.n; ++n) {
+ switch(one_old_user(config->allow.s[n].s[0], config->allow.s[n].s[1],
+ tid)) {
+ case 0:
+ info("created user %s from 'allow' directive", config->allow.s[n].s[0]);
+ break;
+ case DB_KEYEXIST:
+ error(0, "user %s already exists, delete 'allow' directive",
+ config->allow.s[n].s[0]);
+ /* This won't ever become fatal - eventually 'allow' will be
+ * disabled. */
+ break;
+ case DB_LOCK_DEADLOCK:
+ return DB_LOCK_DEADLOCK;
+ }
+ }
+ return 0;
+}
+
+/** @brief Read old 'allow' directives and copy them to the users database */
+void trackdb_old_users(void) {
+ int e;
+
+ if(config->allow.n)
+ WITH_TRANSACTION(trackdb_old_users_tid(tid));
+}
+
+/** @brief Create a root user in the user database if there is none */
+void trackdb_create_root(void) {
+ int e;
+ uint8_t pwbin[12];
+ char *pw;
+
+ /* Choose a new root password */
+ gcry_randomize(pwbin, sizeof pwbin, GCRY_STRONG_RANDOM);
+ pw = mime_to_base64(pwbin, sizeof pwbin);
+ /* Create the root user if it does not exist */
+ WITH_TRANSACTION(create_user("root", pw, "all", 0/*email*/, tid,
+ DB_NOOVERWRITE));
+ if(e == 0)
+ info("created root user");
+}
+
+/** @brief Find a user's password from the database
+ * @param user Username
+ * @return Password or NULL
+ *
+ * Only works if running as a user that can read the database!
+ *
+ * If the user exists but has no password, "" is returned.
+ */
+const char *trackdb_get_password(const char *user) {
+ int e;
+ struct kvp *k;
+ const char *password;
+
+ WITH_TRANSACTION(trackdb_getdata(trackdb_usersdb, user, &k, tid));
+ if(e)
+ return 0;
+ password = kvp_get(k, "password");
+ return password ? password : "";
+}
+
+/** @brief Add a new user
+ * @param user Username
+ * @param password Password or NULL
+ * @param rights Initial rights
+ * @param email Email address
+ * @return 0 on success, non-0 on error
+ */
+int trackdb_adduser(const char *user,
+ const char *password,
+ rights_type rights,
+ const char *email) {
+ int e;
+ const char *r = rights_string(rights);
+
+ WITH_TRANSACTION(create_user(user, password, r, email,
+ tid, DB_NOOVERWRITE));
+ if(e) {
+ error(0, "cannot created user '%s' because they already exist", user);
+ return -1;
+ } else {
+ if(email)
+ info("created user '%s' with rights '%s' and email address '%s'",
+ user, r, email);
+ else
+ info("created user '%s' with rights '%s'", user, r);
+ return 0;
+ }
+}
+
+/** @brief Delete a user
+ * @param user User to delete
+ * @param 0 on success, non-0 if the user didn't exist anyway
+ */
+int trackdb_deluser(const char *user) {
+ int e;
+
+ WITH_TRANSACTION(trackdb_delkey(trackdb_usersdb, user, tid));
+ if(e) {
+ error(0, "cannot delete user '%s' because they do not exist", user);
+ return -1;
+ }
+ info("deleted user '%s'", user);
+ return 0;
+}
+
+/** @brief Get user information
+ * @param user User to query
+ * @return Linked list of user information or NULL if user does not exist
+ *
+ * Every user has at least a @c rights entry so NULL can be used to mean no
+ * such user safely.
+ */
+struct kvp *trackdb_getuserinfo(const char *user) {
+ int e;
+ struct kvp *k;
+
+ WITH_TRANSACTION(trackdb_getdata(trackdb_usersdb, user, &k, tid));
+ if(e)
+ return 0;
+ else
+ return k;
+}
+
+/** @brief Edit user information
+ * @param user User to edit
+ * @param key Key to change
+ * @param value Value to set, or NULL to remove
+ * @param tid Transaction ID
+ * @return 0, DB_LOCK_DEADLOCK or DB_NOTFOUND
+ */
+static int trackdb_edituserinfo_tid(const char *user, const char *key,
+ const char *value, DB_TXN *tid) {
+ struct kvp *k;
+ int e;
+
+ if((e = trackdb_getdata(trackdb_usersdb, user, &k, tid)))
+ return e;
+ if(!kvp_set(&k, key, value))
+ return 0; /* no change */
+ return trackdb_putdata(trackdb_usersdb, user, k, tid, 0);
+}
+
+/** @brief Edit user information
+ * @param user User to edit
+ * @param key Key to change
+ * @param value Value to set, or NULL to remove
+ * @return 0 on success, non-0 on error
+ */
+int trackdb_edituserinfo(const char *user,
+ const char *key, const char *value) {
+ int e;
+
+ WITH_TRANSACTION(trackdb_edituserinfo_tid(user, key, value, tid));
+ if(e)
+ return -1;
+ else
+ return 0;
+}
+