From: Richard Kettlewell Date: Sat, 14 Mar 2009 10:49:33 +0000 (+0000) Subject: Merge uaudio stragglers. X-Git-Tag: 5.0~177 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/commitdiff_plain/d0b4f0acd54eeebcde98a1b4476b232dbece6048?hp=f75ab9d3d9335b87c183caba8d45af5a9721b29e Merge uaudio stragglers. The command backend's pause mode is now configurable, both for server and disorder-playrtp. --- diff --git a/CHANGES.html b/CHANGES.html index 3722b61..fe3c687 100644 --- a/CHANGES.html +++ b/CHANGES.html @@ -62,15 +62,29 @@ span.command {
-

Mac OS X

- +

Server

+
-

The device configuration option and --device option - to disorder-playrtp now work. Devices may be specified either - by UID or name. Fixes The device configuration option work under OS X. Devices may + be specified either by UID or name. Fixes Issue 27.

+ +

Confirmation URLs for online registrations are cleaner now.

+ +
+ +

RTP Player

+ +
+ +

There is a new --command 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.

+ +

The --device option to disorder-playrtp now works + under OS X (as above).

diff --git a/README.upgrades b/README.upgrades index c586dd8..cbe8637 100644 --- a/README.upgrades +++ b/README.upgrades @@ -17,6 +17,14 @@ all 1.1.x versions. 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 diff --git a/debian/postrm.disorder-server b/debian/postrm.disorder-server index 7ba0933..4cd590a 100755 --- a/debian/postrm.disorder-server +++ b/debian/postrm.disorder-server @@ -30,6 +30,7 @@ cleanup_remove() { 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 } @@ -43,6 +44,7 @@ cleanup_purge() { rm -f $state/prefs.db rm -f $state/schedule.db rm -f $state/users.db + rm -f $state/playlists.db } case "$1" in diff --git a/lib/basen.c b/lib/basen.c index 87d2795..7c1f9ec 100644 --- a/lib/basen.c +++ b/lib/basen.c @@ -31,7 +31,7 @@ * @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) @@ -47,7 +47,7 @@ static int zero(const unsigned long *v, int nwords) { * * 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; @@ -66,6 +66,31 @@ static unsigned divide(unsigned long *v, int nwords, unsigned long m) { 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 @@ -75,26 +100,53 @@ static unsigned divide(unsigned long *v, int nwords, unsigned long m) { * @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 diff --git a/lib/basen.h b/lib/basen.h index 717f2ea..c1a60f0 100644 --- a/lib/basen.h +++ b/lib/basen.h @@ -19,7 +19,7 @@ #ifndef BASEN_H #define BASEN_H -int basen(unsigned long *v, +int basen(uint32_t *v, int nwords, char buffer[], size_t bufsize, @@ -29,6 +29,11 @@ int basen(unsigned long *v, * Returns 0 on success or -1 on overflow. */ +int nesab(uint32_t *v, + int nwords, + const char *s, + unsigned base); + #endif /* BASEN_H */ /* diff --git a/lib/random.c b/lib/random.c index 3f8f714..d6be3d9 100644 --- a/lib/random.c +++ b/lib/random.c @@ -75,7 +75,7 @@ void random_get(void *ptr, size_t bytes) { /** @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); diff --git a/libtests/t-basen.c b/libtests/t-basen.c index 715490c..3cbc044 100644 --- a/libtests/t-basen.c +++ b/libtests/t-basen.c @@ -18,12 +18,22 @@ #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); @@ -35,6 +45,24 @@ static void test_basen(void) { 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; @@ -42,6 +70,24 @@ static void test_basen(void) { 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; diff --git a/server/server.c b/server/server.c index b00a70e..767fa31 100644 --- a/server/server.c +++ b/server/server.c @@ -17,13 +17,18 @@ */ #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 */ @@ -1319,30 +1324,23 @@ static int c_users(struct conn *c, 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 @@ -1353,7 +1351,6 @@ static int c_register(struct conn *c, 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; @@ -1363,12 +1360,12 @@ static int c_confirm(struct conn *c, 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 { diff --git a/server/speaker.c b/server/speaker.c index 8358304..b41c94f 100644 --- a/server/speaker.c +++ b/server/speaker.c @@ -163,9 +163,13 @@ struct track { /** @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; @@ -299,7 +303,6 @@ static int speaker_fill(struct track *t) { 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; @@ -334,7 +337,6 @@ static int speaker_fill(struct track *t) { rc = 0; } } - pthread_mutex_unlock(&lock); return rc; } @@ -366,9 +368,7 @@ static void report(void) { 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); } @@ -437,6 +437,10 @@ static size_t speaker_callback(void *buffer, 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; @@ -449,6 +453,7 @@ static void mainloop(void) { 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; @@ -481,9 +486,11 @@ static void mainloop(void) { } 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"); @@ -570,7 +577,6 @@ static void mainloop(void) { 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 @@ -591,7 +597,6 @@ static void mainloop(void) { } 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. */ @@ -641,30 +646,30 @@ static void mainloop(void) { } /* 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. */