chiark / gitweb /
Use users.db. trackdb* moves to lib/, as it's now used by client.c to
authorRichard Kettlewell <rjk@greenend.org.uk>
Thu, 20 Dec 2007 16:02:39 +0000 (16:02 +0000)
committerRichard Kettlewell <rjk@greenend.org.uk>
Thu, 20 Dec 2007 16:02:39 +0000 (16:02 +0000)
pick out root's password (or others if it could read them, but it
can't).

34 files changed:
CHANGES
README
README.upgrades
clients/Makefile.am
clients/authorize.c
clients/authorize.h
clients/disorder.c
doc/disorder_config.5.in
lib/Makefile.am
lib/client.c
lib/client.h
lib/cookies.c
lib/trackdb-int.h [moved from server/trackdb-int.h with 84% similarity]
lib/trackdb.c [moved from server/trackdb.c with 88% similarity]
lib/trackdb.h [moved from server/trackdb.h with 67% similarity]
python/disorder.py.in
scripts/completion.bash
server/Makefile.am
server/api-server.c
server/disorderd.c
server/server.c
server/setup.c [deleted file]
server/setup.h [deleted file]
tests/Makefile.am
tests/cookie.py
tests/dbversion.py
tests/dtest.py
tests/dump.py
tests/files.py
tests/play.py
tests/queue.py
tests/search.py
tests/user-upgrade.py [new file with mode: 0755]
tests/user.py [new file with mode: 0755]

diff --git a/CHANGES b/CHANGES
index cb8df1a06404264d04654558c8c43b02fef50e76..ada5cf916ec78db1a1ae88f270670d238c545078 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,5 +1,11 @@
 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
diff --git a/README b/README
index f6872cb08f05d0faefc2b80ebe936595357fd663..e3d9c2c53160fe05adeb675600b75780864a29ed 100644 (file)
--- a/README
+++ b/README
@@ -160,8 +160,8 @@ skip steps 1 to 6 and configure it via debconf.  This is strongly recommended!
 
      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
index 915a79416593eb266cc727dadbce86aa7c5db5f5..5bea1cdaf642f9cb191bee6577841ebb6ab00cab 100644 (file)
@@ -14,6 +14,17 @@ upgrading between particular versions.  Minor versions are not
 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
index 16f7437bc93f872191f162fffe0badc4bfd2ec13..7baa667ae599975b2c782aa63fb5e1f938ca2e44 100644 (file)
@@ -27,7 +27,7 @@ AM_CPPFLAGS=-I${top_srcdir}/lib -I../lib
 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 \
@@ -38,7 +38,7 @@ disorderfm_DEPENDENCIES=$(LIBOBJS) ../lib/libdisorder.a
 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
index 22c321f815b3fff1c6f532f3997d3c51dec141e3..8697abe21fdab488d28c756df957f3d4d16e6da2 100644 (file)
 #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;
@@ -75,21 +81,10 @@ int authorize(const char *user) {
   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;
 }
 
index ad78deba704b607bc6a64158cc25e891d1d55180..07e87efca6fbd8e36bbc6193d51e179a6bb502d0 100644 (file)
@@ -20,7 +20,7 @@
 #ifndef AUTHORIZE_H
 #define AUTHORIZE_H
 
-int authorize(const char *user);
+int authorize(disorder_client *client, const char *user);
 
 #endif /* AUTHORIZE_H */
 
