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.
 
 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
 * 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
 
 
      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
 
    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.
 
 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
 * 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 \
 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_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 \
 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
 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 <fcntl.h>
 #include <stdio.h>
 
+#include "client.h"
 #include "authorize.h"
 #include "log.h"
 #include "configuration.h"
 #include "printf.h"
 #include "hex.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;
   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);
 
   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;
 }
 
   return 0;
 }
 
index ad78deba704b607bc6a64158cc25e891d1d55180..07e87efca6fbd8e36bbc6193d51e179a6bb502d0 100644 (file)
@@ -20,7 +20,7 @@
 #ifndef AUTHORIZE_H
 #define AUTHORIZE_H
 
 #ifndef AUTHORIZE_H
 #define AUTHORIZE_H
 
-int authorize(const char *user);
+int authorize(disorder_client *client, const char *user);
 
 #endif /* AUTHORIZE_H */
 
 
 #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' },
   { "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 }
 };
 
   { 0, 0, 0, 0 }
 };
 
@@ -334,9 +336,9 @@ static int isarg_filename(const char *s) {
   return s[0] == '/';
 }
 
   return s[0] == '/';
 }
 
-static void cf_authorize(disorder_client attribute((unused)) *c,
+static void cf_authorize(disorder_client *c,
                         char **argv) {
                         char **argv) {
-  if(!authorize(argv[0]))
+  if(!authorize(c, argv[0]))
     auto_reconfigure = 1;
 }
 
     auto_reconfigure = 1;
 }
 
