* USA
*/
-#include <config.h>
-#include "types.h"
-
-#include <pwd.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <gcrypt.h>
-#include <stddef.h>
-#include <time.h>
-#include <limits.h>
-#include <pcre.h>
-#include <netdb.h>
-#include <netinet/in.h>
-
-#include "event.h"
-#include "server.h"
-#include "syscalls.h"
-#include "queue.h"
-#include "server-queue.h"
-#include "play.h"
-#include "log.h"
-#include "mem.h"
-#include "state.h"
-#include "charset.h"
-#include "split.h"
-#include "configuration.h"
-#include "hex.h"
-#include "rights.h"
-#include "trackdb.h"
-#include "table.h"
-#include "kvp.h"
-#include "mixer.h"
-#include "sink.h"
-#include "authhash.h"
-#include "plugin.h"
-#include "printf.h"
-#include "trackname.h"
-#include "eventlog.h"
-#include "defs.h"
-#include "cache.h"
-#include "unicode.h"
-#include "cookies.h"
-#include "base64.h"
-#include "hash.h"
-#include "mime.h"
-#include "sendmail.h"
-#include "wstat.h"
+#include "disorder-server.h"
#ifndef NONCE_SIZE
# define NONCE_SIZE 16
rights_type rights;
/** @brief Next connection */
struct conn *next;
+ /** @brief True if pending rescan had 'wait' set */
+ int rescan_wait;
};
/** @brief Linked list of connections */
return 1; /* completed */
}
+static void finished_rescan(void *ru) {
+ struct conn *const c = ru;
+
+ sink_writes(ev_writer_sink(c->w), "250 rescan completed\n");
+ /* Turn this connection back on */
+ ev_reader_enable(c->r);
+}
+
+static void start_fresh_rescan(void *ru) {
+ struct conn *const c = ru;
+
+ if(trackdb_rescan_underway()) {
+ /* Some other waiter beat us to it. However in this case we're happy to
+ * piggyback; the requirement is that a new rescan be started, not that it
+ * was _our_ rescan. */
+ if(c->rescan_wait) {
+ /* We block until the rescan completes */
+ trackdb_add_rescanned(finished_rescan, c);
+ } else {
+ /* We report that the new rescan has started */
+ sink_writes(ev_writer_sink(c->w), "250 rescan initiated\n");
+ /* Turn this connection back on */
+ ev_reader_enable(c->r);
+ }
+ } else {
+ /* We are the first connection to get a callback so we must start a
+ * rescan. */
+ if(c->rescan_wait) {
+ /* We want to block until the new rescan completes */
+ trackdb_rescan(c->ev, 1/*check*/, finished_rescan, c);
+ } else {
+ /* We can report back immediately */
+ trackdb_rescan(c->ev, 1/*check*/, 0, 0);
+ sink_writes(ev_writer_sink(c->w), "250 rescan initiated\n");
+ /* Turn this connection back on */
+ ev_reader_enable(c->r);
+ }
+ }
+}
+
static int c_rescan(struct conn *c,
- char attribute((unused)) **vec,
- int attribute((unused)) nvec) {
- info("S%x rescan by %s", c->tag, c->who);
- trackdb_rescan(c->ev, 1/*check*/);
- sink_writes(ev_writer_sink(c->w), "250 initiated rescan\n");
- return 1; /* completed */
+ char **vec,
+ int nvec) {
+ int flag_wait = 0, flag_fresh = 0, n;
+
+ /* Parse flags */
+ for(n = 0; n < nvec; ++n) {
+ if(!strcmp(vec[n], "wait"))
+ flag_wait = 1; /* wait for rescan to complete */
+#if 0
+ /* Currently disabled because untested (and hard to test). */
+ else if(!strcmp(vec[n], "fresh"))
+ flag_fresh = 1; /* don't piggyback underway rescan */
+#endif
+ else {
+ sink_writes(ev_writer_sink(c->w), "550 unknown flag\n");
+ return 1; /* completed */
+ }
+ }
+ /* Report what was requested */
+ info("S%x rescan by %s (%s %s)", c->tag, c->who,
+ flag_wait ? "wait" : "",
+ flag_fresh ? "fresh" : "");
+ if(trackdb_rescan_underway()) {
+ if(flag_fresh) {
+ /* We want a fresh rescan but there is already one underway. Arrange a
+ * callback when it completes and then set off a new one. */
+ c->rescan_wait = flag_wait;
+ trackdb_add_rescanned(start_fresh_rescan, c);
+ if(flag_wait)
+ return 0;
+ else {
+ sink_writes(ev_writer_sink(c->w), "250 rescan queued\n");
+ return 1;
+ }
+ } else {
+ /* There's a rescan underway, and it's acceptable to piggyback on it */
+ if(flag_wait) {
+ /* We want to block until completion. */
+ trackdb_add_rescanned(finished_rescan, c);
+ return 0;
+ } else {
+ /* We don't want to block. So we just report that things are in
+ * hand. */
+ sink_writes(ev_writer_sink(c->w), "250 rescan already underway\n");
+ return 1;
+ }
+ }
+ } else {
+ /* No rescan is underway. fresh is therefore irrelevant. */
+ if(flag_wait) {
+ /* We want to block until completion */
+ trackdb_rescan(c->ev, 1/*check*/, finished_rescan, c);
+ return 0;
+ } else {
+ /* We don't want to block. */
+ trackdb_rescan(c->ev, 1/*check*/, 0, 0);
+ sink_writes(ev_writer_sink(c->w), "250 rescan initiated\n");
+ return 1; /* completed */
+ }
+ }
}
static int c_version(struct conn *c,
static int c_get(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
- const char *v;
+ const char *v, *track;
- if(vec[1][0] != '_' && (v = trackdb_get(vec[0], vec[1])))
+ if(!(track = trackdb_resolve(vec[0]))) {
+ sink_writes(ev_writer_sink(c->w), "550 cannot resolve track\n");
+ return 1;
+ }
+ if(vec[1][0] != '_' && (v = trackdb_get(track, vec[1])))
sink_printf(ev_writer_sink(c->w), "252 %s\n", quoteutf8(v));
else
sink_writes(ev_writer_sink(c->w), "555 not found\n");
static int c_set(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
- if(vec[1][0] != '_' && !trackdb_set(vec[0], vec[1], vec[2]))
+ const char *track;
+
+ if(!(track = trackdb_resolve(vec[0]))) {
+ sink_writes(ev_writer_sink(c->w), "550 cannot resolve track\n");
+ return 1;
+ }
+ if(vec[1][0] != '_' && !trackdb_set(track, vec[1], vec[2]))
sink_writes(ev_writer_sink(c->w), "250 OK\n");
else
sink_writes(ev_writer_sink(c->w), "550 not found\n");
char **vec,
int attribute((unused)) nvec) {
struct kvp *k;
+ const char *track;
- k = trackdb_get_all(vec[0]);
+ if(!(track = trackdb_resolve(vec[0]))) {
+ sink_writes(ev_writer_sink(c->w), "550 cannot resolve track\n");
+ return 1;
+ }
+ k = trackdb_get_all(track);
sink_writes(ev_writer_sink(c->w), "253 prefs follow\n");
for(; k; k = k->next)
if(k->name[0] != '_') /* omit internal values */
static int c_exists(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
+ /* trackdb_exists() does its own alias checking */
sink_printf(ev_writer_sink(c->w), "252 %s\n", noyes[trackdb_exists(vec[0])]);
return 1;
}
if(!c->w || !c->r) {
/* This connection has gone up in smoke for some reason */
eventlog_remove(c->lo);
+ c->lo = 0;
return;
}
+ /* user_* messages are restricted */
+ if(!strncmp(msg, "user_", 5)) {
+ /* They are only sent to admin users */
+ if(!(c->rights & RIGHT_ADMIN))
+ return;
+ /* They are not sent over TCP connections unless remote user-management is
+ * enabled */
+ if(!config->remote_userman && !(c->rights & RIGHT__LOCAL))
+ return;
+ }
sink_printf(ev_writer_sink(c->w), "%"PRIxMAX" %s\n",
(uintmax_t)time(0), msg);
}
static int c_part(struct conn *c,
char **vec,
int attribute((unused)) nvec) {
+ const char *track;
+
+ if(!(track = trackdb_resolve(vec[0]))) {
+ sink_writes(ev_writer_sink(c->w), "550 cannot resolve track\n");
+ return 1;
+ }
sink_printf(ev_writer_sink(c->w), "252 %s\n",
- quoteutf8(trackdb_getpart(vec[0], vec[1], vec[2])));
+ quoteutf8(trackdb_getpart(track, vec[1], vec[2])));
return 1;
}
const char *rights;
if(!config->remote_userman && !(c->rights & RIGHT__LOCAL)) {
+ error(0, "S%x: remote adduser", c->tag);
sink_writes(ev_writer_sink(c->w), "550 Remote user management is disabled\n");
return 1;
}
struct conn *d;
if(!config->remote_userman && !(c->rights & RIGHT__LOCAL)) {
+ error(0, "S%x: remote deluser", c->tag);
sink_writes(ev_writer_sink(c->w), "550 Remote user management is disabled\n");
return 1;
}
struct conn *d;
if(!config->remote_userman && !(c->rights & RIGHT__LOCAL)) {
+ error(0, "S%x: remote edituser", c->tag);
sink_writes(ev_writer_sink(c->w), "550 Remote user management is disabled\n");
return 1;
}
/* Update rights for this user */
rights_type r;
- if(parse_rights(vec[2], &r, 1))
- for(d = connections; d; d = d->next)
- if(!strcmp(d->who, vec[0]))
+ if(!parse_rights(vec[2], &r, 1)) {
+ const char *new_rights = rights_string(r);
+ for(d = connections; d; d = d->next) {
+ if(!strcmp(d->who, vec[0])) {
+ /* Update rights */
d->rights = r;
+ /* Notify any log connections */
+ if(d->lo)
+ sink_printf(ev_writer_sink(d->w),
+ "%"PRIxMAX" rights_changed %s\n",
+ (uintmax_t)time(0),
+ quoteutf8(new_rights));
+ }
+ }
+ }
}
sink_writes(ev_writer_sink(c->w), "250 OK\n");
} else {
struct kvp *k;
const char *value;
- if(!config->remote_userman && !(c->rights & RIGHT__LOCAL)) {
+ /* We allow remote querying of rights so that clients can figure out what
+ * they're allowed to do */
+ if(!config->remote_userman
+ && !(c->rights & RIGHT__LOCAL)
+ && strcmp(vec[1], "rights")) {
+ error(0, "S%x: remote userinfo %s %s", c->tag, vec[0], vec[1]);
sink_writes(ev_writer_sink(c->w), "550 Remote user management is disabled\n");
return 1;
}
sink_writes(ev_writer_sink(c->w), "550 Cannot send a reminder email\n");
return 1;
}
+ /* TODO use email_valid() */
if(!(email = kvp_get(k, "email"))
|| !strchr(email, '@')) {
error(0, "user '%s' has no valid email address", vec[0]);
return 0;
}
+static int c_schedule_list(struct conn *c,
+ char attribute((unused)) **vec,
+ int attribute((unused)) nvec) {
+ char **ids = schedule_list(0);
+ sink_writes(ev_writer_sink(c->w), "253 ID list follows\n");
+ while(*ids)
+ sink_printf(ev_writer_sink(c->w), "%s\n", *ids++);
+ sink_writes(ev_writer_sink(c->w), ".\n");
+ return 1; /* completed */
+}
+
+static int c_schedule_get(struct conn *c,
+ char **vec,
+ int attribute((unused)) nvec) {
+ struct kvp *actiondata = schedule_get(vec[0]), *k;
+
+ if(!actiondata) {
+ sink_writes(ev_writer_sink(c->w), "555 No such event\n");
+ return 1; /* completed */
+ }
+ /* Scheduled events are public information. Anyone with RIGHT_READ can see
+ * them. */
+ sink_writes(ev_writer_sink(c->w), "253 Event information follows\n");
+ for(k = actiondata; k; k = k->next)
+ sink_printf(ev_writer_sink(c->w), " %s %s\n",
+ quoteutf8(k->name), quoteutf8(k->value));
+ sink_writes(ev_writer_sink(c->w), ".\n");
+ return 1; /* completed */
+}
+
+static int c_schedule_del(struct conn *c,
+ char **vec,
+ int attribute((unused)) nvec) {
+ struct kvp *actiondata = schedule_get(vec[0]);
+
+ if(!actiondata) {
+ sink_writes(ev_writer_sink(c->w), "555 No such event\n");
+ return 1; /* completed */
+ }
+ /* If you have admin rights you can delete anything. If you don't then you
+ * can only delete your own scheduled events. */
+ if(!(c->rights & RIGHT_ADMIN)) {
+ const char *who = kvp_get(actiondata, "who");
+
+ if(!who || !c->who || strcmp(who, c->who)) {
+ sink_writes(ev_writer_sink(c->w), "551 Not authorized\n");
+ return 1; /* completed */
+ }
+ }
+ if(schedule_del(vec[0]))
+ sink_writes(ev_writer_sink(c->w), "550 Could not delete scheduled event\n");
+ else
+ sink_writes(ev_writer_sink(c->w), "250 Deleted\n");
+ return 1; /* completed */
+}
+
+static int c_schedule_add(struct conn *c,
+ char **vec,
+ int nvec) {
+ struct kvp *actiondata = 0;
+ const char *id;
+
+ /* Standard fields */
+ kvp_set(&actiondata, "who", c->who);
+ kvp_set(&actiondata, "when", vec[0]);
+ kvp_set(&actiondata, "priority", vec[1]);
+ kvp_set(&actiondata, "action", vec[2]);
+ /* Action-dependent fields */
+ if(!strcmp(vec[2], "play")) {
+ if(nvec != 4) {
+ sink_writes(ev_writer_sink(c->w), "550 Wrong number of arguments\n");
+ return 1;
+ }
+ if(!trackdb_exists(vec[3])) {
+ sink_writes(ev_writer_sink(c->w), "550 Track is not in database\n");
+ return 1;
+ }
+ kvp_set(&actiondata, "track", vec[3]);
+ } else if(!strcmp(vec[2], "set-global")) {
+ if(nvec < 4 || nvec > 5) {
+ sink_writes(ev_writer_sink(c->w), "550 Wrong number of arguments\n");
+ return 1;
+ }
+ kvp_set(&actiondata, "key", vec[3]);
+ if(nvec > 4)
+ kvp_set(&actiondata, "value", vec[4]);
+ } else {
+ sink_writes(ev_writer_sink(c->w), "550 Unknown action\n");
+ return 1;
+ }
+ /* schedule_add() checks user rights */
+ id = schedule_add(c->ev, actiondata);
+ if(!id)
+ sink_writes(ev_writer_sink(c->w), "550 Cannot add scheduled event\n");
+ else
+ sink_printf(ev_writer_sink(c->w), "252 %s\n", id);
+ return 1;
+}
+
static const struct command {
/** @brief Command name */
const char *name;
{ "register", 3, 3, c_register, RIGHT_REGISTER|RIGHT__LOCAL },
{ "reminder", 1, 1, c_reminder, RIGHT__LOCAL },
{ "remove", 1, 1, c_remove, RIGHT_REMOVE__MASK },
- { "rescan", 0, 0, c_rescan, RIGHT_RESCAN },
+ { "rescan", 0, INT_MAX, c_rescan, RIGHT_RESCAN },
{ "resolve", 1, 1, c_resolve, RIGHT_READ },
{ "resume", 0, 0, c_resume, RIGHT_PAUSE },
{ "revoke", 0, 0, c_revoke, RIGHT_READ },
{ "rtp-address", 0, 0, c_rtp_address, 0 },
+ { "schedule-add", 3, INT_MAX, c_schedule_add, RIGHT_READ },
+ { "schedule-del", 1, 1, c_schedule_del, RIGHT_READ },
+ { "schedule-get", 1, 1, c_schedule_get, RIGHT_READ },
+ { "schedule-list", 0, 0, c_schedule_list, RIGHT_READ },
{ "scratch", 0, 1, c_scratch, RIGHT_SCRATCH__MASK },
{ "search", 1, 1, c_search, RIGHT_READ },
{ "set", 3, 3, c_set, RIGHT_PREFS, },
sink_writes(ev_writer_sink(c->w), "500 do what?\n");
return 1;
}
- if((n = TABLE_FIND(commands, struct command, name, vec[0])) < 0)
+ if((n = TABLE_FIND(commands, name, vec[0])) < 0)
sink_writes(ev_writer_sink(c->w), "500 unknown command\n");
else {
if(commands[n].rights
D(("server listen_callback fd %d (%s)", fd, l->name));
nonblock(fd);
cloexec(fd);
+ c->next = connections;
c->tag = tags++;
c->ev = ev;
c->w = ev_writer_new(ev, fd, writer_error, c,
c->reader = reader_callback;
c->l = l;
c->rights = 0;
+ connections = c;
gcry_randomize(c->nonce, sizeof c->nonce, GCRY_STRONG_RANDOM);
sink_printf(ev_writer_sink(c->w), "231 %d %s %s\n",
2,