<div class=section>
- <h3>Mac OS X</h3>
-
+ <h3>Server</h3>
+
<div class=section>
- <p>The <tt>device</tt> configuration option and <tt>--device</tt> option
- to <tt>disorder-playrtp</tt> now work. Devices may be specified either
- by UID or name. Fixes <a
+ <p>The <tt>device</tt> configuration option work under OS X. Devices may
+ be specified either by UID or name. Fixes <a
href="http://code.google.com/p/disorder/issues/detail?id=27">Issue
27</a>.</p>
+
+ <p>Confirmation URLs for online registrations are cleaner now.</p>
+
+ </div>
+
+ <h3>RTP Player</h3>
+
+ <div class=section>
+
+ <p>There is a new <tt>--command</tt> option which allows the RTP player
+ to send audio data to a user-chosen command instead of an audio API. See
+ the man page for details.</p>
+
+ <p>The <tt>--device</tt> option to <tt>disorder-playrtp</tt> now works
+ under OS X (as above).</p>
</div>
If you install from .deb files then much of this work is automated.
+* 4.x -> 4.4
+
+The syntax of confirmation strings for online registrations has changed and old
+ones no longer work. This only affects users who registered before the upgrade
+but have not yet confirmed their login. You can delete such half-created users
+with 'disorder deluser USERNAME' (as an administrative user, for instance as
+root on the server) and they can start the registration process again.
+
* 3.0 -> 4.x
If you customized any of the templates, you will pretty much have to start from
rm -f $state/__db.*
rm -f $state/noticed.db
rm -f $state/search.db
+ rm -f $state/isearch.db
rm -f $state/tags.db
rm -f $state/tracks.db
}
rm -f $state/prefs.db
rm -f $state/schedule.db
rm -f $state/users.db
+ rm -f $state/playlists.db
}
case "$1" in
* @param nwords Length of bignum
* @return !v
*/
-static int zero(const unsigned long *v, int nwords) {
+static int zero(const uint32_t *v, int nwords) {
int n;
for(n = 0; n < nwords && !v[n]; ++n)
*
* The quotient is stored in @p v.
*/
-static unsigned divide(unsigned long *v, int nwords, unsigned long m) {
+static unsigned divide(uint32_t *v, int nwords, unsigned long m) {
unsigned long r = 0, a, b;
int n;
return r;
}
+/** @brief Multiple v by m and add a
+ * @param v Pointer to bigendian bignum
+ * @param nwords Length of bignum
+ * @param m Value to multiply by
+ * @param a Value to add
+ * @return 0 on success, non-0 on overflow
+ *
+ * Does v = m * v + a.
+ */
+static int mla(uint32_t *v, int nwords, uint32_t m, uint32_t a) {
+ int n = nwords - 1;
+ uint32_t carry = a;
+
+ while(n >= 0) {
+ const uint64_t p = (uint64_t)v[n] * m + carry;
+ carry = (uint32_t)(p >> 32);
+ v[n] = (uint32_t)p;
+ --n;
+ }
+ /* If there is still a carry then we overflowed */
+ return !!carry;
+}
+
+static const char basen_chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
/** @brief Convert v to a chosen base
* @param v Pointer to bigendian bignum (modified!)
* @param nwords Length of bignum
* @return 0 on success, -1 if the buffer is too small
*
* Converts @p v to a string in the given base using decimal digits, lower case
- * letter sand upper case letters as digits.
+ * letters and upper case letters as digits.
+ *
+ * The inverse of nesab().
*/
-int basen(unsigned long *v,
+int basen(uint32_t *v,
int nwords,
char buffer[],
size_t bufsize,
unsigned base) {
size_t i = bufsize;
- static const char chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
do {
if(i <= 1)
return -1; /* overflow */
- buffer[--i] = chars[divide(v, nwords, base)];
+ buffer[--i] = basen_chars[divide(v, nwords, base)];
} while(!zero(v, nwords));
memmove(buffer, buffer + i, bufsize - i);
buffer[bufsize - i] = 0;
return 0;
}
+/** @brief Convert a string back to a large integer in an arbitrary base
+ * @param v Where to store result as a bigendian bignum
+ * @param nwords Space available in @p v
+ * @param s Input string
+ * @param base Number base (2..62)
+ * @return 0 on success, non-0 on overflow or invalid input
+ *
+ * The inverse of basen(). If the number is much smaller than the buffer then
+ * the first words will be 0.
+ */
+int nesab(uint32_t *v,
+ int nwords,
+ const char *s,
+ unsigned base) {
+ /* Initialize to 0 */
+ memset(v, 0, nwords * sizeof *v);
+ while(*s) {
+ const char *dp = strchr(basen_chars, *s++);
+ if(!dp)
+ return -1;
+ if(mla(v, nwords, base, dp - basen_chars))
+ return -1;
+ }
+ return 0;
+}
+
/*
Local Variables:
c-basic-offset:2
#ifndef BASEN_H
#define BASEN_H
-int basen(unsigned long *v,
+int basen(uint32_t *v,
int nwords,
char buffer[],
size_t bufsize,
* Returns 0 on success or -1 on overflow.
*/
+int nesab(uint32_t *v,
+ int nwords,
+ const char *s,
+ unsigned base);
+
#endif /* BASEN_H */
/*
/** @brief Return a random ID string */
char *random_id(void) {
- unsigned long words[2];
+ uint32_t words[2];
char id[128];
random_get(words, sizeof words);
#include "test.h"
static void test_basen(void) {
- unsigned long v[64];
+ uint32_t v[64];
char buffer[1024];
v[0] = 999;
insist(basen(v, 1, buffer, sizeof buffer, 10) == 0);
check_string(buffer, "999");
+ memset(v, 0xFF, sizeof v);
+ insist(nesab(v, 1, buffer, 10) == 0);
+ check_integer(v[0], 999);
+ check_integer(v[1], 0xFFFFFFFF);
+ insist(nesab(v, 4, buffer, 10) == 0);
+ check_integer(v[0], 0);
+ check_integer(v[1], 0);
+ check_integer(v[2], 0);
+ check_integer(v[3], 999);
+ check_integer(v[4], 0xFFFFFFFF);
v[0] = 1+2*7+3*7*7+4*7*7*7;
insist(basen(v, 1, buffer, sizeof buffer, 7) == 0);
v[3] = 0x0C0D0E0F;
insist(basen(v, 4, buffer, sizeof buffer, 256) == 0);
check_string(buffer, "123456789abcdef");
+ memset(v, 0xFF, sizeof v);
+ insist(nesab(v, 4, buffer, 256) == 0);
+ check_integer(v[0], 0x00010203);
+ check_integer(v[1], 0x04050607);
+ check_integer(v[2], 0x08090A0B);
+ check_integer(v[3], 0x0C0D0E0F);
+ check_integer(v[4], 0xFFFFFFFF);
+ memset(v, 0xFF, sizeof v);
+ insist(nesab(v, 8, buffer, 256) == 0);
+ check_integer(v[0], 0);
+ check_integer(v[1], 0);
+ check_integer(v[2], 0);
+ check_integer(v[3], 0);
+ check_integer(v[4], 0x00010203);
+ check_integer(v[5], 0x04050607);
+ check_integer(v[6], 0x08090A0B);
+ check_integer(v[7], 0x0C0D0E0F);
+ check_integer(v[8], 0xFFFFFFFF);
v[0] = 0x00010203;
v[1] = 0x04050607;
v[3] = 0x0C0D0E0F;
insist(basen(v, 4, buffer, sizeof buffer, 16) == 0);
check_string(buffer, "102030405060708090a0b0c0d0e0f");
+ memset(v, 0xFF, sizeof v);
+ insist(nesab(v, 4, buffer, 16) == 0);
+ check_integer(v[0], 0x00010203);
+ check_integer(v[1], 0x04050607);
+ check_integer(v[2], 0x08090A0B);
+ check_integer(v[3], 0x0C0D0E0F);
+ check_integer(v[4], 0xFFFFFFFF);
+ memset(v, 0xFF, sizeof v);
+ insist(nesab(v, 8, buffer, 16) == 0);
+ check_integer(v[0], 0);
+ check_integer(v[1], 0);
+ check_integer(v[2], 0);
+ check_integer(v[3], 0);
+ check_integer(v[4], 0x00010203);
+ check_integer(v[5], 0x04050607);
+ check_integer(v[6], 0x08090A0B);
+ check_integer(v[7], 0x0C0D0E0F);
+ check_integer(v[8], 0xFFFFFFFF);
v[0] = 0x00010203;
v[1] = 0x04050607;
*/
#include "disorder-server.h"
+#include "basen.h"
#ifndef NONCE_SIZE
# define NONCE_SIZE 16
#endif
#ifndef CONFIRM_SIZE
-# define CONFIRM_SIZE 10
+/** @brief Size of nonce in confirmation string in 32-bit words
+ *
+ * 64 bits gives 11 digits (in base 62).
+ */
+# define CONFIRM_SIZE 2
#endif
int volume_left, volume_right; /* last known volume */
return 1; /* completed */
}
-/** @brief Base64 mapping table for confirmation strings
- *
- * This is used with generic_to_base64() and generic_base64(). We cannot use
- * the MIME table as that contains '+' and '=' which get quoted when
- * URL-encoding. (The CGI still does the URL encoding but it is desirable to
- * avoid it being necessary.)
- */
-static const char confirm_base64_table[] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/.*";
-
static int c_register(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
- char *buf, *cs;
- size_t bufsize;
- int offset;
-
- /* The confirmation string is base64(username;nonce) */
- bufsize = strlen(vec[0]) + CONFIRM_SIZE + 2;
- buf = xmalloc_noptr(bufsize);
- offset = byte_snprintf(buf, bufsize, "%s;", vec[0]);
- gcry_randomize(buf + offset, CONFIRM_SIZE, GCRY_STRONG_RANDOM);
- cs = generic_to_base64((uint8_t *)buf, offset + CONFIRM_SIZE,
- confirm_base64_table);
+ char *cs;
+ uint32_t nonce[CONFIRM_SIZE];
+ char nonce_str[(32 * CONFIRM_SIZE) / 5 + 1];
+
+ /* The confirmation string is username/base62(nonce). The confirmation
+ * process will pick the username back out to identify them but the _whole_
+ * string is used as the confirmation string. Base 62 means we used only
+ * letters and digits, minimizing the chance of the URL being mispasted. */
+ gcry_randomize(nonce, sizeof nonce, GCRY_STRONG_RANDOM);
+ if(basen(nonce, CONFIRM_SIZE, nonce_str, sizeof nonce_str, 62)) {
+ error(0, "buffer too small encoding confirmation string");
+ sink_writes(ev_writer_sink(c->w), "550 Cannot create user\n");
+ }
+ byte_xasprintf(&cs, "%s/%s", vec[0], nonce_str);
if(trackdb_adduser(vec[0], vec[1], config->default_rights, vec[2], cs))
sink_writes(ev_writer_sink(c->w), "550 Cannot create user\n");
else
static int c_confirm(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
- size_t nuser;
char *user, *sep;
rights_type rights;
const char *host;
sink_writes(ev_writer_sink(c->w), "530 Authentication failure\n");
return 1;
}
- if(!(user = generic_base64(vec[0], &nuser, confirm_base64_table))
- || !(sep = memchr(user, ';', nuser))) {
+ /* Picking the LAST / means we don't (here) rule out slashes in usernames. */
+ if(!(sep = strrchr(vec[0], '/'))) {
sink_writes(ev_writer_sink(c->w), "550 Malformed confirmation string\n");
return 1;
}
- *sep = 0;
+ user = xstrndup(vec[0], sep - vec[0]);
if(trackdb_confirm(user, vec[0], &rights))
sink_writes(ev_writer_sink(c->w), "550 Incorrect confirmation string\n");
else {
/** @brief Lock protecting data structures
*
* This lock protects values shared between the main thread and the callback.
- * It is needed e.g. if changing @ref playing or if modifying buffer pointers.
- * It is not needed to add a new track, to read values only modified in the
- * same thread, etc.
+ *
+ * It is held 'all' the time by the main thread, the exceptions being when
+ * called activate/deactivate callbacks and when calling (potentially) slow
+ * system calls (in particular poll(), where in fact the main thread will spend
+ * most of its time blocked).
+ *
+ * The callback holds it when it's running.
*/
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
t->id, t->eof, t->used));
if(t->eof)
return -1;
- pthread_mutex_lock(&lock);
if(t->used < sizeof t->buffer) {
/* there is room left in the buffer */
where = (t->start + t->used) % sizeof t->buffer;
rc = 0;
}
}
- pthread_mutex_unlock(&lock);
return rc;
}
memset(&sm, 0, sizeof sm);
sm.type = paused ? SM_PAUSED : SM_PLAYING;
strcpy(sm.id, playing->id);
- pthread_mutex_lock(&lock);
sm.data = playing->played / (uaudio_rate * uaudio_channels);
- pthread_mutex_unlock(&lock);
speaker_send(1, &sm);
time(&last_report);
}
if(!provided_samples) {
memset(buffer, 0, max_bytes);
provided_samples = max_samples;
+ if(playing)
+ info("%zu samples silence, playing->used=%zu", provided_samples, playing->used);
+ else
+ info("%zu samples silence, playing=NULL", provided_samples);
}
pthread_mutex_unlock(&lock);
return provided_samples;
int n, fd, stdin_slot, timeout, listen_slot, sigpipe_slot;
/* Keep going while our parent process is alive */
+ pthread_mutex_lock(&lock);
while(getppid() != 1) {
int force_report = 0;
} else
t->slot = -1;
}
- sigpipe_slot = addfd(sigpipe[1], POLLIN);
+ sigpipe_slot = addfd(sigpipe[0], POLLIN);
/* Wait for something interesting to happen */
+ pthread_mutex_unlock(&lock);
n = poll(fds, fdno, timeout);
+ pthread_mutex_lock(&lock);
if(n < 0) {
if(errno == EINTR) continue;
fatal(errno, "error calling poll");
D(("SM_CANCEL %s", sm.id));
t = removetrack(sm.id);
if(t) {
- pthread_mutex_lock(&lock);
if(t == playing || t == pending_playing) {
/* Scratching the track that the server believes is playing,
* which might either be the actual playing track or a pending
}
strcpy(sm.id, t->id);
destroy(t);
- pthread_mutex_unlock(&lock);
} else {
/* Probably scratching the playing track well before it's got
* going, but could indicate a bug, so we log this as an error. */
}
/* When the track is actually finished, deconfigure it */
if(playing && playing->eof && !playing->used) {
- pthread_mutex_lock(&lock);
removetrack(playing->id);
destroy(playing);
playing = 0;
- pthread_mutex_unlock(&lock);
}
/* Act on the pending SM_PLAY */
if(!playing && pending_playing) {
- pthread_mutex_lock(&lock);
playing = pending_playing;
pending_playing = 0;
- pthread_mutex_unlock(&lock);
force_report = 1;
}
/* Impose any state change required by the above */
if(playable()) {
if(!activated) {
activated = 1;
+ pthread_mutex_unlock(&lock);
backend->activate();
+ pthread_mutex_lock(&lock);
}
} else {
if(activated) {
activated = 0;
+ pthread_mutex_unlock(&lock);
backend->deactivate();
+ pthread_mutex_lock(&lock);
}
}
/* If we've not reported our state for a second do so now. */