/*
* This file is part of DisOrder.
- * Copyright (C) 2004-2008 Richard Kettlewell
+ * Copyright (C) 2004-2009 Richard Kettlewell
*
- * This program is free software; you can redistribute it and/or modify
+ * 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
+ * the Free Software Foundation, either version 3 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.
- *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#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 */
sink_writes(ev_writer_sink(c->w), "550 cannot resolve track\n");
return 1;
}
- q = queue_add(track, c->who, WHERE_BEFORE_RANDOM);
+ q = queue_add(track, c->who, WHERE_BEFORE_RANDOM, NULL, origin_picked);
queue_write();
- /* If we added the first track, and something is playing, then prepare the
- * new track. If nothing is playing then we don't bother as it wouldn't gain
- * anything. */
- if(q == qhead.next && playing)
- prepare(c->ev, q);
sink_printf(ev_writer_sink(c->w), "252 %s\n", q->id);
+ /* We make sure the track at the head of the queue is prepared, just in case
+ * we added it. We could be more subtle but prepare() will ensure we don't
+ * prepare the same track twice so there's no point. */
+ if(qhead.next != &qhead)
+ prepare(c->ev, qhead.next);
/* If the queue was empty but we are for some reason paused then
* unpause. */
if(!playing) resume_playing(0);
return 1; /* completed */
}
+static int c_playafter(struct conn *c, char **vec,
+ int attribute((unused)) nvec) {
+ const char *track;
+ struct queue_entry *q;
+ const char *afterme = vec[0];
+
+ for(int n = 1; n < nvec; ++n) {
+ if(!trackdb_exists(vec[n])) {
+ sink_writes(ev_writer_sink(c->w), "550 track is not in database\n");
+ return 1;
+ }
+ if(!(track = trackdb_resolve(vec[n]))) {
+ sink_writes(ev_writer_sink(c->w), "550 cannot resolve track\n");
+ return 1;
+ }
+ q = queue_add(track, c->who, WHERE_AFTER, afterme, origin_picked);
+ if(!q) {
+ sink_printf(ev_writer_sink(c->w), "550 No such ID\n");
+ return 1;
+ }
+ info("added %s as %s after %s", track, q->id, afterme);
+ afterme = q->id;
+ }
+ queue_write();
+ sink_printf(ev_writer_sink(c->w), "252 OK\n");
+ /* We make sure the track at the head of the queue is prepared, just in case
+ * we added it. We could be more subtle but prepare() will ensure we don't
+ * prepare the same track twice so there's no point. */
+ if(qhead.next != &qhead) {
+ prepare(c->ev, qhead.next);
+ info("prepared %s", qhead.next->id);
+ }
+ /* If the queue was empty but we are for some reason paused then
+ * unpause. */
+ if(!playing)
+ resume_playing(0);
+ play(c->ev);
+ return 1; /* completed */
+}
+
static int c_remove(struct conn *c, char **vec,
int attribute((unused)) nvec) {
struct queue_entry *q;
queue_fix_sofar(playing);
if((l = trackdb_get(playing->track, "_length"))
&& (length = atol(l))) {
- time(&when);
+ xtime(&when);
when += length - playing->sofar + config->gap;
}
} else
/* Nothing is playing but playing is enabled, so whatever is
* first in the queue can be expected to start immediately. */
- time(&when);
+ xtime(&when);
}
for(q = qhead.next; q != &qhead; q = q->next) {
/* fill in estimated start time */
sink_writes(ev_writer_sink(c->w), "510 Prohibited\n");
return 1;
}
- if(mixer_control(-1/*as configured*/, &l, &r, set))
+ if(!api || !api->set_volume) {
sink_writes(ev_writer_sink(c->w), "550 error accessing mixer\n");
- else {
- sink_printf(ev_writer_sink(c->w), "252 %d %d\n", l, r);
- if(l != volume_left || r != volume_right) {
- volume_left = l;
- volume_right = r;
- snprintf(lb, sizeof lb, "%d", l);
- snprintf(rb, sizeof rb, "%d", r);
- eventlog("volume", lb, rb, (char *)0);
- }
+ return 1;
+ }
+ (set ? api->set_volume : api->get_volume)(&l, &r);
+ sink_printf(ev_writer_sink(c->w), "252 %d %d\n", l, r);
+ if(l != volume_left || r != volume_right) {
+ volume_left = l;
+ volume_right = r;
+ snprintf(lb, sizeof lb, "%d", l);
+ snprintf(rb, sizeof rb, "%d", r);
+ eventlog("volume", lb, rb, (char *)0);
}
return 1;
}
return;
}
sink_printf(ev_writer_sink(c->w), "%"PRIxMAX" %s\n",
- (uintmax_t)time(0), msg);
+ (uintmax_t)xtime(0), msg);
}
static int c_log(struct conn *c,
sink_writes(ev_writer_sink(c->w), "254 OK\n");
/* pump out initial state */
- time(&now);
+ xtime(&now);
sink_printf(ev_writer_sink(c->w), "%"PRIxMAX" state %s\n",
(uintmax_t)now,
playing_is_enabled() ? "enable_play" : "disable_play");
static int c_rtp_address(struct conn *c,
char attribute((unused)) **vec,
int attribute((unused)) nvec) {
- if(config->api == BACKEND_NETWORK) {
+ if(api == &uaudio_rtp) {
+ char **addr;
+
+ netaddress_format(&config->broadcast, NULL, &addr);
sink_printf(ev_writer_sink(c->w), "252 %s %s\n",
- quoteutf8(config->broadcast.s[0]),
- quoteutf8(config->broadcast.s[1]));
+ quoteutf8(addr[1]),
+ quoteutf8(addr[2]));
} else
sink_writes(ev_writer_sink(c->w), "550 No RTP\n");
return 1;
if(d->lo)
sink_printf(ev_writer_sink(d->w),
"%"PRIxMAX" rights_changed %s\n",
- (uintmax_t)time(0),
+ (uintmax_t)xtime(0),
quoteutf8(new_rights));
}
}
return list_response(c, "User list follows", trackdb_listusers());
}
-/** @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 {
if(!last_reminder)
last_reminder = hash_new(sizeof (time_t));
last = hash_find(last_reminder, vec[0]);
- time(&now);
+ xtime(&now);
if(last && now < *last + config->reminder_interval) {
error(0, "sent a password reminder to '%s' too recently", vec[0]);
sink_writes(ev_writer_sink(c->w), "550 Cannot send a reminder email\n");
return 1;
}
+static int c_adopt(struct conn *c,
+ char **vec,
+ int attribute((unused)) nvec) {
+ struct queue_entry *q;
+
+ if(!c->who) {
+ sink_writes(ev_writer_sink(c->w), "550 no identity\n");
+ return 1;
+ }
+ if(!(q = queue_find(vec[0]))) {
+ sink_writes(ev_writer_sink(c->w), "550 no such track on the queue\n");
+ return 1;
+ }
+ if(q->origin != origin_random) {
+ sink_writes(ev_writer_sink(c->w), "550 not a random track\n");
+ return 1;
+ }
+ q->origin = origin_adopted;
+ q->submitter = xstrdup(c->who);
+ eventlog("adopted", q->id, q->submitter, (char *)0);
+ queue_write();
+ sink_writes(ev_writer_sink(c->w), "250 OK\n");
+ return 1;
+}
+
static int playlist_response(struct conn *c,
int err) {
switch(err) {
rights_type rights;
} commands[] = {
{ "adduser", 2, 3, c_adduser, RIGHT_ADMIN|RIGHT__LOCAL },
+ { "adopt", 1, 1, c_adopt, RIGHT_PLAY },
{ "allfiles", 0, 2, c_allfiles, RIGHT_READ },
{ "confirm", 1, 1, c_confirm, 0 },
{ "cookie", 1, 1, c_cookie, 0 },
{ "part", 3, 3, c_part, RIGHT_READ },
{ "pause", 0, 0, c_pause, RIGHT_PAUSE },
{ "play", 1, 1, c_play, RIGHT_PLAY },
+ { "playafter", 2, INT_MAX, c_playafter, RIGHT_PLAY },
{ "playing", 0, 0, c_playing, RIGHT_READ },
{ "playlist-delete", 1, 1, c_playlist_delete, RIGHT_PLAY },
{ "playlist-get", 1, 1, c_playlist_get, RIGHT_READ },
c->ev = ev;
c->w = ev_writer_new(ev, fd, writer_error, c,
"client writer");
+ if(!c->w) {
+ error(0, "ev_writer_new for file inbound connection (fd=%d) failed",
+ fd);
+ close(fd);
+ return 0;
+ }
c->r = ev_reader_new(ev, fd, redirect_reader_callback, reader_error, c,
"client reader");
+ if(!c->r)
+ /* Main reason for failure is the FD is too big and that will already have
+ * been handled */
+ fatal(0, "ev_reader_new for file inbound connection (fd=%d) failed", fd);
ev_tie(c->r, c->w);
c->fd = fd;
c->reader = reader_callback;
l->pf = pf;
if(ev_listen(ev, fd, listen_callback, l, "server listener"))
exit(EXIT_FAILURE);
+ info("listening on %s", name);
return fd;
}