chiark / gitweb /
server: implement multiple-unicast RTP
[disorder] / server / server.c
index 0ebfb4f0782fb8c4d5d8f91184928a4eb6606c5b..4f06ab48f9b959c90a6fc612fa5df5bb7f7d3aa3 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2004-2009 Richard Kettlewell
+ * Copyright (C) 2004-2012 Richard Kettlewell
  *
  * 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
@@ -42,6 +42,7 @@ int wideopen;
 struct listener {
   const char *name;
   int pf;
+  int privileged;
 };
 
 struct conn;
@@ -117,6 +118,12 @@ struct conn {
   void *body_u;
   /** @brief Accumulating body */
   struct vector body[1];
+
+  /** @brief Nonzero if an active RTP request exists */
+  int rtp_requested;
+
+  /** @brief RTP destination (if @ref rtp_requested is nonzero) */
+  struct sockaddr_storage rtp_destination;
 };
 
 /** @brief Linked list of connections */
@@ -140,10 +147,18 @@ static int command(struct conn *c, char *line);
 
 static const char *noyes[] = { "no", "yes" };
 
-/** @brief Remove a connection from the connection list */
+/** @brief Remove a connection from the connection list
+ *
+ * This is a good place for cleaning things up when connections are closed for
+ * any reason.
+ */
 static void remove_connection(struct conn *c) {
   struct conn **cc;
 
+  if(c->rtp_requested) {
+    rtp_request_cancel(&c->rtp_destination);
+    c->rtp_requested = 0;
+  }
   for(cc = &connections; *cc && *cc != c; cc = &(*cc)->next)
     ;
   if(*cc)
@@ -201,9 +216,9 @@ static int reader_error(ev_source attribute((unused)) *ev,
 
 static int c_disable(struct conn *c, char **vec, int nvec) {
   if(nvec == 0)
-    disable_playing(c->who);
+    disable_playing(c->who, c->ev);
   else if(nvec == 1 && !strcmp(vec[0], "now"))
-    disable_playing(c->who);
+    disable_playing(c->who, c->ev);
   else {
     sink_writes(ev_writer_sink(c->w), "550 invalid argument\n");
     return 1;                  /* completed */
@@ -589,7 +604,7 @@ static int c_user(struct conn *c,
   /* check whether the response is right */
   res = authhash(c->nonce, sizeof c->nonce, password,
                 config->authorization_algorithm);
-  if(wideopen || (res && !strcmp(res, vec[1]))) {
+  if(wideopen || c->l->privileged || (res && !strcmp(res, vec[1]))) {
     c->who = vec[0];
     c->rights = rights;
     /* currently we only bother logging remote connections */
@@ -634,7 +649,7 @@ static int c_queue(struct conn *c,
       if((l = trackdb_get(playing->track, "_length"))
         && (length = atol(l))) {
        xtime(&when);
-       when += length - playing->sofar + config->gap;
+       when += length - playing->sofar;
       }
     } else
       /* Nothing is playing but playing is enabled, so whatever is
@@ -649,7 +664,7 @@ static int c_queue(struct conn *c,
     if(when) {
       if((l = trackdb_get(q->track, "_length"))
         && (length = atol(l)))
-       when += length + config->gap;
+       when += length;
       else
        when = 0;
     }
@@ -866,7 +881,7 @@ static int c_random_enable(struct conn *c,
 static int c_random_disable(struct conn *c,
                            char attribute((unused)) **vec,
                            int attribute((unused)) nvec) {
-  disable_random(c->who);
+  disable_random(c->who, c->ev);
   sink_writes(ev_writer_sink(c->w), "250 OK\n");
   return 1;                    /* completed */
 }
@@ -1150,8 +1165,19 @@ static int c_set_global(struct conn *c,
     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");
+  /* We special-case the 'magic' preferences here. */
+  if(!strcmp(vec[0], "playing")) {
+    (flag_enabled(vec[1]) ? enable_playing : disable_playing)(c->who, c->ev);
+    sink_printf(ev_writer_sink(c->w), "250 OK\n");
+  } else if(!strcmp(vec[0], "random-play")) {
+    (flag_enabled(vec[1]) ? enable_random : disable_random)(c->who, c->ev);
+    sink_printf(ev_writer_sink(c->w), "250 OK\n");
+  } else {
+    if(!trackdb_set_global(vec[0], vec[1], c->who))
+      sink_printf(ev_writer_sink(c->w), "250 OK\n");
+    else
+      sink_writes(ev_writer_sink(c->w), "550 not found\n");
+  }
   return 1;
 }
 
@@ -1177,7 +1203,7 @@ static int c_nop(struct conn *c,
 static int c_new(struct conn *c,
                 char **vec,
                 int nvec) {
-  int max, n;
+  int max;
   char **tracks;
 
   if(nvec > 0)
@@ -1188,7 +1214,6 @@ static int c_new(struct conn *c,
     max = config->new_max;
   tracks = trackdb_new(0, max);
   sink_printf(ev_writer_sink(c->w), "253 New track list follows\n");
-  n = 0;
   while(*tracks) {
     sink_printf(ev_writer_sink(c->w), "%s%s\n",
                **tracks == '.' ? "." : "", *tracks);
@@ -1205,15 +1230,65 @@ static int c_rtp_address(struct conn *c,
   if(api == &uaudio_rtp) {
     char **addr;
 
-    netaddress_format(&config->broadcast, NULL, &addr);
-    sink_printf(ev_writer_sink(c->w), "252 %s %s\n",
-               quoteutf8(addr[1]),
-               quoteutf8(addr[2]));
+    if(!strcmp(config->rtp_mode, "request"))
+      sink_printf(ev_writer_sink(c->w), "252 - -\n");
+    else {
+      netaddress_format(&config->broadcast, NULL, &addr);
+      sink_printf(ev_writer_sink(c->w), "252 %s %s\n",
+                  quoteutf8(addr[1]),
+                  quoteutf8(addr[2]));
+    }
   } else
     sink_writes(ev_writer_sink(c->w), "550 No RTP\n");
   return 1;
 }
 
+static int c_rtp_cancel(struct conn *c,
+                        char attribute((unused)) **vec,
+                        int attribute((unused)) nvec) {
+  if(!c->rtp_requested) {
+    sink_writes(ev_writer_sink(c->w), "550 No active RTP stream\n");
+    return 1;
+  }
+  rtp_request_cancel(&c->rtp_destination);
+  c->rtp_requested = 0;
+  sink_writes(ev_writer_sink(c->w), "250 Cancelled RTP stream\n");
+  return 1;
+}
+
+static int c_rtp_request(struct conn *c,
+                         char **vec,
+                         int attribute((unused)) nvec) {
+  static const struct addrinfo hints = {
+    .ai_family = AF_UNSPEC,
+    .ai_socktype = SOCK_DGRAM,
+    .ai_protocol = IPPROTO_UDP,
+    .ai_flags = AI_NUMERICHOST|AI_NUMERICSERV,
+  };
+  struct addrinfo *res;
+  int rc = getaddrinfo(vec[0], vec[1], &hints, &res);
+  if(rc) {
+    disorder_error(0, "%s port %s: %s",
+                   vec[0], vec[1], gai_strerror(rc));
+    sink_writes(ev_writer_sink(c->w), "550 Invalid address\n");
+    return 1;
+  }
+  disorder_info("%s requested RTP stream to %s %s", c->who, vec[0], vec[1]);
+  /* TODO might be useful to tighten this up to restrict clients to targetting
+   * themselves only */
+  if(c->rtp_requested) {
+    rtp_request_cancel(&c->rtp_destination);
+    c->rtp_requested = 0;
+  }
+  memcpy(&c->rtp_destination, res->ai_addr, res->ai_addrlen);
+  freeaddrinfo(res);
+  rtp_request(&c->rtp_destination);
+  c->rtp_requested = 1;
+  sink_writes(ev_writer_sink(c->w), "250 Initiated RTP stream\n");
+  // TODO teardown on connection close
+  return 1;
+}
+
 static int c_cookie(struct conn *c,
                    char **vec,
                    int attribute((unused)) nvec) {
@@ -1269,7 +1344,7 @@ static int c_revoke(struct conn *c,
     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");
+    sink_writes(ev_writer_sink(c->w), "510 Did not log in with cookie\n");
   return 1;
 }
 
@@ -1280,7 +1355,7 @@ static int c_adduser(struct conn *c,
 
   if(!config->remote_userman && !(c->rights & RIGHT__LOCAL)) {
     disorder_error(0, "S%x: remote adduser", c->tag);
-    sink_writes(ev_writer_sink(c->w), "550 Remote user management is disabled\n");
+    sink_writes(ev_writer_sink(c->w), "510 Remote user management is disabled\n");
     return 1;
   }
   if(nvec > 2) {
@@ -1306,7 +1381,7 @@ static int c_deluser(struct conn *c,
 
   if(!config->remote_userman && !(c->rights & RIGHT__LOCAL)) {
     disorder_error(0, "S%x: remote deluser", c->tag);
-    sink_writes(ev_writer_sink(c->w), "550 Remote user management is disabled\n");
+    sink_writes(ev_writer_sink(c->w), "510 Remote user management is disabled\n");
     return 1;
   }
   if(trackdb_deluser(vec[0])) {
@@ -1328,7 +1403,7 @@ static int c_edituser(struct conn *c,
 
   if(!config->remote_userman && !(c->rights & RIGHT__LOCAL)) {
     disorder_error(0, "S%x: remote edituser", c->tag);
-    sink_writes(ev_writer_sink(c->w), "550 Remote user management is disabled\n");
+    sink_writes(ev_writer_sink(c->w), "510 Remote user management is disabled\n");
     return 1;
   }
   /* RIGHT_ADMIN can do anything; otherwise you can only set your own email
@@ -1387,7 +1462,7 @@ static int c_userinfo(struct conn *c,
      && !(c->rights & RIGHT__LOCAL)
      && strcmp(vec[1], "rights")) {
     disorder_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");
+    sink_writes(ev_writer_sink(c->w), "510 Remote user management is disabled\n");
     return 1;
   }
   /* RIGHT_ADMIN allows anything; otherwise you can only get your own email
@@ -1460,7 +1535,7 @@ static int c_confirm(struct conn *c,
   }
   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");
+    sink_writes(ev_writer_sink(c->w), "510 Incorrect confirmation string\n");
   else {
     c->who = user;
     c->cookie = 0;
@@ -1610,7 +1685,7 @@ static int c_schedule_del(struct conn *c,
     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");
+      sink_writes(ev_writer_sink(c->w), "510 Not authorized\n");
       return 1;                                /* completed */
     }
   }
@@ -1695,7 +1770,7 @@ static int playlist_response(struct conn *c,
   case 0:
     assert(!"cannot cope with success");
   case EACCES:
-    sink_writes(ev_writer_sink(c->w), "550 Access denied\n");
+    sink_writes(ev_writer_sink(c->w), "510 Access denied\n");
     break;
   case EINVAL:
     sink_writes(ev_writer_sink(c->w), "550 Invalid playlist name\n");
@@ -1835,7 +1910,8 @@ static int c_playlist_unlock(struct conn *c,
   return 1;
 }
 
-static const struct command {
+/** @brief Server's definition of a command */
+static const struct server_command {
   /** @brief Command name */
   const char *name;
 
@@ -1905,6 +1981,8 @@ static const struct command {
   { "resume",         0, 0,       c_resume,         RIGHT_PAUSE },
   { "revoke",         0, 0,       c_revoke,         RIGHT_READ },
   { "rtp-address",    0, 0,       c_rtp_address,    0 },
+  { "rtp-cancel",     0, 0,       c_rtp_cancel,     0 },
+  { "rtp-request",    2, 2,       c_rtp_request,    RIGHT_READ },
   { "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 },
@@ -2125,12 +2203,16 @@ static int listen_callback(ev_source *ev,
 
 int server_start(ev_source *ev, int pf,
                 size_t socklen, const struct sockaddr *sa,
-                const char *name) {
+                const char *name,
+                int privileged) {
   int fd;
   struct listener *l = xmalloc(sizeof *l);
   static const int one = 1;
 
-  D(("server_init socket %s", name));
+  D(("server_init socket %s privileged=%d", name, privileged));
+  /* Sanity check */
+  if(privileged && pf != AF_UNIX)
+    disorder_fatal(0, "cannot create a privileged listener on a non-local port");
   fd = xsocket(pf, SOCK_STREAM, 0);
   xsetsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
   if(bind(fd, sa, socklen) < 0) {
@@ -2142,6 +2224,7 @@ int server_start(ev_source *ev, int pf,
   cloexec(fd);
   l->name = name;
   l->pf = pf;
+  l->privileged = privileged;
   if(ev_listen(ev, fd, listen_callback, l, "server listener"))
     exit(EXIT_FAILURE);
   disorder_info("listening on %s", name);