@@ -410,6 +412,12 @@ static void cf_rtp_address(disorder_client *c,
   xprintf("address: %s\nport: %s\n", address, port);
 }
 
   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;
 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[] = {
   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",
   { "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;
   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");
 
   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();
     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 '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");
     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);
   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
 .\"
 .\"
 .\" 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
 \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
 .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                                       \
        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                             \
        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 <errno.h>
 #include <netdb.h>
 #include <stdlib.h>
+#include <pcre.h>
 
 #include "log.h"
 #include "mem.h"
 
 #include "log.h"
 #include "mem.h"
@@ -49,6 +50,7 @@
 #include "addr.h"
 #include "authhash.h"
 #include "client-common.h"
 #include "addr.h"
 #include "authhash.h"
 #include "client-common.h"
+#include "trackdb.h"
 
 struct disorder_client {
   FILE *fpin, *fpout;
 
 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;
                        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(!(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 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;
 }
 
   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
 /*
 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_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 */
 
 /*
 #endif /* CLIENT_H */
 
 /*
index 4ac9f656cdc3b952bf4c3a9a642103b2bf299c8b..666c6ce714d9db01dd166a01cd5df3bea4a2c717 100644 (file)
@@ -30,6 +30,7 @@
 #include <errno.h>
 #include <time.h>
 #include <gcrypt.h>
 #include <errno.h>
 #include <time.h>
 #include <gcrypt.h>
+#include <pcre.h>
 
 #include "cookies.h"
 #include "hash.h"
 
 #include "cookies.h"
 #include "hash.h"
@@ -39,6 +40,7 @@
 #include "mime.h"
 #include "configuration.h"
 #include "kvp.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
 
 /** @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) {
  * @return Cookie or NULL
  */
 char *make_cookie(const char *user) {
-  char *password;
+  const char *password;
   time_t now;
   char *b, *bp, *c, *g;
   time_t now;
   char *b, *bp, *c, *g;
-  int n;
 
   /* semicolons aren't allowed in usernames */
   if(strchr(user, ';')) {
 
   /* 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 */
     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;
   }
     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)
   /* 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 *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)) {
 
   /* 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 */
     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;
   }
     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);
   /* 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 */
 
 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,
 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
  * 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 <config.h>
 #include "types.h"
@@ -39,6 +42,7 @@
 #include <sys/wait.h>
 #include <dirent.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
 #include <dirent.h>
 #include <sys/stat.h>
+#include <gcrypt.h>
 
 #include "event.h"
 #include "mem.h"
 
 #include "event.h"
 #include "mem.h"
@@ -59,6 +63,7 @@
 #include "hash.h"
 #include "unicode.h"
 #include "unidata.h"
 #include "hash.h"
 #include "unicode.h"
 #include "unidata.h"
+#include "mime.h"
 
 #define RESCAN "disorder-rescan"
 #define DEADLOCK "disorder-deadlock"
 
 #define RESCAN "disorder-rescan"
 #define DEADLOCK "disorder-deadlock"
@@ -190,8 +195,6 @@ void trackdb_init(int flags) {
     struct stat st;
     char *p;
 
     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
     /* 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(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);
           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);
     }
       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",
     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
  *
 /** @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
  * - @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;
  */
 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 */
 
   /* 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;
   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",
   }
   /* 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",
   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",
   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",
   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",
   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];
   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;
 /* close track databases */
 void trackdb_close(void) {
   int err;
-  
+
   /* sanity checks */
   assert(opened == 1);
   --opened;
   /* 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) {
 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;
   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) {
   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;
                    const char *path) {
   int err;
   DB_TXN *tid;
-  
+
   for(;;) {
     tid = trackdb_begin_transaction();
     err = trackdb_notice_tid(track, path, 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 */
     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(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 */
       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;
 char **trackdb_stats(int *nstatsp) {
   DB_TXN *tid;
   struct vector v;
-  
+
   vector_init(&v);
   for(;;) {
     tid = trackdb_begin_transaction();
   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 */
   }
   if(value) {
     /* TODO: if value matches default then set value=0 */
   }
-  
+
   for(;;) {
     tid = trackdb_begin_transaction();
     if((err = gettrackdata(track, &t, &p, 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
            || (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);
         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;
 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)
   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;
   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);
   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(np)
     *np = v.nvec;
   return v.vec;
-}  
+}
 
 /* If S is tag:something, return something.  Else return 0. */
 static const char *checktag(const char *s) {
 
 /* 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;
   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 */
     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. */
 }
 
    * 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
 /*
 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
  */
  * 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
  * @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 */
 
 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 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 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 */
 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);
 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 */
 
 
 #endif /* TRACKDB_H */
 
index c501be5b8802002d3bb45b16b85d1f8f39dca23c..64789815fddc7214f1f290b1421af926ddf73115 100644 (file)
@@ -276,7 +276,7 @@ class client:
   debug_proto = 0x0001
   debug_body = 0x0002
 
   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
     """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.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
     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:
         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 = sha.sha()
-          h.update(self.config['password'])
+          h.update(password)
           h.update(binascii.unhexlify(challenge))
           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'
         else:
           self._simple("cookie", cookie)
         self.state = 'connected'
@@ -850,6 +860,14 @@ class client:
     """Revoke a login cookie"""
     self._simple("revoke")
 
     """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
 
   ########################################################################
   # 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
              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
              -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                   \
        play.c play.h                                   \
        server.c server.h                               \
        server-queue.c server-queue.h                   \
-       setup.c setup.h                                 \
        state.c state.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
 
        ../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_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                              \
 
 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
 
        ../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                                   \
 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
 
        ../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
 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 \
        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
 
 disorder_cgi_LDFLAGS=-export-dynamic
 disorder_cgi_DEPENDENCIES=../lib/libdisorder.a
 
index 04c0f25c8c254541c2bf373ff78719380be420f0..77960a3e90fb800da548b2c84036a3e44069ddcb 100644 (file)
@@ -19,6 +19,7 @@
  */
 
 #include <config.h>
  */
 
 #include <config.h>
+#include "types.h"
 
 #include <stdio.h>
 #include <stdlib.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 "mixer.h"
 #include "eventlog.h"
 #include "printf.h"
-#include "setup.h"
 
 static ev_source *ev;
 
 
 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();
     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
   /* 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);
   /* 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);
   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 */
   /* 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) {
 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");
 
   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 */
     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;
   }
     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];
                 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;
 }
 
   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 */
 
 #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[] = {
   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 },
   { "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 },
   { "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 },
   { "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 },
   { "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 },
   { "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 }
 };
   { "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 \
        ${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()
 def test():
     """Exercise cookie protocol"""
     dtest.start_daemon()
+    dtest.create_user()
     print " connecting"
     c = disorder.client()
     v = c.version()
     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.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)
     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
 
     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
     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
 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
 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))
 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)
     copyfile("%s/sounds/scratch.ogg" % top_srcdir,
              "%s/scratch.ogg" % testroot)
+    default_config()
 
 def start_daemon():
     """start_daemon()
 
 def start_daemon():
     """start_daemon()
@@ -253,6 +256,15 @@ Start the daemon."""
     if waited > 0:
         print "  took about %ds for socket to appear" % waited
 
     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 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()
 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
     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"
     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"
     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(),
     # 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)"
     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)"
     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 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"
     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"
     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")
     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"
     c.setglobal("bar", "after")
     dtest.stop_daemon();
     print "restoring database"
@@ -68,15 +69,15 @@ def test():
                          "--undump", dump])
     dtest.start_daemon(); 
     c = disorder.client()
                          "--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"
     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"
     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"
     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"
     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)"
     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()
 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()
     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()
 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
     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()
     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()
 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()
     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()
 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()
     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()