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
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
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
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 */
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);
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) {
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");
}
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);
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
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 */
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
#include "cache.h"
#include "unicode.h"
#include "cookies.h"
+#include "mime.h"
#ifndef NONCE_SIZE
# define NONCE_SIZE 16
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");
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;
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;
} 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 },
{ "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 },
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()