X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/5b3f0631ed2aff1d83499e5daa148dafc345f741..91d9a42d3d2a8382f1b3729b2f4c6c2aafe4126d:/lib/trackdb.c diff --git a/lib/trackdb.c b/lib/trackdb.c index 46a875c..f5d5836 100644 --- a/lib/trackdb.c +++ b/lib/trackdb.c @@ -17,7 +17,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ -/** @file server/trackdb.c +/** @file lib/trackdb.c * @brief Track database * * This file is getting in desparate need of splitting up... @@ -64,7 +64,7 @@ #include "hash.h" #include "unicode.h" #include "unidata.h" -#include "mime.h" +#include "base64.h" #define RESCAN "disorder-rescan" #define DEADLOCK "disorder-deadlock" @@ -113,7 +113,7 @@ DB *trackdb_prefsdb; * - Values are UTF-8(NFC(unicode(path name))) * - There can be more than one value per key * - Presence of key,value means that path matches the search terms - * - Only tracks fond in @ref tracks_tracksdb are represented here + * - Only tracks fond in @ref trackdb_tracksdb are represented here * - This database can be reconstructed, it contains no user data */ DB *trackdb_searchdb; @@ -259,10 +259,23 @@ static int reap_db_deadlock(ev_source attribute((unused)) *ev, return 0; } -static pid_t subprogram(ev_source *ev, const char *prog, - int outputfd) { +static pid_t subprogram(ev_source *ev, int outputfd, const char *prog, + ...) { pid_t pid; - + va_list ap; + const char *args[1024], **argp, *a; + + argp = args; + *argp++ = prog; + *argp++ = "--config"; + *argp++ = configfile; + *argp++ = debugging ? "--debug" : "--no-debug"; + *argp++ = log_default == &log_syslog ? "--syslog" : "--no-syslog"; + va_start(ap, prog); + while((a = va_arg(ap, const char *))) + *argp++ = a; + va_end(ap); + *argp = 0; /* If we're in the background then trap subprocess stdout/stderr */ if(!(pid = xfork())) { exitfn = _exit; @@ -279,10 +292,7 @@ static pid_t subprogram(ev_source *ev, const char *prog, /* If we were negatively niced, undo it. We don't bother checking for * error, it's not that important. */ setpriority(PRIO_PROCESS, 0, 0); - execlp(prog, prog, "--config", configfile, - debugging ? "--debug" : "--no-debug", - log_default == &log_syslog ? "--syslog" : "--no-syslog", - (char *)0); + execvp(prog, (char **)args); fatal(errno, "error invoking %s", prog); } return pid; @@ -291,7 +301,7 @@ static pid_t subprogram(ev_source *ev, const char *prog, /* start deadlock manager */ void trackdb_master(ev_source *ev) { assert(db_deadlock_pid == -1); - db_deadlock_pid = subprogram(ev, DEADLOCK, -1); + db_deadlock_pid = subprogram(ev, -1, DEADLOCK, (char *)0); ev_child(ev, db_deadlock_pid, 0, reap_db_deadlock, 0); D(("started deadlock manager")); } @@ -349,7 +359,7 @@ static DB *open_db(const char *path, } /** @brief Open track databases - * @param Flags flags word + * @param flags Flags flags word * * @p flags should have one of: * - @p TRACKDB_NO_UPGRADE, if no upgrade should be attempted @@ -396,7 +406,7 @@ void trackdb_open(int flags) { /* This database needs upgrading */ info("invoking disorder-dbupgrade to upgrade from %ld to %ld", oldversion, config->dbversion); - pid = subprogram(0, "disorder-dbupgrade", -1); + pid = subprogram(0, -1, "disorder-dbupgrade", (char *)0); while(waitpid(pid, &err, 0) == -1 && errno == EINTR) ; if(err) @@ -1353,7 +1363,7 @@ void trackdb_stats_subprocess(ev_source *ev, d->done = done; d->u = u; xpipe(p); - pid = subprogram(ev, "disorder-stats", p[1]); + pid = subprogram(ev, p[1], "disorder-stats", (char *)0); xclose(p[1]); ev_child(ev, pid, 0, stats_finished, d); ev_reader_new(ev, p[0], stats_read, stats_error, d, "disorder-stats reader"); @@ -2146,14 +2156,20 @@ static int reap_rescan(ev_source attribute((unused)) *ev, return 0; } -void trackdb_rescan(ev_source *ev) { +/** @brief Initiate a rescan + * @param ev Event loop or 0 to block + * @param check 1 to recheck lengths, 0 to suppress check + */ +void trackdb_rescan(ev_source *ev, int check) { int w; if(rescan_pid != -1) { error(0, "rescan already underway"); return; } - rescan_pid = subprogram(ev, RESCAN, -1); + rescan_pid = subprogram(ev, -1, RESCAN, + check ? "--check" : "--no-check", + (char *)0); if(ev) { ev_child(ev, rescan_pid, 0, reap_rescan, 0); D(("started rescanner")); @@ -2421,22 +2437,56 @@ static int trusted(const char *user) { return n < config->trust.n; } +/** @brief Return non-zero for a valid username + * + * Currently we only allow the letters and digits in ASCII. We could be more + * liberal than this but it is a nice simple test. It is critical that + * semicolons are never allowed. + */ +static int valid_username(const char *user) { + if(!*user) + return 0; + while(*user) { + const uint8_t c = *user++; + /* For now we are very strict */ + if((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9')) + /* ok */; + else + return 0; + } + return 1; +} + /** @brief Add a user */ static int create_user(const char *user, const char *password, const char *rights, const char *email, + const char *confirmation, DB_TXN *tid, uint32_t flags) { struct kvp *k = 0; char s[64]; + /* sanity check user */ + if(!valid_username(user)) { + error(0, "invalid username '%s'", user); + return -1; + } + if(parse_rights(rights, 0, 1)) { + error(0, "invalid rights string"); + return -1; + } /* data for this user */ if(password) kvp_set(&k, "password", password); kvp_set(&k, "rights", rights); if(email) kvp_set(&k, "email", email); + if(confirmation) + kvp_set(&k, "confirmation", confirmation); snprintf(s, sizeof s, "%jd", (intmax_t)time(0)); kvp_set(&k, "created", s); return trackdb_putdata(trackdb_usersdb, user, k, tid, flags); @@ -2455,11 +2505,18 @@ static int one_old_user(const char *user, const char *password, /* pick rights */ if(!strcmp(user, "root")) rights = "all"; - else if(trusted(user)) - rights = rights_string(config->default_rights|RIGHT_ADMIN|RIGHT_RESCAN); - else - rights = rights_string(config->default_rights); - return create_user(user, password, rights, 0/*email*/, tid, DB_NOOVERWRITE); + else if(trusted(user)) { + rights_type r; + + parse_rights(config->default_rights, &r, 1); + r &= ~(rights_type)(RIGHT_SCRATCH__MASK|RIGHT_MOVE__MASK|RIGHT_REMOVE__MASK); + r |= (RIGHT_ADMIN|RIGHT_RESCAN + |RIGHT_SCRATCH_ANY|RIGHT_MOVE_ANY|RIGHT_REMOVE_ANY); + rights = rights_string(r); + } else + rights = config->default_rights; + return create_user(user, password, rights, 0/*email*/, 0/*confirmation*/, + tid, DB_NOOVERWRITE); } static int trackdb_old_users_tid(DB_TXN *tid) { @@ -2502,8 +2559,9 @@ void trackdb_create_root(void) { 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)); + WITH_TRANSACTION(create_user("root", pw, "all", + 0/*email*/, 0/*confirmation*/, + tid, DB_NOOVERWRITE)); if(e == 0) info("created root user"); } @@ -2532,34 +2590,35 @@ const char *trackdb_get_password(const char *user) { * @param user Username * @param password Password or NULL * @param rights Initial rights - * @param email Email address + * @param email Email address or NULL + * @param confirmation Confirmation string or NULL * @return 0 on success, non-0 on error */ int trackdb_adduser(const char *user, const char *password, - rights_type rights, - const char *email) { + const char *rights, + const char *email, + const char *confirmation) { int e; - const char *r = rights_string(rights); - WITH_TRANSACTION(create_user(user, password, r, email, + WITH_TRANSACTION(create_user(user, password, rights, email, confirmation, tid, DB_NOOVERWRITE)); if(e) { - error(0, "cannot created user '%s' because they already exist", user); + error(0, "cannot create 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); + user, rights, email); else - info("created user '%s' with rights '%s'", user, r); + info("created user '%s' with rights '%s'", user, rights); return 0; } } /** @brief Delete a user * @param user User to delete - * @param 0 on success, non-0 if the user didn't exist anyway + * @return 0 on success, non-0 if the user didn't exist anyway */ int trackdb_deluser(const char *user) { int e; @@ -2625,7 +2684,7 @@ int trackdb_edituserinfo(const char *user, error(0, "cannot remove 'rights' key from user '%s'", user); return -1; } - if(parse_rights(value, 0)) { + if(parse_rights(value, 0, 1)) { error(0, "invalid rights string"); return -1; } @@ -2662,6 +2721,66 @@ char **trackdb_listusers(void) { return v->vec; } +/** @brief Confirm a user registration + * @param user Username + * @param confirmation Confirmation string + * @param rightsp Where to put user rights + * @param tid Transaction ID + * @return 0 on success, non-0 on error + */ +static int trackdb_confirm_tid(const char *user, const char *confirmation, + rights_type *rightsp, + DB_TXN *tid) { + const char *stored_confirmation; + struct kvp *k; + int e; + const char *rights; + + if((e = trackdb_getdata(trackdb_usersdb, user, &k, tid))) + return e; + if(!(stored_confirmation = kvp_get(k, "confirmation"))) { + error(0, "already confirmed user '%s'", user); + /* DB claims -30,800 to -30,999 so -1 should be a safe bet */ + return -1; + } + if(!(rights = kvp_get(k, "rights"))) { + error(0, "no rights for unconfirmed user '%s'", user); + return -1; + } + if(parse_rights(rights, rightsp, 1)) + return -1; + if(strcmp(confirmation, stored_confirmation)) { + error(0, "wrong confirmation string for user '%s'", user); + return -1; + } + /* 'sall good */ + kvp_set(&k, "confirmation", 0); + return trackdb_putdata(trackdb_usersdb, user, k, tid, 0); +} + +/** @brief Confirm a user registration + * @param user Username + * @param confirmation Confirmation string + * @param rightsp Where to put user rights + * @return 0 on success, non-0 on error + */ +int trackdb_confirm(const char *user, const char *confirmation, + rights_type *rightsp) { + int e; + + WITH_TRANSACTION(trackdb_confirm_tid(user, confirmation, rightsp, tid)); + switch(e) { + case 0: + info("registration confirmed for user '%s'", user); + return 0; + case DB_NOTFOUND: + error(0, "confirmation for nonexistent user '%s'", user); + return -1; + default: /* already reported */ + return -1; + } +} + /* Local Variables: c-basic-offset:2