See ChangeLog.d/* for detailed revision history.
+* Changes up to version 2.1
+
+** Server
+
+Users are now stored in the database rather than a configuration file.
+
* Changes up to version 2.0
** General
disorder authorize USERNAME
- This will automatically choose a random password and add new line to
- /etc/disorder/config.private and create /etc/disorder/config.USERNAME.
+ This will automatically choose a random password and create
+ /etc/disorder/config.USERNAME.
Those users should now be able to access the server from the same host as it
runs on, either via the disorder command or Disobedience. To run
explicitly mentioned; a version number like 1.1 implicitly includes
all 1.1.x versions.
+* 2.0 -> 2.1
+
+** Authentication
+
+Users are now stored in the database rather than in 'allow' directives in a
+private configuration file. 'allow' is still understood in this version, but
+is only used to populate the database on startup. After the first (successful)
+run of the server the remaining 'allow' directives can be deleted.
+
+'allow' will stop working entirely in a future version.
+
* 1.4/1.5 -> 2.0
** 'transform' and 'namepart' directives
disorder_SOURCES=disorder.c authorize.c authorize.h \
../lib/memgc.c
disorder_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
- $(LIBGC) $(LIBGCRYPT) $(LIBPCRE)
+ $(LIBGC) $(LIBGCRYPT) $(LIBPCRE) $(LIBDB)
disorder_DEPENDENCIES=$(LIBOBJS) ../lib/libdisorder.a
disorderfm_SOURCES=disorderfm.c \
disorder_playrtp_SOURCES=playrtp.c playrtp.h playrtp-mem.c \
playrtp-alsa.c playrtp-coreaudio.c playrtp-oss.c
disorder_playrtp_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
- $(LIBASOUND) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT) $(COREAUDIO)
+ $(LIBASOUND) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT) $(COREAUDIO) $(LIBDB)
disorder_playrtp_DEPENDENCIES=$(LIBOBJS) ../lib/libdisorder.a
filename_bytes_SOURCES=filename-bytes.c
#include <fcntl.h>
#include <stdio.h>
+#include "client.h"
#include "authorize.h"
#include "log.h"
#include "configuration.h"
#include "printf.h"
#include "hex.h"
-int authorize(const char *user) {
+/** @brief Create a DisOrder login for the calling user, called @p user
+ * @param client DisOrder client
+ * @param user Username to create
+ * @return 0 on success, non-0 on error
+ */
+int authorize(disorder_client *client, const char *user) {
uint8_t pwbin[10];
const struct passwd *pw, *jbpw;
gid_t jbgid;
if(rename(t, c) < 0)
fatal(errno, "error renaming %s to %s", t, c);
- /* append to config.private. We might create it along the way (though this
- * is unlikely) in which case it had better be 640 root:jukebox */
- if(!(c = config_private()))
- fatal(0, "cannot determine private config file");
- if((fd = open(c, O_WRONLY|O_APPEND|O_CREAT, 0600)) < 0)
- fatal(errno, "error opening %s", c);
- if(fchown(fd, 0, jbgid) < 0)
- fatal(errno, "error chowning %s", c);
- if(fchmod(fd, 0640) < 0)
- fatal(errno, "error chmoding %s", t);
- if(!(fp = fdopen(fd, "a")))
- fatal(errno, "error calling fdopen");
- if(fprintf(fp, "allow %s %s\n", user, pwhex) < 0
- || fclose(fp) < 0)
- fatal(errno, "error appending to %s", c);
+ /* create the user on the server */
+ if(disorder_adduser(client, user, pwhex))
+ return -1;
+
return 0;
}
#ifndef AUTHORIZE_H
#define AUTHORIZE_H
-int authorize(const char *user);
+int authorize(disorder_client *client, const char *user);
#endif /* AUTHORIZE_H */
{ "local", no_argument, 0, 'l' },
{ "no-per-user-config", no_argument, 0, 'N' },
{ "help-commands", no_argument, 0, 'H' },
+ { "user", required_argument, 0, 'u' },
+ { "password", required_argument, 0, 'p' },
{ 0, 0, 0, 0 }
};
return s[0] == '/';
}
-static void cf_authorize(disorder_client attribute((unused)) *c,
+static void cf_authorize(disorder_client *c,
char **argv) {
- if(!authorize(argv[0]))
+ if(!authorize(c, argv[0]))
auto_reconfigure = 1;
}
xprintf("address: %s\nport: %s\n", address, port);
}
+static void cf_adduser(disorder_client *c,
+ char **argv) {
+ if(disorder_adduser(c, argv[0], argv[1]))
+ exit(EXIT_FAILURE);
+}
+
static const struct command {
const char *name;
int min, max;
int (*isarg)(const char *);
const char *argstr, *desc;
} commands[] = {
+ { "adduser", 2, 2, cf_adduser, 0, "USER PASSWORD",
+ "Create a new user" },
{ "allfiles", 1, 2, cf_allfiles, isarg_regexp, "DIR [~REGEXP]",
"List all files and directories in DIR" },
{ "authorize", 1, 1, cf_authorize, 0, "USER",
disorder_client *c = 0;
int status = 0;
struct vector args;
+ const char *user = 0, *password = 0;
mem_init();
/* garbage-collect PCRE's memory */
pcre_malloc = xmalloc;
pcre_free = xfree;
if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale");
- while((n = getopt_long(argc, argv, "hVc:dHlN", options, 0)) >= 0) {
+ while((n = getopt_long(argc, argv, "hVc:dHlNu:p:", options, 0)) >= 0) {
switch(n) {
case 'h': help();
case 'H': help_commands();
case 'd': debugging = 1; break;
case 'l': local = 1; break;
case 'N': config_per_user = 0; break;
+ case 'u': user = optarg; break;
+ case 'p': password = optarg; break;
default: fatal(0, "invalid option");
}
}
if(config_read(0)) fatal(0, "cannot read configuration");
+ if(user) {
+ config->username = user;
+ config->password = 0;
+ }
+ if(password)
+ config->password = password;
if(local)
config->connect.n = 0;
if(!(c = disorder_new(1))) exit(EXIT_FAILURE);
-
.\"
.\" Copyright (C) 2004, 2005, 2006, 2007 Richard Kettlewell
.\"
\fB/cgi-bin/jukebox\fR.
.SS "Authentication Configuration"
.TP
-.B allow \fIUSERNAME\fR \fIPASSWORD\fR
-Specify a username/password pair.
-.IP
-If
-.B allow
-is used without arguments, the list of allowed users is cleared.
-.TP
.B password \fIPASSWORD\fR
Specify password.
.TP
types.h \
table.c table.h \
timeval.h \
+ trackdb.h trackdb.c trackdb-int.h \
trackname.c trackname.h \
user.h user.c \
unicode.h unicode.c \
#include <errno.h>
#include <netdb.h>
#include <stdlib.h>
+#include <pcre.h>
#include "log.h"
#include "mem.h"
#include "addr.h"
#include "authhash.h"
#include "client-common.h"
+#include "trackdb.h"
struct disorder_client {
FILE *fpin, *fpout;
const char *ident) {
const char *username, *password;
disorder_client *c = vc;
- int n;
if(!(username = config->username)) {
error(0, "no username configured");
return -1;
}
- if(!(password = config->password)) {
- for(n = 0; (n < config->allow.n
- && strcmp(config->allow.s[n].s[0], username)); ++n)
- ;
- if(n < config->allow.n)
- password = config->allow.s[n].s[1];
- else {
- error(0, "no password configured");
- return -1;
- }
+ password = config->password;
+ if(!password) {
+ /* Maybe we can read the database */
+ /* TODO failure to open the database should not be fatal */
+ trackdb_init(TRACKDB_NO_RECOVER|TRACKDB_NO_UPGRADE);
+ trackdb_open(TRACKDB_READ_ONLY);
+ password = trackdb_get_password(username);
+ trackdb_close();
+ }
+ if(!password) {
+ /* Oh well */
+ error(0, "no password configured");
+ return -1;
}
return disorder_connect_sock(c, sa, len, username, password, ident);
}
return 0;
}
+int disorder_adduser(disorder_client *c,
+ const char *user, const char *password) {
+ return disorder_simple(c, 0, "adduser", user, password, (char *)0);
+}
+
+int disorder_deluser(disorder_client *c, const char *user) {
+ return disorder_simple(c, 0, "deluser", user, (char *)0);
+}
+
/*
Local Variables:
c-basic-offset:2
int disorder_rtp_address(disorder_client *c, char **addressp, char **portp);
+int disorder_adduser(disorder_client *c,
+ const char *user, const char *password);
+int disorder_deluser(disorder_client *c, const char *user);
+
#endif /* CLIENT_H */
/*
#include <errno.h>
#include <time.h>
#include <gcrypt.h>
+#include <pcre.h>
#include "cookies.h"
#include "hash.h"
#include "mime.h"
#include "configuration.h"
#include "kvp.h"
+#include "trackdb.h"
/** @brief Hash function used in signing HMAC */
#define ALGO GCRY_MD_SHA1
* @return Cookie or NULL
*/
char *make_cookie(const char *user) {
- char *password;
+ const char *password;
time_t now;
char *b, *bp, *c, *g;
- int n;
/* semicolons aren't allowed in usernames */
if(strchr(user, ';')) {
return 0;
}
/* look up the password */
- for(n = 0; n < config->allow.n
- && strcmp(config->allow.s[n].s[0], user); ++n)
- ;
- if(n >= config->allow.n) {
+ password = trackdb_get_password(user);
+ if(!password) {
error(0, "make_cookie for nonexistent user");
return 0;
}
- password = config->allow.s[n].s[1];
/* make sure we have a valid signing key */
time(&now);
if(now >= signing_key_validity_limit)
char *c1, *c2;
intmax_t t;
time_t now;
- char *user, *bp, *password, *sig;
- int n;
+ char *user, *bp, *sig;
+ const char *password;
/* check the revocation list */
if(revoked && hash_find(revoked, cookie)) {
return 0;
}
/* look up the password */
- for(n = 0; n < config->allow.n
- && strcmp(config->allow.s[n].s[0], user); ++n)
- ;
- if(n >= config->allow.n) {
+ password = trackdb_get_password(user);
+ if(!password) {
error(0, "verify_cookie for nonexistent user");
return 0;
}
- password = config->allow.s[n].s[1];
/* construct the expected subject. We re-encode the timestamp and the
* password. */
byte_xasprintf(&bp, "%jx;%s;%s", t, urlencodestring(user), password);
void trackdb_commit_transaction(DB_TXN *tid);
/* begin, abort or commit a transaction */
+/** @brief Evaluate @p expr in a transaction, looping on deadlock
+ *
+ * @c tid will be the transaction handle. @p e will be the error code.
+ */
+#define WITH_TRANSACTION(expr) do { \
+ DB_TXN *tid; \
+ \
+ tid = trackdb_begin_transaction(); \
+ while((e = (expr)) == DB_LOCK_DEADLOCK) { \
+ trackdb_abort_transaction(tid); \
+ tid = trackdb_begin_transaction(); \
+ } \
+ if(e) \
+ trackdb_abort_transaction(tid); \
+ else \
+ trackdb_commit_transaction(tid); \
+} while(0)
+
int trackdb_getdata(DB *db,
const char *track,
struct kvp **kp,
* USA
*/
/** @file server/trackdb.c
- * @brief Track database */
+ * @brief Track database
+ *
+ * This file is getting in desparate need of splitting up...
+ */
#include <config.h>
#include "types.h"
#include <sys/wait.h>
#include <dirent.h>
#include <sys/stat.h>
+#include <gcrypt.h>
#include "event.h"
#include "mem.h"
#include "hash.h"
#include "unicode.h"
#include "unidata.h"
+#include "mime.h"
#define RESCAN "disorder-rescan"
#define DEADLOCK "disorder-deadlock"
struct stat st;
char *p;
- /* create home directory if it does not exist */
- mkdir(config->home, 0755);
/* Remove world/group permissions on any regular files already in the
* database directory. Actually we don't care about all of them but it's
* easier to just do the lot. This can be revisited if it's a serious
if(lstat(p, &st) == 0
&& S_ISREG(st.st_mode)
&& (st.st_mode & 077)) {
- if(chmod(p, st.st_mode & (~(mode_t)077) & 07777) < 0)
+ if(chmod(p, st.st_mode & 07700) < 0)
fatal(errno, "cannot chmod %s", p);
}
xfree(p);
xdup2(outputfd, 1);
xclose(outputfd);
}
- /* If we were negatively niced, undo it. We don't bother checking for
- * error, it's not that important. */
+ /* ensure we don't leak privilege anywhere */
+ if(setuid(geteuid()) < 0)
+ fatal(errno, "error calling setuid");
+ /* 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",
/** @brief Open track databases
* @param Flags flags word
*
- * @p flags should be one of:
+ * @p flags should have one of:
* - @p TRACKDB_NO_UPGRADE, if no upgrade should be attempted
* - @p TRACKDB_CAN_UPGRADE, if an upgrade may be attempted
* - @p TRACKDB_OPEN_FOR_UPGRADE, if this is disorder-dbupgrade
+ * Also it may have:
+ * - @p TRACKDB_READ_ONLY, read only access
*/
void trackdb_open(int flags) {
int err;
pid_t pid;
+ uint32_t dbflags = flags & TRACKDB_READ_ONLY ? DB_RDONLY : DB_CREATE;
/* sanity checks */
assert(opened == 0);
++opened;
/* check the database version first */
- trackdb_globaldb = open_db("global.db", 0, DB_HASH, 0, 0666);
+ trackdb_globaldb = open_db("global.db", 0, DB_HASH, DB_RDONLY, 0666);
if(trackdb_globaldb) {
/* This is an existing database */
const char *s;
}
/* open the databases */
trackdb_tracksdb = open_db("tracks.db",
- DB_RECNUM, DB_BTREE, DB_CREATE, 0666);
+ DB_RECNUM, DB_BTREE, dbflags, 0666);
trackdb_searchdb = open_db("search.db",
- DB_DUP|DB_DUPSORT, DB_HASH, DB_CREATE, 0666);
+ DB_DUP|DB_DUPSORT, DB_HASH, dbflags, 0666);
trackdb_tagsdb = open_db("tags.db",
- DB_DUP|DB_DUPSORT, DB_HASH, DB_CREATE, 0666);
- trackdb_prefsdb = open_db("prefs.db", 0, DB_HASH, DB_CREATE, 0666);
- trackdb_globaldb = open_db("global.db", 0, DB_HASH, DB_CREATE, 0666);
+ DB_DUP|DB_DUPSORT, DB_HASH, dbflags, 0666);
+ trackdb_prefsdb = open_db("prefs.db", 0, DB_HASH, dbflags, 0666);
+ trackdb_globaldb = open_db("global.db", 0, DB_HASH, dbflags, 0666);
trackdb_noticeddb = open_db("noticed.db",
- DB_DUPSORT, DB_BTREE, DB_CREATE, 0666);
+ DB_DUPSORT, DB_BTREE, dbflags, 0666);
trackdb_usersdb = open_db("users.db",
- 0, DB_HASH, DB_CREATE, 0600);
+ 0, DB_HASH, dbflags, 0600);
if(!trackdb_existing_database) {
/* Stash the database version */
char buf[32];
/* close track databases */
void trackdb_close(void) {
int err;
-
+
/* sanity checks */
assert(opened == 1);
--opened;
}
}
-/* delete a database entry */
+/** @brief Delete a database entry
+ * @param db Database
+ * @param track Key to delete
+ * @param tid Transaction ID
+ * @return 0, DB_NOTFOUND or DB_LOCK_DEADLOCK
+ */
int trackdb_delkey(DB *db,
const char *track,
DB_TXN *tid) {
int err;
const char *actual = track;
struct kvp *t = 0, *p = 0;
-
+
if((err = trackdb_getdata(trackdb_tracksdb, track, &t, tid))) goto done;
if((actual = kvp_get(t, "_alias_for"))) {
if(flags & GTD_NOALIAS) {
const char *path) {
int err;
DB_TXN *tid;
-
+
for(;;) {
tid = trackdb_begin_transaction();
err = trackdb_notice_tid(track, path, tid);
return err;
else if(err == DB_NOTFOUND) return 0;
/* compute the alias, if any, and delete it */
- if(compute_alias(&alias, track, p, tid)) return err;
+ if((err = compute_alias(&alias, track, p, tid))) return err;
if(alias) {
/* if the alias points to some other track then compute_alias won't
* return it */
- if(trackdb_delkey(trackdb_tracksdb, alias, tid))
+ if((err = trackdb_delkey(trackdb_tracksdb, alias, tid))
+ && err != DB_NOTFOUND)
return err;
}
/* update search.db */
char **trackdb_stats(int *nstatsp) {
DB_TXN *tid;
struct vector v;
-
+
vector_init(&v);
for(;;) {
tid = trackdb_begin_transaction();
if(value) {
/* TODO: if value matches default then set value=0 */
}
-
+
for(;;) {
tid = trackdb_begin_transaction();
if((err = gettrackdata(track, &t, &p, 0,
|| (oldalias && newalias && !strcmp(oldalias, newalias)))) {
/* adjust alias records to fit change */
if(oldalias
- && trackdb_delkey(trackdb_tracksdb, oldalias, tid)) goto fail;
+ && trackdb_delkey(trackdb_tracksdb, oldalias, tid) == DB_LOCK_DEADLOCK)
+ goto fail;
if(newalias) {
a = 0;
kvp_set(&a, "_alias_for", track);
const char *trackdb_resolve(const char *track) {
DB_TXN *tid;
const char *actual;
-
+
for(;;) {
tid = trackdb_begin_transaction();
if(gettrackdata(track, 0, 0, &actual, 0, tid) == DB_LOCK_DEADLOCK)
size_t l, last_dir_len = 0;
char *last_dir = 0, *track, *alias;
struct kvp *p;
-
+
dl = strlen(dir);
cursor = trackdb_opencursor(trackdb_tracksdb, tid);
make_key(&k, dir);
if(np)
*np = v.nvec;
return v.vec;
-}
+}
/* If S is tag:something, return something. Else return 0. */
static const char *checktag(const char *s) {
for(n = 0; n < nwordlist; ++n) {
uint32_t *w32;
size_t nw32;
-
+
w[n] = utf8_casefold_compat(wordlist[n], strlen(wordlist[n]), 0);
if(checktag(w[n])) {
++ntags; /* count up tags */
* long-term storage requirements than record the db logfiles. */
}
+/* 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;
+}
+
/*
Local Variables:
c-basic-offset:2
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
-/** @file server/trackdb.h
+/** @file lib/trackdb.h
* @brief Track database public interface */
#ifndef TRACKDB_H
extern unsigned long cache_files_hits, cache_files_misses;
/* Cache entry type and tracking for regexp-based lookups */
+/** @brief User can perform read-only operations */
+#define RIGHT_READ 0x00000001
+
+/** @brief User can add tracks to the queue */
+#define RIGHT_PLAY 0x00000002
+
+/** @brief User can move any track */
+#define RIGHT_MOVE_ANY 0x00000004
+
+/** @brief User can move their own tracks */
+#define RIGHT_MOVE_MINE 0x00000008
+
+/** @brief User can move randomly chosen tracks */
+#define RIGHT_MOVE_RANDOM 0x00000010
+
+#define RIGHT_MOVE__MASK 0x0000001c
+
+/** @brief User can remove any track */
+#define RIGHT_REMOVE_ANY 0x00000020
+
+/** @brief User can remove their own tracks */
+#define RIGHT_REMOVE_MINE 0x00000040
+
+/** @brief User can remove randomly chosen tracks */
+#define RIGHT_REMOVE_RANDOM 0x00000080
+
+#define RIGHT_REMOVE__MASK 0x000000e0
+
+/** @brief User can scratch any track */
+#define RIGHT_SCRATCH_ANY 0x00000100
+
+/** @brief User can scratch their own tracks */
+#define RIGHT_SCRATCH_MINE 0x00000200
+
+/** @brief User can scratch randomly chosen tracks */
+#define RIGHT_SCRATCH_RANDOM 0x00000400
+
+#define RIGHT_SCRATCH__MASK 0x00000700
+
+/** @brief User can change the volume */
+#define RIGHT_VOLUME 0x00000800
+
+/** @brief User can perform admin operations */
+#define RIGHT_ADMIN 0x00001000
+
+/** @brief User can initiate a rescan */
+#define RIGHT_RESCAN 0x00002000
+
+/** @brief User can register new users */
+#define RIGHT_REGISTER 0x00004000
+
+/** @brief User can edit their own userinfo */
+#define RIGHT_USERINFO 0x00008000
+
+/** @brief User can modify track preferences */
+#define RIGHT_PREFS 0x00010000
+
+/** @brief User can modify global preferences */
+#define RIGHT_GLOBAL_PREFS 0x00020000
+
+/** @brief Current rights mask */
+#define RIGHTS__MASK 0x0003ffff
+
+/** @brief Unsigned type big enough for rights */
+typedef uint32_t rights_type;
+
/** @brief Do not attempt database recovery (trackdb_init()) */
#define TRACKDB_NO_RECOVER 0x0000
/** @brief May create database environment (trackdb_init()) */
#define TRACKDB_MAY_CREATE 0x0010
+/** @brief Read-only access (trackdb_open()) */
+#define TRACKDB_READ_ONLY 0x0020
+
void trackdb_init(int flags);
void trackdb_deinit(void);
/* close/close environment */
char **trackdb_new(int *ntracksp, int maxtracks);
void trackdb_expire_noticed(time_t when);
+void trackdb_old_users(void);
+void trackdb_create_root(void);
+const char *trackdb_get_password(const char *user);
+int trackdb_adduser(const char *user,
+ const char *password,
+ rights_type rights,
+ const char *email);
+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);
+
+rights_type default_rights(void);
#endif /* TRACKDB_H */
debug_proto = 0x0001
debug_body = 0x0002
- def __init__(self):
+ def __init__(self, user=None, password=None):
"""Constructor for DisOrder client class.
The constructor reads the configuration file, but does not connect
self.config = { 'collections': [],
'username': pw.pw_name,
'home': _dbhome }
+ self.user = user
+ self.password = password
home = os.getenv("HOME")
if not home:
home = pw.pw_dir
self.r = s.makefile("rb")
(res, challenge) = self._simple()
if cookie is None:
+ if self.user is None:
+ user = self.config['username']
+ else:
+ user = self.user
+ if self.password is None:
+ password = self.config['password']
+ else:
+ password = self.password
h = sha.sha()
- h.update(self.config['password'])
+ h.update(password)
h.update(binascii.unhexlify(challenge))
- self._simple("user", self.config['username'], h.hexdigest())
+ self._simple("user", user, h.hexdigest())
else:
self._simple("cookie", cookie)
self.state = 'connected'
"""Revoke a login cookie"""
self._simple("revoke")
+ def adduser(self, user, password):
+ """Create a user"""
+ self._simple("adduser", user, password)
+
+ def deluser(self, user):
+ """Delete a user"""
+ self._simple("deluser", user)
+
########################################################################
# I/O infrastructure
random-enable recent reconfigure remove rescan scratch
search set set-volume shutdown stats unset version resolve
part pause resume scratch-id get-global set-global unset-global
- tags new rtp-address
+ tags new rtp-address adduser
-h --help -H --help-commands --version -V --config -c
--length --debug -d" \
disorder
play.c play.h \
server.c server.h \
server-queue.c server-queue.h \
- setup.c setup.h \
state.c state.h \
- trackdb.c trackdb.h trackdb-int.h exports.c \
+ exports.c \
../lib/memgc.c
disorderd_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
$(LIBPCRE) $(LIBDB) $(LIBAO) $(LIBGC) $(LIBGCRYPT) $(LIBICONV)
disorderd_LDFLAGS=-export-dynamic
disorderd_DEPENDENCIES=../lib/libdisorder.a
-disorder_deadlock_SOURCES=deadlock.c \
- trackdb.c trackdb.h
+disorder_deadlock_SOURCES=deadlock.c
disorder_deadlock_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
$(LIBDB) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT)
disorder_deadlock_DEPENDENCIES=../lib/libdisorder.a
disorder_rescan_SOURCES=rescan.c \
api.c api-server.c \
- trackdb.c trackdb.h exports.c \
+ exports.c \
../lib/memgc.c
disorder_rescan_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
$(LIBDB) $(LIBGC) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT)
disorder_rescan_LDFLAGS=-export-dynamic
disorder_rescan_DEPENDENCIES=../lib/libdisorder.a
-disorder_stats_SOURCES=stats.c trackdb.c trackdb.h
+disorder_stats_SOURCES=stats.c
disorder_stats_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
$(LIBDB) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT)
disorder_stats_DEPENDENCIES=../lib/libdisorder.a
disorder_dump_SOURCES=dump.c \
- trackdb.c trackdb.h \
../lib/memgc.c
disorder_dump_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
$(LIBPCRE) $(LIBDB) $(LIBICONV) $(LIBGC) $(LIBGCRYPT)
disorder_dump_DEPENDENCIES=$(LIBOBJS) ../lib/libdisorder.a
-disorder_dbupgrade_SOURCES=dbupgrade.c trackdb.c trackdb.h ../lib/memgc.c
+disorder_dbupgrade_SOURCES=dbupgrade.c ../lib/memgc.c
disorder_dbupgrade_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
$(LIBDB) $(LIBGC) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT)
disorder_dbupgrade_DEPENDENCIES=../lib/libdisorder.a
api.c api-client.c api-client.h \
cgi.c cgi.h cgimain.c exports.c
disorder_cgi_LDADD=../lib/libdisorder.a \
- $(LIBPCRE) $(LIBGCRYPT) $(LIBDL)
+ $(LIBPCRE) $(LIBGCRYPT) $(LIBDL) $(LIBDB)
disorder_cgi_LDFLAGS=-export-dynamic
disorder_cgi_DEPENDENCIES=../lib/libdisorder.a
*/
#include <config.h>
+#include "types.h"
#include <stdio.h>
#include <stdlib.h>
#include "mixer.h"
#include "eventlog.h"
#include "printf.h"
-#include "setup.h"
static ev_source *ev;
fatal(0, "cannot read configuration");
/* make sure the home directory exists and has suitable permissions */
make_home();
- /* create the default login */
- make_root_login();
/* Start the speaker process (as root! - so it can choose its nice value) */
speaker_setup(ev);
/* set server nice value _after_ starting the speaker, so that they
/* initialize database environment */
trackdb_init(TRACKDB_NORMAL_RECOVER|TRACKDB_MAY_CREATE);
trackdb_master(ev);
- /* install new config */
+ /* install new config (calls trackdb_open()) */
reconfigure(ev, 0);
+ /* pull in old users */
+ trackdb_old_users();
+ /* create a root login */
+ trackdb_create_root();
/* re-read config if we receive a SIGHUP */
if(ev_signal(ev, SIGHUP, handle_sighup, 0)) fatal(0, "ev_signal failed");
/* exit on SIGINT/SIGTERM */
static int c_user(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
- int n;
- const char *res, *host;
+ const char *res, *host, *password;
if(c->who) {
sink_writes(ev_writer_sink(c->w), "530 already authenticated\n");
return 1;
}
/* find the user */
- for(n = 0; n < config->allow.n
- && strcmp(config->allow.s[n].s[0], vec[0]); ++n)
- ;
- /* if it's a real user check whether the response is right */
- if(n >= config->allow.n) {
+ password = trackdb_get_password(vec[0]);
+ /* reject nonexistent users */
+ if(!password) {
info("S%x unknown user '%s' from %s", c->tag, vec[0], host);
sink_writes(ev_writer_sink(c->w), "530 authentication failed\n");
return 1;
}
- res = authhash(c->nonce, sizeof c->nonce, config->allow.s[n].s[1],
+ /* check whether the response is right */
+ res = authhash(c->nonce, sizeof c->nonce, password,
config->authorization_algorithm);
if(wideopen || (res && !strcmp(res, vec[1]))) {
c->who = vec[0];
return 1;
}
+static int c_adduser(struct conn *c,
+ char **vec,
+ int attribute((unused)) nvec) {
+ /* TODO local only */
+ if(trackdb_adduser(vec[0], vec[1], default_rights(), 0))
+ sink_writes(ev_writer_sink(c->w), "550 Cannot create user\n");
+ else
+ sink_writes(ev_writer_sink(c->w), "250 User created\n");
+ return 1;
+}
+
+static int c_deluser(struct conn *c,
+ char **vec,
+ int attribute((unused)) nvec) {
+ /* TODO local only */
+ if(trackdb_deluser(vec[0]))
+ sink_writes(ev_writer_sink(c->w), "550 Cannot deleted user\n");
+ else
+ sink_writes(ev_writer_sink(c->w), "250 User deleted\n");
+ return 1;
+}
+
+static int c_edituser(struct conn *c,
+ char attribute((unused)) **vec,
+ int attribute((unused)) nvec) {
+ sink_writes(ev_writer_sink(c->w), "550 Not implemented\n"); /* TODO */
+ return 1;
+}
+
+static int c_userinfo(struct conn *c,
+ char attribute((unused)) **vec,
+ int attribute((unused)) nvec) {
+ sink_writes(ev_writer_sink(c->w), "550 Not implemented\n"); /* TODO */
+ return 1;
+}
+
#define C_AUTH 0001 /* must be authenticated */
#define C_TRUSTED 0002 /* must be trusted user */
int (*fn)(struct conn *, char **, int);
unsigned flags;
} commands[] = {
+ { "adduser", 2, 2, c_adduser, C_AUTH|C_TRUSTED },
{ "allfiles", 0, 2, c_allfiles, C_AUTH },
{ "become", 1, 1, c_become, C_AUTH|C_TRUSTED },
{ "cookie", 1, 1, c_cookie, 0 },
+ { "deluser", 1, 1, c_deluser, C_AUTH|C_TRUSTED },
{ "dirs", 0, 2, c_dirs, C_AUTH },
{ "disable", 0, 1, c_disable, C_AUTH },
+ { "edituser", 3, 3, c_edituser, C_AUTH },
{ "enable", 0, 0, c_enable, C_AUTH },
{ "enabled", 0, 0, c_enabled, C_AUTH },
{ "exists", 1, 1, c_exists, C_AUTH },
{ "stats", 0, 0, c_stats, C_AUTH },
{ "tags", 0, 0, c_tags, C_AUTH },
{ "unset", 2, 2, c_set, C_AUTH },
- { "unset-global", 1, 1, c_set_global, C_AUTH },
+ { "unset-global", 1, 1, c_set_global, C_AUTH },
{ "user", 2, 2, c_user, 0 },
+ { "userinfo", 2, 2, c_userinfo, C_AUTH },
{ "version", 0, 0, c_version, C_AUTH },
{ "volume", 0, 2, c_volume, C_AUTH }
};
+++ /dev/null
-/*
- * This file is part of DisOrder.
- * Copyright (C) 2007 Richard Kettlewell
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-/** @file server/setup.c
- * @brief Automated setup functions
- */
-
-#include <config.h>
-#include "types.h"
-
-#include <unistd.h>
-#include <fcntl.h>
-#include <gcrypt.h>
-#include <pwd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <stdio.h>
-
-#include "log.h"
-#include "mem.h"
-#include "printf.h"
-#include "configuration.h"
-#include "setup.h"
-#include "hex.h"
-#include "defs.h"
-
-/** @brief Create config.private with a login for root */
-void make_root_login(void) {
- struct stat sb;
- char *privconfig, *privconfignew;
- int fd;
- FILE *fp;
- struct passwd *pw;
- uint8_t pwbin[10];
- char *pwhex;
-
- if(config->user) {
- if(!(pw = getpwnam(config->user)))
- fatal(0, "cannot find user %s", config->user);
- } else
- pw = 0;
- /* Compute filenames */
- byte_xasprintf(&privconfig, "%s/config.private", pkgconfdir);
- byte_xasprintf(&privconfignew, "%s/config.private.new", pkgconfdir);
- /* If config.private already exists don't overwrite it */
- if(stat(privconfig, &sb) == 0)
- return;
- /* Choose a new root password */
- gcry_randomize(pwbin, sizeof pwbin, GCRY_STRONG_RANDOM);
- pwhex = hex(pwbin, sizeof pwbin);
- /* Create the file */
- if((fd = open(privconfignew, O_WRONLY|O_CREAT, 0600)) < 0) {
- error(errno, "error creating %s", privconfignew);
- return; /* not fatal! */
- }
- /* Fix permissions */
- if(pw) {
- if(fchown(fd, 0, pw->pw_gid) < 0)
- fatal(errno, "error setting owner/group for %s", privconfignew);
- if(fchmod(fd, 0640) < 0)
- fatal(errno, "error setting permissions for %s", privconfignew);
- }
- /* Write the required 'allow' line */
- if(!(fp = fdopen(fd, "w")))
- fatal(errno, "fdopen");
- if(fprintf(fp, "allow root %s\n", pwhex) < 0
- || fclose(fp) < 0)
- fatal(errno, "error writing %s", privconfignew);
- /* Rename into place */
- if(rename(privconfignew, privconfig) < 0)
- fatal(errno, "error renaming %s", privconfignew);
-}
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
+++ /dev/null
-/*
- * This file is part of DisOrder
- * Copyright (C) 2007 Richard Kettlewell
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#ifndef SETUP_H
-#define SETUP_H
-
-void make_root_login(void);
-
-#endif /* SETUP_H */
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
${PYTHON} ${srcdir}/alltests
EXTRA_DIST=alltests dtest.py dbversion.py search.py \
- queue.py dump.py play.py cookie.py
+ queue.py dump.py play.py cookie.py user-upgrade.py user.py
def test():
"""Exercise cookie protocol"""
dtest.start_daemon()
+ dtest.create_user()
print " connecting"
c = disorder.client()
v = c.version()
dtest.copyfile(config, configsave)
open(config, "a").write("dbversion 1\n")
dtest.start_daemon()
+ dtest.create_user()
dtest.stop_daemon()
# Revert to default configuration
dtest.copyfile(configsave, config)
except:
return False
-def common_setup():
- remove_dir(testroot)
- os.mkdir(testroot)
- # Choose a port
- global port
- port = random.randint(49152, 65535)
- while not bindable(port + 1):
- print "port %d is not bindable, trying another" % (port + 1)
- port = random.randint(49152, 65535)
- # Log anything sent to that port
- packetlog = "%s/packetlog" % testroot
- subprocess.Popen(["disorder-udplog",
- "--output", packetlog,
- "127.0.0.1", "%d" % port])
- # disorder-udplog will quit when its parent process terminates
+def default_config():
+ """Write the default config"""
open("%s/config" % testroot, "w").write(
"""home %s
collection fs UTF-8 %s/tracks
stopword the a an and to too in on of we i am as im for is
username fred
password fredpass
-allow fred fredpass
-trust fred
+trust fred root
plugins
plugins %s/plugins
plugins %s/plugins/.libs
broadcast_from 127.0.0.1 %d
""" % (testroot, testroot, testroot, top_builddir, top_builddir,
port, port + 1))
+
+def common_setup():
+ remove_dir(testroot)
+ os.mkdir(testroot)
+ # Choose a port
+ global port
+ port = random.randint(49152, 65535)
+ while not bindable(port + 1):
+ print "port %d is not bindable, trying another" % (port + 1)
+ port = random.randint(49152, 65535)
+ # Log anything sent to that port
+ packetlog = "%s/packetlog" % testroot
+ subprocess.Popen(["disorder-udplog",
+ "--output", packetlog,
+ "127.0.0.1", "%d" % port])
+ # disorder-udplog will quit when its parent process terminates
copyfile("%s/sounds/scratch.ogg" % top_srcdir,
"%s/scratch.ogg" % testroot)
+ default_config()
def start_daemon():
"""start_daemon()
if waited > 0:
print " took about %ds for socket to appear" % waited
+def create_user(username="fred", password="fredpass"):
+ """create_user(USERNAME, PASSWORD)
+
+ Create a user, abusing direct database access to do so."""
+ print " creating user %s" % username
+ command(["disorder",
+ "--config", disorder._configfile, "--no-per-user-config",
+ "--user", "root", "adduser", username, password])
+
def stop_daemon():
"""stop_daemon()
def test():
"""Exercise database dumper"""
dtest.start_daemon()
+ dtest.create_user()
c = disorder.client()
track = "%s/Joe Bloggs/First Album/02:Second track.ogg" % dtest.tracks
dump = "%s/dumpfile" % dtest.testroot
- print "setting a track pref"
+ print " setting a track pref"
c.set(track, "foo", "before")
assert c.get(track, "foo") == "before", "checking track foo=before"
- print "setting a global pref"
+ print " setting a global pref"
c.setglobal("foo", "before");
assert c.getglobal("foo") == "before", "checking global foo=before"
- print "adding a tag"
+ print " adding a tag"
# Exercise the tags-changed code
c.set(track, "tags", " first tag, Another Tag")
assert dtest.lists_have_same_contents(c.tags(),
assert dtest.lists_have_same_contents(c.tags(),
[u"another tag", u"wibble"]),\
"checking tag list(2)"
- print "checking track appears in tag search"
+ print " checking track appears in tag search"
tracks = c.search(["tag:wibble"])
assert len(tracks) == 1, "checking there is exactly one search result(1)"
assert tracks[0] == track, "checking for right search result(1)"
tracks = c.search(["tag: another tAg "])
assert len(tracks) == 1, "checking there is exactly one search result(2)"
assert tracks[0] == track, "checking for right search result(2)"
- print "dumping database"
+ print " dumping database"
print dtest.command(["disorder-dump", "--config", disorder._configfile,
"--dump", dump])
- print "changing track pref"
+ print " changing track pref"
c.set(track, "foo", "after");
assert c.get(track, "foo") == "after", "checking track foo=before"
- print "changing global pref"
+ print " changing global pref"
c.setglobal("foo", "after");
assert c.getglobal("foo") == "after", "checking global foo=before"
- print "adding fresh track pref"
+ print " adding fresh track pref"
c.set(track, "bar", "after")
- print "adding fresh global pref"
+ print " adding fresh global pref"
c.setglobal("bar", "after")
dtest.stop_daemon();
print "restoring database"
"--undump", dump])
dtest.start_daemon();
c = disorder.client()
- print "checking track pref"
+ print " checking track pref"
assert c.get(track, "foo") == "before", "checking track foo=before after undump"
- print "checking global pref"
+ print " checking global pref"
assert c.getglobal("foo") == "before", "checking global foo=before after undump"
- print "checking fresh track pref"
+ print " checking fresh track pref"
assert c.get(track, "bar") is None, "checking fresh track pref has gone"
- print "checking fresh global pref"
+ print " checking fresh global pref"
assert c.getglobal("bar") is None, "checking fresh global pref has gone"
- print "checking tag search still works"
+ print " checking tag search still works"
tracks = c.search(["tag:wibble"])
assert len(tracks) == 1, "checking there is exactly one search result"
assert tracks[0] == track, "checking for right search result(3)"
def test():
"""Check that the file listing comes out right"""
dtest.start_daemon()
+ dtest.create_user()
assert dtest.check_files() == 0, "dtest.check_files"
print " checking regexp file listing"
c = disorder.client()
def test():
"""Play some tracks"""
dtest.start_daemon()
+ dtest.create_user()
c = disorder.client()
track = u"%s/Joe Bloggs/First Album/02:Second track.ogg" % dtest.tracks
- print "adding track to queue"
+ print " adding track to queue"
c.play(track)
print " checking track turned up in queue"
q = c.queue()
def test():
"""Check the queue is padded to the (default) configured length"""
dtest.start_daemon()
+ dtest.create_user()
c = disorder.client()
print " getting queue via python module"
q = c.queue()
def test():
"""Check that the search produces the right results"""
dtest.start_daemon()
+ dtest.create_user()
time.sleep(2) # give rescan a chance
global client
client = disorder.client()
--- /dev/null
+#! /usr/bin/env python
+#
+# This file is part of DisOrder.
+# Copyright (C) 2007 Richard Kettlewell
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+#
+import dtest,disorder
+
+def test():
+ """Test upgrade to new user database"""
+ print " testing upgrade from old versions"
+ open("%s/config" % dtest.testroot, "a").write(
+ """allow fred fredpass
+""")
+ dtest.start_daemon()
+ print " checking can log in after upgrade"
+ c = disorder.client()
+ c.version()
+ dtest.stop_daemon()
+ dtest.default_config()
+ dtest.start_daemon()
+ print " checking can log in after removing 'allow'"
+ c = disorder.client()
+ c.version()
+
+if __name__ == '__main__':
+ dtest.run()
--- /dev/null
+#! /usr/bin/env python
+#
+# This file is part of DisOrder.
+# Copyright (C) 2007 Richard Kettlewell
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+#
+import dtest,disorder
+
+def test():
+ """Test user database"""
+ dtest.start_daemon()
+ dtest.create_user()
+ print " checking user creation"
+ c = disorder.client()
+ c.adduser("bob", "bobpass")
+ print " checking new user can log in"
+ c = disorder.client(user="bob", password="bobpass")
+ c.version()
+ print " checking user deletion"
+ c = disorder.client()
+ c.deluser("bob")
+ print " checking new user can no longer log in"
+ c = disorder.client(user="bob", password="bobpass")
+ try:
+ c.version()
+ print "*** should not be able to log in after deletion ***"
+ assert False
+ except disorder.operationError:
+ pass # good
+ print " deleted user could no longer log in."
+
+if __name__ == '__main__':
+ dtest.run()