From ba39faf632da43d64106536f256153c2092346e4 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Fri, 21 Dec 2007 20:24:54 +0000 Subject: [PATCH] register/confirm commands, and tests, and docs Organization: Straylight/Edgeware From: Richard Kettlewell --- doc/disorder_protocol.5.in | 9 ++++ lib/client.c | 12 ++++++ lib/client.h | 4 ++ lib/trackdb.c | 86 +++++++++++++++++++++++++++++++++++--- lib/trackdb.h | 4 +- python/disorder.py.in | 9 ++++ server/server.c | 47 ++++++++++++++++++++- tests/user.py | 16 +++++++ 8 files changed, 179 insertions(+), 8 deletions(-) diff --git a/doc/disorder_protocol.5.in b/doc/disorder_protocol.5.in index fa98a81..a1a4af2 100644 --- a/doc/disorder_protocol.5.in +++ b/doc/disorder_protocol.5.in @@ -58,6 +58,10 @@ Creates a new user with the given username and password. Requires the Lists all the files and directories in \fIDIRECTORY\fR in a response body. If \fIREGEXP\fR is present only matching files and directories are returned. .TP +.B confirm \fICONFIRMATION +Confirm user registration. \fICONFIRMATION\fR is as returned from +\fBregister\fR below. This command can be used without logging in. +.TP .B cookie \fICOOKIE Log a user back in using a cookie created with \fBmake-cookie\fR. .TP @@ -210,6 +214,11 @@ information syntax. Request that DisOrder reconfigure itself. Requires the \fBadmin\fR right. command. .TP +.B register \fIUSER PASSWORD EMAIL +Register a new user. Requires the \fBregister\fR right. The result contains a +confirmation string; the user will be be able to log in until this has been +presented back to the server via the \fBconfirm\fR command. +.TP .B remove \fIID\fR Remove the track identified by \fIID\fR. Requires one of the \fBremove mine\fR, \fBremove random\fR or \fBremove any\fR rights depending on how the diff --git a/lib/client.c b/lib/client.c index 739fc20..39272e6 100644 --- a/lib/client.c +++ b/lib/client.c @@ -697,6 +697,18 @@ int disorder_edituser(disorder_client *c, const char *user, return disorder_simple(c, 0, "edituser", user, key, value, (char *)0); } +int disorder_register(disorder_client *c, const char *user, + const char *password, const char *email, + char **confirmp) { + return dequote(disorder_simple(c, confirmp, "register", + user, password, email, (char *)0), + confirmp); +} + +int disorder_confirm(disorder_client *c, const char *confirm) { + return disorder_simple(c, 0, "confirm", confirm, (char *)0); +} + /* Local Variables: c-basic-offset:2 diff --git a/lib/client.h b/lib/client.h index f336de6..c685b3d 100644 --- a/lib/client.h +++ b/lib/client.h @@ -197,6 +197,10 @@ int disorder_edituser(disorder_client *c, const char *user, const char *key, const char *value); int disorder_users(disorder_client *c, char ***vecp, int *nvecp); +int disorder_register(disorder_client *c, const char *user, + const char *password, const char *email, + char **confirmp); +int disorder_confirm(disorder_client *c, const char *confirm); #endif /* CLIENT_H */ diff --git a/lib/trackdb.c b/lib/trackdb.c index 46a875c..3694748 100644 --- a/lib/trackdb.c +++ b/lib/trackdb.c @@ -2421,22 +2421,52 @@ 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; + } /* 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); @@ -2459,7 +2489,8 @@ static int one_old_user(const char *user, const char *password, 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); + return create_user(user, password, rights, 0/*email*/, 0/*confirmation*/, + tid, DB_NOOVERWRITE); } static int trackdb_old_users_tid(DB_TXN *tid) { @@ -2502,8 +2533,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"); } @@ -2538,11 +2570,12 @@ const char *trackdb_get_password(const char *user) { int trackdb_adduser(const char *user, const char *password, rights_type rights, - const char *email) { + 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, r, email, confirmation, tid, DB_NOOVERWRITE)); if(e) { error(0, "cannot created user '%s' because they already exist", user); @@ -2662,6 +2695,49 @@ char **trackdb_listusers(void) { return v->vec; } +static int trackdb_confirm_tid(const char *user, const char *confirmation, + DB_TXN *tid) { + const char *stored_confirmation; + struct kvp *k; + int e; + + 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(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 + * @return 0 on success, non-0 on error + */ +int trackdb_confirm(const char *user, const char *confirmation) { + int e; + + WITH_TRANSACTION(trackdb_confirm_tid(user, confirmation, 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 diff --git a/lib/trackdb.h b/lib/trackdb.h index 2c57e86..d777146 100644 --- a/lib/trackdb.h +++ b/lib/trackdb.h @@ -162,12 +162,14 @@ const char *trackdb_get_password(const char *user); int trackdb_adduser(const char *user, const char *password, rights_type rights, - const char *email); + const char *email, + const char *confirmation); int trackdb_deluser(const char *user); struct kvp *trackdb_getuserinfo(const char *user); int trackdb_edituserinfo(const char *user, const char *key, const char *value); char **trackdb_listusers(void); +int trackdb_confirm(const char *user, const char *confirmation); #endif /* TRACKDB_H */ diff --git a/python/disorder.py.in b/python/disorder.py.in index 650671d..36157a0 100644 --- a/python/disorder.py.in +++ b/python/disorder.py.in @@ -881,6 +881,15 @@ class client: self._simple("users") return self._body() + def register(self, username, password, email): + """Register a user""" + res, details = self._simple("register", username, password, email) + return _split(details)[0] + + def confirm(self, confirmation): + """Confirm a user registration""" + res, details = self._simple("confirm", confirmation) + ######################################################################## # I/O infrastructure diff --git a/server/server.c b/server/server.c index 807a276..22cf454 100644 --- a/server/server.c +++ b/server/server.c @@ -66,6 +66,7 @@ #include "cache.h" #include "unicode.h" #include "cookies.h" +#include "mime.h" #ifndef NONCE_SIZE # define NONCE_SIZE 16 @@ -1077,7 +1078,8 @@ static int c_revoke(struct conn *c, static int c_adduser(struct conn *c, char **vec, int attribute((unused)) nvec) { - if(trackdb_adduser(vec[0], vec[1], config->default_rights, 0)) + if(trackdb_adduser(vec[0], vec[1], config->default_rights, + 0/*email*/, 0/*confirmation*/)) sink_writes(ev_writer_sink(c->w), "550 Cannot create user\n"); else sink_writes(ev_writer_sink(c->w), "250 User created\n"); @@ -1088,7 +1090,7 @@ static int c_deluser(struct conn *c, char **vec, int attribute((unused)) nvec) { if(trackdb_deluser(vec[0])) - sink_writes(ev_writer_sink(c->w), "550 Cannot deleted user\n"); + sink_writes(ev_writer_sink(c->w), "550 Cannot delete user\n"); else sink_writes(ev_writer_sink(c->w), "250 User deleted\n"); return 1; @@ -1152,6 +1154,45 @@ static int c_users(struct conn *c, return 1; /* completed */ } +static int c_register(struct conn *c, + char **vec, + int attribute((unused)) nvec) { + char *buf, *cs; + size_t bufsize; + int offset; + + /* The confirmation string is base64(username;nonce) */ + bufsize = strlen(vec[0]) + NONCE_SIZE + 2; + buf = xmalloc_noptr(bufsize); + offset = byte_snprintf(buf, bufsize, "%s;", vec[0]); + gcry_randomize(buf + offset, NONCE_SIZE, GCRY_STRONG_RANDOM); + cs = mime_to_base64((uint8_t *)buf, offset + NONCE_SIZE); + if(trackdb_adduser(vec[0], vec[1], config->default_rights, vec[2], cs)) + sink_writes(ev_writer_sink(c->w), "550 Cannot create user\n"); + else + sink_printf(ev_writer_sink(c->w), "252 %s\n", quoteutf8(cs)); + return 1; +} + +static int c_confirm(struct conn *c, + char **vec, + int attribute((unused)) nvec) { + size_t nuser; + char *user, *sep; + + if(!(user = mime_base64(vec[0], &nuser)) + || !(sep = memchr(user, ';', nuser))) { + sink_writes(ev_writer_sink(c->w), "550 Malformed confirmation string\n"); + return 1; + } + *sep = 0; + if(trackdb_confirm(user, vec[0])) + sink_writes(ev_writer_sink(c->w), "550 Incorrect confirmation string\n"); + else + sink_writes(ev_writer_sink(c->w), "250 OK\n"); + return 1; +} + static const struct command { /** @brief Command name */ const char *name; @@ -1174,6 +1215,7 @@ static const struct command { } commands[] = { { "adduser", 2, 2, c_adduser, RIGHT_ADMIN|RIGHT__LOCAL }, { "allfiles", 0, 2, c_allfiles, RIGHT_READ }, + { "confirm", 1, 1, c_confirm, 0 }, { "cookie", 1, 1, c_cookie, 0 }, { "deluser", 1, 1, c_deluser, RIGHT_ADMIN|RIGHT__LOCAL }, { "dirs", 0, 2, c_dirs, RIGHT_READ }, @@ -1203,6 +1245,7 @@ static const struct command { { "random-enabled", 0, 0, c_random_enabled, RIGHT_READ }, { "recent", 0, 0, c_recent, RIGHT_READ }, { "reconfigure", 0, 0, c_reconfigure, RIGHT_ADMIN }, + { "register", 3, 3, c_register, RIGHT_REGISTER|RIGHT__LOCAL }, { "remove", 1, 1, c_remove, RIGHT_REMOVE__MASK }, { "rescan", 0, 0, c_rescan, RIGHT_RESCAN }, { "resolve", 1, 1, c_resolve, RIGHT_READ }, diff --git a/tests/user.py b/tests/user.py index b04e2a2..74f0212 100755 --- a/tests/user.py +++ b/tests/user.py @@ -55,6 +55,22 @@ def test(): users = c.users() assert dtest.lists_have_same_contents(users, ["fred", "root"]) + print " testing user registration" + cs = c.register("joe", "joepass", "joe@nowhere.invalid") + print " confirmation string is %s" % cs + print " checking unconfirmed user cannot log in" + jc = disorder.client(user="joe", password="joepass") + try: + jc.version() + print "*** should not be able to log in before confirmation ***" + assert False + except disorder.operationError: + pass # good + print " confirming user" + c.confirm(cs) + print " checking confirmed user can log in" + jc = disorder.client(user="joe", password="joepass") + jc.version() if __name__ == '__main__': dtest.run() -- [mdw]