server side support for cookies, basic tests
[disorder] / server / server.c
index ccd0823..cd97435 100644 (file)
@@ -63,6 +63,8 @@
 #include "eventlog.h"
 #include "defs.h"
 #include "cache.h"
+#include "unicode.h"
+#include "cookies.h"
 
 #ifndef NONCE_SIZE
 # define NONCE_SIZE 16
@@ -106,6 +108,8 @@ struct conn {
   struct eventlog_output *lo;
   /** @brief Parent listener */
   const struct listener *l;
+  /** @brief Login cookie or NULL */
+  char *cookie;
 };
 
 static int reader_callback(ev_source *ev,
@@ -126,20 +130,19 @@ static int writer_error(ev_source attribute((unused)) *ev,
                        void *u) {
   struct conn *c = u;
 
-  D(("server writer_error %d", errno_value));
-  info("writer_error S%x %d", c->tag, errno_value);
+  D(("server writer_error S%x %d", c->tag, errno_value));
   if(errno_value == 0) {
     /* writer is done */
-    error(errno_value, "S%x writer completed", c->tag);        /* TODO */
+    D(("S%x writer completed", c->tag));
   } else {
     if(errno_value != EPIPE)
       error(errno_value, "S%x write error on socket", c->tag);
     if(c->r) {
-      info("cancel reader");
+      D(("cancel reader"));
       ev_reader_cancel(c->r);
       c->r = 0;
     }
-    info("done cancel reader");
+    D(("done cancel reader"));
   }
   c->w = 0;
   ev_report(ev);
@@ -155,8 +158,7 @@ static int reader_error(ev_source attribute((unused)) *ev,
                        void *u) {
   struct conn *c = u;
 
-  D(("server reader_error %d", errno_value));
-  info("reader_error S%x %d", c->tag, errno_value);
+  D(("server reader_error S%x %d", c->tag, errno_value));
   error(errno_value, "S%x read error on socket", c->tag);
   if(c->w)
     ev_writer_close(c->w);
@@ -226,7 +228,7 @@ static int c_play(struct conn *c, char **vec,
    * anything. */
   if(q == qhead.next && playing)
     prepare(c->ev, q);
-  sink_writes(ev_writer_sink(c->w), "250 queued\n");
+  sink_printf(ev_writer_sink(c->w), "252 %s\n", q->id);
   /* If the queue was empty but we are for some reason paused then
    * unpause. */
   if(!playing) resume_playing(0);
@@ -347,7 +349,7 @@ static int c_version(struct conn *c,
                     char attribute((unused)) **vec,
                     int attribute((unused)) nvec) {
   /* VERSION had better only use the basic character set */
-  sink_printf(ev_writer_sink(c->w), "251 %s\n", disorder_version_string);
+  sink_printf(ev_writer_sink(c->w), "251 %s\n", disorder_short_version_string);
   return 1;                    /* completed */
 }
 
@@ -371,39 +373,48 @@ static int c_become(struct conn *c,
   return 1;
 }
 
-static int c_user(struct conn *c,
-                 char **vec,
-                 int attribute((unused)) nvec) {
-  int n;
-  const char *res;
+static const char *connection_host(struct conn *c) {
   union {
     struct sockaddr sa;
     struct sockaddr_in in;
     struct sockaddr_in6 in6;
   } u;
   socklen_t l;
+  int n;
   char host[1024];
 
-  if(c->who) {
-    sink_writes(ev_writer_sink(c->w), "530 already authenticated\n");
-    return 1;
-  }
   /* get connection data */
   l = sizeof u;
   if(getpeername(c->fd, &u.sa, &l) < 0) {
     error(errno, "S%x error calling getpeername", c->tag);
-    sink_writes(ev_writer_sink(c->w), "530 authentication failure\n");
-    return 1;
+    return 0;
   }
   if(c->l->pf != PF_UNIX) {
     if((n = getnameinfo(&u.sa, l,
                        host, sizeof host, 0, 0, NI_NUMERICHOST))) {
       error(0, "S%x error calling getnameinfo: %s", c->tag, gai_strerror(n));
-      sink_writes(ev_writer_sink(c->w), "530 authentication failure\n");
-      return 1;
+      return 0;
     }
+    return xstrdup(host);
   } else
-    strcpy(host, "local");
+    return "local";
+}
+
+static int c_user(struct conn *c,
+                 char **vec,
+                 int attribute((unused)) nvec) {
+  int n;
+  const char *res, *host;
+
+  if(c->who) {
+    sink_writes(ev_writer_sink(c->w), "530 already authenticated\n");
+    return 1;
+  }
+  /* get connection data */
+  if(!(host = connection_host(c))) {
+    sink_writes(ev_writer_sink(c->w), "530 authentication failure\n");
+    return 1;
+  }
   /* find the user */
   for(n = 0; n < config->allow.n
        && strcmp(config->allow.s[n].s[0], vec[0]); ++n)
@@ -419,7 +430,7 @@ static int c_user(struct conn *c,
   if(wideopen || (res && !strcmp(res, vec[1]))) {
     c->who = vec[0];
     /* currently we only bother logging remote connections */
-    if(c->l->pf != PF_UNIX)
+    if(strcmp(host, "local"))
       info("S%x %s connected from %s", c->tag, vec[0], host);
     sink_writes(ev_writer_sink(c->w), "230 OK\n");
     return 1;
@@ -581,7 +592,7 @@ static int c_get(struct conn *c,
   if(vec[1][0] != '_' && (v = trackdb_get(vec[0], vec[1])))
     sink_printf(ev_writer_sink(c->w), "252 %s\n", v);
   else
-    sink_writes(ev_writer_sink(c->w), "550 not found\n");
+    sink_writes(ev_writer_sink(c->w), "555 not found\n");
   return 1;
 }
 
@@ -753,9 +764,9 @@ static int logging_reader_callback(ev_source attribute((unused)) *ev,
   ev_reader_consume(reader, bytes);
   if(eof) {
     /* Oops, that's all for now */
-    info("logging reader eof");
+    D(("logging reader eof"));
     if(c->w) {
-      info("close writer");
+      D(("close writer"));
       ev_writer_close(c->w);
       c->w = 0;
     }
@@ -807,19 +818,6 @@ static int c_log(struct conn *c,
   return 0;
 }
 
-static void post_move_cleanup(void) {
-  struct queue_entry *q;
-
-  /* If we have caused any random tracks to not be at the end then we make them
-   * no longer be random. */
-  for(q = qhead.next; q != &qhead; q = q->next)
-    if(q->state == playing_random && q->next != &qhead)
-      q->state = playing_unplayed;
-  /* That might mean we need to add a new random track. */
-  add_random_track();
-  queue_write();
-}
-
 static int c_move(struct conn *c,
                  char **vec,
                  int attribute((unused)) nvec) {
@@ -838,7 +836,6 @@ static int c_move(struct conn *c,
     return 1;
   }
   n = queue_move(q, atoi(vec[1]), c->who);
-  post_move_cleanup();
   sink_printf(ev_writer_sink(c->w), "252 %d\n", n);
   /* If we've moved to the head of the queue then prepare the track. */
   if(q == qhead.next)
@@ -875,7 +872,6 @@ static int c_moveafter(struct conn *c,
       return 1;
     }
   queue_moveafter(q, nvec, qs, c->who);
-  post_move_cleanup();
   sink_printf(ev_writer_sink(c->w), "250 Moved tracks\n");
   /* If we've moved to the head of the queue then prepare the track. */
   if(q == qhead.next)
@@ -923,6 +919,10 @@ static int c_tags(struct conn *c,
 static int c_set_global(struct conn *c,
                        char **vec,
                        int attribute((unused)) nvec) {
+  if(vec[0][0] == '_') {
+    sink_writes(ev_writer_sink(c->w), "550 cannot set internal global preferences\n");
+    return 1;
+  }
   trackdb_set_global(vec[0], vec[1], c->who);
   sink_printf(ev_writer_sink(c->w), "250 OK\n");
   return 1;
@@ -936,7 +936,7 @@ static int c_get_global(struct conn *c,
   if(s)
     sink_printf(ev_writer_sink(c->w), "252 %s\n", s);
   else
-    sink_writes(ev_writer_sink(c->w), "550 not found\n");
+    sink_writes(ev_writer_sink(c->w), "555 not found\n");
   return 1;
 }
 
@@ -974,7 +974,61 @@ static int c_rtp_address(struct conn *c,
     sink_writes(ev_writer_sink(c->w), "550 No RTP\n");
   return 1;
 }
+
+static int c_cookie(struct conn *c,
+                   char **vec,
+                   int attribute((unused)) nvec) {
+  const char *host;
+  char *user;
+
+  /* Can't log in twice on the same connection */
+  if(c->who) {
+    sink_writes(ev_writer_sink(c->w), "530 already authenticated\n");
+    return 1;
+  }
+  /* Get some kind of peer identifcation */
+  if(!(host = connection_host(c))) {
+    sink_writes(ev_writer_sink(c->w), "530 authentication failure\n");
+    return 1;
+  }
+  /* Check the cookie */
+  user = verify_cookie(vec[0]);
+  if(!user) {
+    sink_writes(ev_writer_sink(c->w), "530 authentication failure\n");
+    return 1;
+  }
+  /* Log in */
+  c->who = user;
+  c->cookie = vec[0];
+  if(strcmp(host, "local"))
+    info("S%x %s connected with cookie from %s", c->tag, user, host);
+  sink_writes(ev_writer_sink(c->w), "230 OK\n");
+  return 1;
+}
+
+static int c_make_cookie(struct conn *c,
+                        char attribute((unused)) **vec,
+                        int attribute((unused)) nvec) {
+  const char *cookie = make_cookie(c->who);
+
+  if(cookie)
+    sink_printf(ev_writer_sink(c->w), "252 %s\n", cookie);
+  else
+    sink_writes(ev_writer_sink(c->w), "550 Cannot create cookie\n");
+  return 1;
+}
+
+static int c_revoke(struct conn *c,
+                   char attribute((unused)) **vec,
+                   int attribute((unused)) nvec) {
+  if(c->cookie) {
+    revoke_cookie(c->cookie);
+    sink_writes(ev_writer_sink(c->w), "250 OK\n");
+  } else
+    sink_writes(ev_writer_sink(c->w), "550 Did not log in with cookie\n");
+  return 1;
+}
+
 #define C_AUTH         0001            /* must be authenticated */
 #define C_TRUSTED      0002            /* must be trusted user */
 
@@ -986,6 +1040,7 @@ static const struct command {
 } commands[] = {
   { "allfiles",       0, 2,       c_allfiles,       C_AUTH },
   { "become",         1, 1,       c_become,         C_AUTH|C_TRUSTED },
+  { "cookie",         1, 1,       c_cookie,         0 },
   { "dirs",           0, 2,       c_dirs,           C_AUTH },
   { "disable",        0, 1,       c_disable,        C_AUTH },
   { "enable",         0, 0,       c_enable,         C_AUTH },
@@ -996,6 +1051,7 @@ static const struct command {
   { "get-global",     1, 1,       c_get_global,     C_AUTH },
   { "length",         1, 1,       c_length,         C_AUTH },
   { "log",            0, 0,       c_log,            C_AUTH },
+  { "make-cookie",    0, 0,       c_make_cookie,    C_AUTH },
   { "move",           2, 2,       c_move,           C_AUTH },
   { "moveafter",      1, INT_MAX, c_moveafter,      C_AUTH },
   { "new",            0, 1,       c_new,            C_AUTH },
@@ -1015,6 +1071,7 @@ static const struct command {
   { "rescan",         0, 0,       c_rescan,         C_AUTH|C_TRUSTED },
   { "resolve",        1, 1,       c_resolve,        C_AUTH },
   { "resume",         0, 0,       c_resume,         C_AUTH },
+  { "revoke",         0, 0,       c_revoke,         C_AUTH },
   { "rtp-address",    0, 0,       c_rtp_address,    C_AUTH },
   { "scratch",        0, 1,       c_scratch,        C_AUTH },
   { "search",         1, 1,       c_search,         C_AUTH },
@@ -1042,6 +1099,11 @@ static int command(struct conn *c, char *line) {
   int nvec, n;
 
   D(("server command %s", line));
+  /* We force everything into NFC as early as possible */
+  if(!(line = utf8_compose_canon(line, strlen(line), 0))) {
+    sink_writes(ev_writer_sink(c->w), "500 cannot normalize command\n");
+    return 1;
+  }
   if(!(vec = split(line, &nvec, SPLIT_QUOTES, command_error, c))) {
     sink_writes(ev_writer_sink(c->w), "500 cannot parse command\n");
     return 1;
@@ -1120,10 +1182,10 @@ static int reader_callback(ev_source attribute((unused)) *ev,
   if(eof) {
     if(bytes)
       error(0, "S%x unterminated line", c->tag);
-    info("normal reader close");
+    D(("normal reader close"));
     c->r = 0;
     if(c->w) {
-      info("close associated writer");
+      D(("close associated writer"));
       ev_writer_close(c->w);
       c->w = 0;
     }