index 7ee9865bad53e39dcd2cc58a7ded0bab7d9941de..7d15afea72d8f54076dc191032134f2f664aae99 100644 (file)
@@ -64,6 +64,8 @@ static const struct option options[] = {
   { "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 }
 };
 
@@ -334,9 +336,9 @@ static int isarg_filename(const char *s) {
   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;
 }
 
@@ -410,6 +412,12 @@ static void cf_rtp_address(disorder_client *c,
   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;
@@ -417,6 +425,8 @@ static const struct command {
   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",
@@ -538,13 +548,14 @@ int main(int argc, char **argv) {
   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();
@@ -553,10 +564,18 @@ int main(int argc, char **argv) {
     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);
index 6cb4a7897ddc4168a8cc89d88b57b773c5295879..6eb57b8323da0d5db98b25945887ce9f1e774ca0 100644 (file)
@@ -1,4 +1,3 @@
-
 .\"
 .\" Copyright (C) 2004, 2005, 2006, 2007 Richard Kettlewell
 .\"
@@ -566,13 +565,6 @@ This must be the full URL, e.g. \fBhttp://myhost/cgi-bin/jukebox\fR and not
 \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
index 13bafc69c21cc2595cede4bbda1c499ebd0ff907..7c792d8bbee97dc58a934efc524f6b077b6037c7 100644 (file)
@@ -62,6 +62,7 @@ libdisorder_a_SOURCES=charset.c charset.h             \
        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                             \
index 27b7beb7692afde6ec4fb89dacd7eac64091e54c..e4e28e89eba405a5f8e3f89205cc30d399a7c636 100644 (file)
@@ -31,6 +31,7 @@
 #include <errno.h>
 #include <netdb.h>
 #include <stdlib.h>
+#include <pcre.h>
 
 #include "log.h"
 #include "mem.h"
@@ -49,6 +50,7 @@
 #include "addr.h"
 #include "authhash.h"
 #include "client-common.h"
+#include "trackdb.h"
 
 struct disorder_client {
   FILE *fpin, *fpout;
@@ -154,22 +156,24 @@ static int connect_sock(void *vc,
                        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);
 }
@@ -646,6 +650,15 @@ int disorder_rtp_address(disorder_client *c, char **addressp, char **portp) {
   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
index 39a68304d80498ca4400344c5293974bbb4c6268..ab93115f91c6610dca093c53ccfcdb1caf5089b6 100644 (file)
@@ -188,6 +188,10 @@ int disorder_new_tracks(disorder_client *c,
 
 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 */
 
 /*
index 4ac9f656cdc3b952bf4c3a9a642103b2bf299c8b..666c6ce714d9db01dd166a01cd5df3bea4a2c717 100644 (file)
@@ -30,6 +30,7 @@
 #include <errno.h>
 #include <time.h>
 #include <gcrypt.h>
+#include <pcre.h>
 
 #include "cookies.h"
 #include "hash.h"
@@ -39,6 +40,7 @@
 #include "mime.h"
 #include "configuration.h"
 #include "kvp.h"
+#include "trackdb.h"
 
 /** @brief Hash function used in signing HMAC */
 #define ALGO GCRY_MD_SHA1
@@ -112,10 +114,9 @@ static char *sign(const uint8_t *key,
  * @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, ';')) {
@@ -123,14 +124,11 @@ char *make_cookie(const char *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)
@@ -155,8 +153,8 @@ char *verify_cookie(const char *cookie) {
   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)) {
@@ -189,14 +187,11 @@ char *verify_cookie(const char *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);
similarity index 84%
rename from server/trackdb-int.h
rename to lib/trackdb-int.h
index 441a056f397da61cca98b9699c79cd94212fe423..1d31cfe83cc646d13cdab3aff7cf1ffda7ea4b5f 100644 (file)
@@ -53,6 +53,24 @@ void trackdb_abort_transaction(DB_TXN *tid);
 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,
similarity index 88%
rename from server/trackdb.c
rename to lib/trackdb.c
index 4c00e2826e582b988213bb1cc387f0f8536b8521..d3e6e40d61fdff446029a1a412f2868502041368 100644 (file)
  * 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"
@@ -39,6 +42,7 @@
 #include <sys/wait.h>
 #include <dirent.h>
 #include <sys/stat.h>
+#include <gcrypt.h>
 
 #include "event.h"
 #include "mem.h"
@@ -59,6 +63,7 @@
 #include "hash.h"
 #include "unicode.h"
 #include "unidata.h"
+#include "mime.h"
 
 #define RESCAN "disorder-rescan"
 #define DEADLOCK "disorder-deadlock"
@@ -190,8 +195,6 @@ void trackdb_init(int flags) {
     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
@@ -206,7 +209,7 @@ void trackdb_init(int flags) {
       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);
@@ -270,8 +273,11 @@ static pid_t subprogram(ev_source *ev, const char *prog,
       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",
@@ -345,20 +351,23 @@ static DB *open_db(const char *path,
 /** @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;
@@ -415,17 +424,17 @@ void trackdb_open(int flags) {
   }
   /* 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];
@@ -440,7 +449,7 @@ void trackdb_open(int flags) {
 /* close track databases */
 void trackdb_close(void) {
   int err;
-  
+
   /* sanity checks */
   assert(opened == 1);
   --opened;
@@ -513,7 +522,12 @@ int trackdb_putdata(DB *db,
   }
 }
 
-/* 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) {
@@ -918,7 +932,7 @@ static int gettrackdata(const char *track,
   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) {
@@ -950,7 +964,7 @@ int trackdb_notice(const char *track,
                    const char *path) {
   int err;
   DB_TXN *tid;
-  
+
   for(;;) {
     tid = trackdb_begin_transaction();
     err = trackdb_notice_tid(track, path, tid);
@@ -1043,11 +1057,12 @@ int trackdb_obsolete(const char *track, DB_TXN *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 */
@@ -1236,7 +1251,7 @@ static int search_league(struct vector *v, int count, DB_TXN *tid) {
 char **trackdb_stats(int *nstatsp) {
   DB_TXN *tid;
   struct vector v;
-  
+
   vector_init(&v);
   for(;;) {
     tid = trackdb_begin_transaction();
@@ -1356,7 +1371,7 @@ int trackdb_set(const char *track,
   if(value) {
     /* TODO: if value matches default then set value=0 */
   }
-  
+
   for(;;) {
     tid = trackdb_begin_transaction();
     if((err = gettrackdata(track, &t, &p, 0,
@@ -1384,7 +1399,8 @@ int trackdb_set(const char *track,
            || (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);
@@ -1465,7 +1481,7 @@ fail:
 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)
@@ -1827,7 +1843,7 @@ static int do_list(struct vector *v, const char *dir,
   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);
@@ -1913,7 +1929,7 @@ fail:
   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) {
@@ -1946,7 +1962,7 @@ char **trackdb_search(char **wordlist, int nwordlist, int *ntracks) {
   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 */
@@ -2395,6 +2411,287 @@ void trackdb_gc(void) {
    * 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
similarity index 67%
rename from server/trackdb.h
rename to lib/trackdb.h
index d9d7ceb020aca517f5e46c784a39e929f1d57f45..b3179747fe528ff22ee7dee747aa9da94fd2dc06 100644 (file)
@@ -17,7 +17,7 @@
  * 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
@@ -29,6 +29,72 @@ extern const struct cache_type cache_files_type;
 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
 
@@ -56,6 +122,9 @@ extern unsigned long cache_files_hits, cache_files_misses;
 /** @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 */
@@ -153,6 +222,19 @@ const char *trackdb_get_global(const char *name);
 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 */
 
index c501be5b8802002d3bb45b16b85d1f8f39dca23c..64789815fddc7214f1f290b1421af926ddf73115 100644 (file)
@@ -276,7 +276,7 @@ class client:
   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
@@ -294,6 +294,8 @@ class client:
     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
@@ -375,10 +377,18 @@ class client:
         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'
@@ -850,6 +860,14 @@ class client:
     """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
 
index 3f863eea69522b2c4034193a8dfd96acc7d11df3..3c13c294d75aba0533492ffcb5dca064a73b65b3 100644 (file)
@@ -31,7 +31,7 @@ complete -o default \
              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
index 4332ed4d3dc0f7ddb5564ac78ca832252976e992..5d38b56b244159e47133d1b0af8771afa45bda55 100644 (file)
@@ -32,17 +32,15 @@ disorderd_SOURCES=disorderd.c                               \
        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
@@ -69,26 +67,25 @@ disorder_normalize_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
@@ -97,7 +94,7 @@ disorder_cgi_SOURCES=dcgi.c dcgi.h                    \
        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
 
index 04c0f25c8c254541c2bf373ff78719380be420f0..77960a3e90fb800da548b2c84036a3e44069ddcb 100644 (file)
@@ -19,6 +19,7 @@
  */
 
 #include <config.h>
+#include "types.h"
 
 #include <stdio.h>
 #include <stdlib.h>
index 3d77d7329c5af528471eb7070bad6c68553886c8..c1f578e224124056682955a119210c1641048084 100644 (file)
@@ -56,7 +56,6 @@
 #include "mixer.h"
 #include "eventlog.h"
 #include "printf.h"
-#include "setup.h"
 
 static ev_source *ev;
 
@@ -249,8 +248,6 @@ int main(int argc, char **argv) {
     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
@@ -279,8 +276,12 @@ int main(int argc, char **argv) {
   /* 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 */
index cd97435cd2f4fcb3f8662b64438c0429e59b100d..145bfe222f9c277de23ee84e51a3e80833cfc1be 100644 (file)
@@ -403,8 +403,7 @@ static const char *connection_host(struct conn *c) {
 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");
@@ -416,16 +415,15 @@ static int c_user(struct conn *c,
     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];
@@ -1029,6 +1027,42 @@ static int c_revoke(struct conn *c,
   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 */
 
@@ -1038,11 +1072,14 @@ static const struct command {
   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 },
@@ -1081,8 +1118,9 @@ static const struct command {
   { "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 }
 };
diff --git a/server/setup.c b/server/setup.c
deleted file mode 100644 (file)
index 4ccb00e..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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:
-*/
diff --git a/server/setup.h b/server/setup.h
deleted file mode 100644 (file)
index ea31106..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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:
-*/
index 9527a55fc1d72d2033753c8b14037e6c300b71b7..c78401aa6a12829448d9b316c67e603426f5570e 100644 (file)
@@ -30,4 +30,4 @@ check:
        ${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
index 7f0045434d35a2807d5a8e297be6248bc41e388a..c0b71e08bc9d04a2bbc5dea6c93166346d445277 100755 (executable)
@@ -23,6 +23,7 @@ import dtest,disorder
 def test():
     """Exercise cookie protocol"""
     dtest.start_daemon()
+    dtest.create_user()
     print " connecting"
     c = disorder.client()
     v = c.version()
index 35f71adae04172bac8194b655c0b837efc07e0d5..4ae71d7a81c479fee5643720eb3079438c0bae29 100755 (executable)
@@ -28,6 +28,7 @@ def test():
     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)
index ef25fc4bdd42307b22fd718f2db99df73fe44647..5ab56edd48a95904e775e437d6352a6255077452 100644 (file)
@@ -166,21 +166,8 @@ def bindable(p):
     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
@@ -193,8 +180,7 @@ stopword 21 22 23 24 25 26 27 28 29 30
 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
@@ -211,8 +197,25 @@ broadcast 127.0.0.1 %d
 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()
@@ -253,6 +256,15 @@ Start the 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()
 
index ff28c6392c5bb3ade40d3e401e57f46aefea565e..f3122c237e4399f4897ac8380aa9ddeaa43b658f 100755 (executable)
@@ -23,16 +23,17 @@ import dtest,time,disorder,re
 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(),
@@ -42,25 +43,25 @@ def test():
     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"
@@ -68,15 +69,15 @@ def test():
                          "--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)"
index 69e98d4f9a6df494f49c2f05f5bed72978dd343d..7c22ffcede5d49d9206969a180b17b8a12965a91 100755 (executable)
@@ -23,6 +23,7 @@ import dtest,time,disorder,sys
 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()
index 92bb6649dfc5c248cd0f3f2d71616cbedf148600..9292dfc8bcdb71679017c6031f0b79fda6996094 100755 (executable)
@@ -23,9 +23,10 @@ import dtest,time,disorder,re
 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()
index cc0b619a31e719b97fc81640e08e4fb1694e9c17..c78d6ed82c5ecd8f7297402cd58ed7807144d47d 100755 (executable)
@@ -23,6 +23,7 @@ import dtest,time,disorder,re
 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()
index 01ab893efa5360f742a2bb738ec6be95fdfd78c0..3be6ec932a0074f73713beacdde9de18c80c093d 100755 (executable)
@@ -41,6 +41,7 @@ def check_search_results(terms, expected):
 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()
diff --git a/tests/user-upgrade.py b/tests/user-upgrade.py
new file mode 100755 (executable)
index 0000000..6141714
--- /dev/null
@@ -0,0 +1,41 @@
+#! /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()
diff --git a/tests/user.py b/tests/user.py
new file mode 100755 (executable)
index 0000000..8c13232
--- /dev/null
@@ -0,0 +1,47 @@
+#! /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()