chiark / gitweb /
server side support for cookies, basic tests
authorRichard Kettlewell <rjk@greenend.org.uk>
Tue, 18 Dec 2007 17:04:00 +0000 (17:04 +0000)
committerRichard Kettlewell <rjk@greenend.org.uk>
Tue, 18 Dec 2007 17:04:00 +0000 (17:04 +0000)
14 files changed:
lib/Makefile.am
lib/configuration.c
lib/configuration.h
lib/cookies.c [new file with mode: 0644]
lib/cookies.h [new file with mode: 0644]
lib/kvp.c
lib/kvp.h
python/disorder.py.in
server/Makefile.am
server/disorderd.c
server/server.c
tests/Makefile.am
tests/cookie.py [new file with mode: 0755]
tests/dtest.py

index cf538787ad21ca437069236eab36282d1ef40077..13bafc69c21cc2595cede4bbda1c499ebd0ff907 100644 (file)
@@ -30,6 +30,7 @@ libdisorder_a_SOURCES=charset.c charset.h             \
        client.c client.h                               \
        client-common.c client-common.h                 \
        configuration.c configuration.h                 \
        client.c client.h                               \
        client-common.c client-common.h                 \
        configuration.c configuration.h                 \
+       cookies.c cookies.h                             \
        defs.c defs.h                                   \
        eclient.c eclient.h                             \
        event.c event.h                                 \
        defs.c defs.h                                   \
        eclient.c eclient.h                             \
        event.c event.h                                 \
index 2dcf8f97e90f9712b80ac660cd9950b6791fe30d..3ddfea16e0cd6b4b5d99988fd445314ad9d8fa42 100644 (file)
@@ -899,6 +899,8 @@ static const struct conf conf[] = {
   { C(checkpoint_min),   &type_integer,          validate_non_negative },
   { C(collection),       &type_collections,      validate_any },
   { C(connect),          &type_stringlist,       validate_addrport },
   { C(checkpoint_min),   &type_integer,          validate_non_negative },
   { C(collection),       &type_collections,      validate_any },
   { C(connect),          &type_stringlist,       validate_addrport },
+  { C(cookie_login_lifetime),  &type_integer,    validate_positive },
+  { C(cookie_key_lifetime),  &type_integer,      validate_positive },
   { C(dbversion),        &type_integer,          validate_positive },
   { C(device),           &type_string,           validate_any },
   { C(gap),              &type_integer,          validate_non_negative },
   { C(dbversion),        &type_integer,          validate_positive },
   { C(device),           &type_string,           validate_any },
   { C(gap),              &type_integer,          validate_non_negative },
@@ -1058,6 +1060,8 @@ static struct config *config_default(void) {
   c->mixer = xstrdup("/dev/mixer");
   c->channel = xstrdup("pcm");
   c->dbversion = 2;
   c->mixer = xstrdup("/dev/mixer");
   c->channel = xstrdup("pcm");
   c->dbversion = 2;
+  c->cookie_login_lifetime = 86400;
+  c->cookie_key_lifetime = 86400 * 7;
   return c;
 }
 
   return c;
 }
 
index 02c953a83fca8e8a7bc99a701b41074440fa76f9..eb028b05105c183694bb2ce0c5895a032111b106 100644 (file)
@@ -245,6 +245,12 @@ struct config {
 
   /** @brief Whether to loop back multicast packets */
   int multicast_loop;
 
   /** @brief Whether to loop back multicast packets */
   int multicast_loop;
+
+  /** @brief Login lifetime in seconds */
+  long cookie_login_lifetime;
+
+  /** @brief Signing key lifetime in seconds */
+  long cookie_key_lifetime;
   
   /* derived values: */
   int nparts;                          /* number of distinct name parts */
   
   /* derived values: */
   int nparts;                          /* number of distinct name parts */
diff --git a/lib/cookies.c b/lib/cookies.c
new file mode 100644 (file)
index 0000000..4ac9f65
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2007 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
+ * the Free Software Foundation; either version 2 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.
+ *
+ * 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
+ */
+/** @file lib/cookies.c
+ * @brief Cookie support
+ */
+
+#include <config.h>
+#include "types.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <time.h>
+#include <gcrypt.h>
+
+#include "cookies.h"
+#include "hash.h"
+#include "mem.h"
+#include "log.h"
+#include "printf.h"
+#include "mime.h"
+#include "configuration.h"
+#include "kvp.h"
+
+/** @brief Hash function used in signing HMAC */
+#define ALGO GCRY_MD_SHA1
+
+/** @brief Size of key to use */
+#define HASHSIZE 20
+
+/** @brief Signing key */
+static uint8_t signing_key[HASHSIZE];
+
+/** @brief Previous signing key */
+static uint8_t old_signing_key[HASHSIZE];
+
+/** @brief Signing key validity limit or 0 if none */
+static time_t signing_key_validity_limit;
+
+/** @brief Hash of revoked cookies */
+static hash *revoked;
+
+/** @brief Callback to expire revocation list */
+static int revoked_cleanup_callback(const char *key, void *value,
+                                    void *u) {
+  if(*(time_t *)value < *(time_t *)u)
+    hash_remove(revoked, key);
+  return 0;
+}
+
+/** @brief Generate a new key */
+static void newkey(void) {
+  time_t now;
+
+  time(&now);
+  memcpy(old_signing_key, signing_key, HASHSIZE);
+  gcry_randomize(signing_key, HASHSIZE, GCRY_STRONG_RANDOM);
+  signing_key_validity_limit = now + config->cookie_key_lifetime;
+  /* Now is a good time to clean up the revocation list... */
+  if(revoked)
+    hash_foreach(revoked, revoked_cleanup_callback, &now);
+}
+
+/** @brief Sign @p subject with @p key and return the base64 of the result
+ * @param key Key to sign with (@ref HASHSIZE bytes)
+ * @param subject Subject string
+ * @return Base64-encoded signature or NULL
+ */
+static char *sign(const uint8_t *key,
+                 const char *subject) {
+  gcry_error_t e;
+  gcry_md_hd_t h;
+  uint8_t *sig;
+  char *sig64;
+
+  if((e = gcry_md_open(&h, ALGO, GCRY_MD_FLAG_HMAC))) {
+    error(0, "gcry_md_open: %s", gcry_strerror(e));
+    return 0;
+  }
+  if((e = gcry_md_setkey(h, key, HASHSIZE))) {
+    error(0, "gcry_md_setkey: %s", gcry_strerror(e));
+    gcry_md_close(h);
+    return 0;
+  }
+  gcry_md_write(h, subject, strlen(subject));
+  sig = gcry_md_read(h, ALGO);
+  sig64 = mime_to_base64(sig, HASHSIZE);
+  gcry_md_close(h);
+  return sig64;
+}
+
+/** @brief Create a login cookie
+ * @param user Username
+ * @return Cookie or NULL
+ */
+char *make_cookie(const char *user) {
+  char *password;
+  time_t now;
+  char *b, *bp, *c, *g;
+  int n;
+
+  /* semicolons aren't allowed in usernames */
+  if(strchr(user, ';')) {
+    error(0, "make_cookie for username with semicolon");
+    return 0;
+  }
+  /* look up the password */
+  for(n = 0; n < config->allow.n
+       && strcmp(config->allow.s[n].s[0], user); ++n)
+    ;
+  if(n >= config->allow.n) {
+    error(0, "make_cookie for nonexistent user");
+    return 0;
+  }
+  password = config->allow.s[n].s[1];
+  /* make sure we have a valid signing key */
+  time(&now);
+  if(now >= signing_key_validity_limit)
+    newkey();
+  /* construct the subject */
+  byte_xasprintf(&b, "%jx;%s;", (intmax_t)now + config->cookie_login_lifetime,
+                urlencodestring(user));
+  byte_xasprintf(&bp, "%s%s", b, password);
+  /* sign it */
+  if(!(g = sign(signing_key, bp)))
+    return 0;
+  /* put together the final cookie */
+  byte_xasprintf(&c, "%s%s", b, g);
+  return c;
+}
+
+/** @brief Verify a cookie
+ * @param cookie Cookie to verify
+ * @return Verified user or NULL
+ */
+char *verify_cookie(const char *cookie) {
+  char *c1, *c2;
+  intmax_t t;
+  time_t now;
+  char *user, *bp, *password, *sig;
+  int n;
+
+  /* check the revocation list */
+  if(revoked && hash_find(revoked, cookie)) {
+    error(0, "attempt to log in with revoked cookie");
+    return 0;
+  }
+  /* parse the cookie */
+  errno = 0;
+  t = strtoimax(cookie, &c1, 16);
+  if(errno) {
+    error(errno, "error parsing cookie timestamp");
+    return 0;
+  }
+  if(*c1 != ';') {
+    error(0, "invalid cookie timestamp");
+    return 0;
+  }
+  /* There'd better be two semicolons */
+  c2 = strchr(c1 + 1, ';');
+  if(c2 == 0) {
+    error(0, "invalid cookie syntax");
+    return 0;
+  }
+  /* Extract the username */
+  user = xstrndup(c1 + 1, c2 - (c1 + 1));
+  /* check expiry */
+  time(&now);
+  if(now >= t) {
+    error(0, "cookie has expired");
+    return 0;
+  }
+  /* look up the password */
+  for(n = 0; n < config->allow.n
+       && strcmp(config->allow.s[n].s[0], user); ++n)
+    ;
+  if(n >= config->allow.n) {
+    error(0, "verify_cookie for nonexistent user");
+    return 0;
+  }
+  password = config->allow.s[n].s[1];
+  /* construct the expected subject.  We re-encode the timestamp and the
+   * password. */
+  byte_xasprintf(&bp, "%jx;%s;%s", t, urlencodestring(user), password);
+  /* Compute the expected signature.  NB we base64 the expected signature and
+   * compare that rather than exposing our base64 parser to the cookie. */
+  if(!(sig = sign(signing_key, bp)))
+    return 0;
+  if(!strcmp(sig, c2 + 1))
+    return user;
+  /* that didn't match, try the old key */
+  if(!(sig = sign(old_signing_key, bp)))
+    return 0;
+  if(!strcmp(sig, c2 + 1))
+    return user;
+  /* that didn't match either */
+  error(0, "cookie signature does not match");
+  return 0;
+}
+
+/** @brief Revoke a cookie
+ * @param cookie Cookie to revoke
+ *
+ * Further attempts to log in with @p cookie will fail.
+ */
+void revoke_cookie(const char *cookie) {
+  time_t when;
+  char *ptr;
+
+  /* find the cookie's expiry time */
+  errno = 0;
+  when = (time_t)strtoimax(cookie, &ptr, 16);
+  /* reject bogus cookies */
+  if(errno)
+    return;
+  if(*ptr != ';')
+    return;
+  /* make sure the revocation list exists */
+  if(!revoked)
+    revoked = hash_new(sizeof(time_t));
+  /* add the cookie to it; its value is the expiry time */
+  hash_add(revoked, cookie, &when, HASH_INSERT);
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
diff --git a/lib/cookies.h b/lib/cookies.h
new file mode 100644 (file)
index 0000000..f7e3a3a
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2007 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
+ * the Free Software Foundation; either version 2 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.
+ *
+ * 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
+ */
+/** @file lib/cookies.h
+ * @brief Cookie support
+ */
+
+#ifndef COOKIES_H
+#define COOKIES_H
+
+char *make_cookie(const char *user);
+char *verify_cookie(const char *cookie);
+void revoke_cookie(const char *cookie);
+
+#endif /* COOKIES_H */
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
index 6c18a8f7f9f50fd6b3f9c2d5174bf76e708a29e5..471de78e80bf8fe3cfa9743182904d64a9c22823 100644 (file)
--- a/lib/kvp.c
+++ b/lib/kvp.c
@@ -124,6 +124,10 @@ int urlencode(struct sink *sink, const char *s, size_t n) {
   return 0;
 }
 
   return 0;
 }
 
+/** @brief URL-encode @p s
+ * @param s String to encode
+ * @return Encoded string
+ */
 const char *urlencodestring(const char *s) {
   struct dynstr d;
 
 const char *urlencodestring(const char *s) {
   struct dynstr d;
 
@@ -133,6 +137,20 @@ const char *urlencodestring(const char *s) {
   return d.vec;
 }
 
   return d.vec;
 }
 
+/** @brief URL-decode @p s
+ * @param s String to decode
+ * @param ns Length of string
+ * @return Decoded string
+ */
+const char *urldecodestring(const char *s, size_t ns) {
+  struct dynstr d;
+
+  dynstr_init(&d);
+  urldecode(sink_dynstr(&d), s, ns);
+  dynstr_terminate(&d);
+  return d.vec;
+}
+
 char *kvp_urlencode(const struct kvp *kvp, size_t *np) {
   struct dynstr d;
   struct sink *sink;
 char *kvp_urlencode(const struct kvp *kvp, size_t *np) {
   struct dynstr d;
   struct sink *sink;
index db6d17756bfb3e4dad4daf3de6b72083a167b577..98c62cdb9224ec8bd5f049a513702284b1e3a23a 100644 (file)
--- a/lib/kvp.h
+++ b/lib/kvp.h
@@ -55,6 +55,8 @@ int urlencode(struct sink *sink, const char *s, size_t n);
 const char *urlencodestring(const char *s);
 /* return the url-encoded form of @s@ */
 
 const char *urlencodestring(const char *s);
 /* return the url-encoded form of @s@ */
 
+const char *urldecodestring(const char *s, size_t ns);
+
 #endif /* KVP_H */
 
 /*
 #endif /* KVP_H */
 
 /*
index 0f16c1a54c82533b1ffe0c14a2b4857f881e606d..c501be5b8802002d3bb45b16b85d1f8f39dca23c 100644 (file)
@@ -327,8 +327,10 @@ class client:
       sys.stderr.write("\n")
       sys.stderr.flush()
 
       sys.stderr.write("\n")
       sys.stderr.flush()
 
-  def connect(self):
-    """Connect to the DisOrder server and authenticate.
+  def connect(self, cookie=None):
+    """c.connect(cookie=None)
+
+    Connect to the DisOrder server and authenticate.
 
     Raises communicationError if connection fails and operationError if
     authentication fails (in which case disconnection is automatic).
 
     Raises communicationError if connection fails and operationError if
     authentication fails (in which case disconnection is automatic).
@@ -339,6 +341,9 @@ class client:
 
     Other operations automatically connect if we're not already
     connected, so it is not strictly necessary to call this method.
 
     Other operations automatically connect if we're not already
     connected, so it is not strictly necessary to call this method.
+
+    If COOKIE is specified then that is used to log in instead of
+    the username/password.
     """
     if self.state == 'disconnected':
       try:
     """
     if self.state == 'disconnected':
       try:
@@ -369,10 +374,13 @@ class client:
         self.w = s.makefile("wb")
         self.r = s.makefile("rb")
         (res, challenge) = self._simple()
         self.w = s.makefile("wb")
         self.r = s.makefile("rb")
         (res, challenge) = self._simple()
-        h = sha.sha()
-        h.update(self.config['password'])
-        h.update(binascii.unhexlify(challenge))
-        self._simple("user", self.config['username'], h.hexdigest())
+        if cookie is None:
+          h = sha.sha()
+          h.update(self.config['password'])
+          h.update(binascii.unhexlify(challenge))
+          self._simple("user", self.config['username'], h.hexdigest())
+        else:
+          self._simple("cookie", cookie)
         self.state = 'connected'
       except socket.error, e:
         self._disconnect()
         self.state = 'connected'
       except socket.error, e:
         self._disconnect()
@@ -833,6 +841,15 @@ class client:
     else:
       return details
 
     else:
       return details
 
+  def make_cookie(self):
+    """Create a login cookie"""
+    ret, details = self._simple("make-cookie")
+    return details
+  
+  def revoke(self):
+    """Revoke a login cookie"""
+    self._simple("revoke")
+
   ########################################################################
   # I/O infrastructure
 
   ########################################################################
   # I/O infrastructure
 
index eae90c60e49ed976020a07dae5e0252994ec70bf..4332ed4d3dc0f7ddb5564ac78ca832252976e992 100644 (file)
@@ -144,6 +144,7 @@ check-decode: disorder-decode disorder-normalize
        sox ${top_srcdir}/sounds/scratch.ogg scratch.wav
        ./disorder-decode scratch.wav | \
          ./disorder-normalize --config config > decoded.raw
        sox ${top_srcdir}/sounds/scratch.ogg scratch.wav
        ./disorder-decode scratch.wav | \
          ./disorder-normalize --config config > decoded.raw
+       ls -l *.raw
        cmp decoded.raw oggdec.raw
        rm -f scratch.wav config decoded.raw oggdec.raw
 
        cmp decoded.raw oggdec.raw
        rm -f scratch.wav config decoded.raw oggdec.raw
 
index ba18c237ff7c44a2100ec45c331a2f893234ef66..34d9e23fb2f33d79494323d1171bac22e76c1817 100644 (file)
@@ -38,6 +38,7 @@
 #include <sys/time.h>
 #include <pcre.h>
 #include <fcntl.h>
 #include <sys/time.h>
 #include <pcre.h>
 #include <fcntl.h>
+#include <gcrypt.h>
 
 #include "daemonize.h"
 #include "event.h"
 
 #include "daemonize.h"
 #include "event.h"
@@ -238,6 +239,8 @@ int main(int argc, char **argv) {
   info("process ID %lu", (unsigned long)getpid());
   fix_path();
   srand(time(0));                      /* don't start the same every time */
   info("process ID %lu", (unsigned long)getpid());
   fix_path();
   srand(time(0));                      /* don't start the same every time */
+  /* gcrypt initialization */
+  gcry_control(GCRYCTL_INIT_SECMEM, 1);
   /* create event loop */
   ev = ev_new();
   if(ev_child_setup(ev)) fatal(0, "ev_child_setup failed");
   /* create event loop */
   ev = ev_new();
   if(ev_child_setup(ev)) fatal(0, "ev_child_setup failed");
index a36996409331d7105d5d5adf99452675b491eb7d..cd97435cd2f4fcb3f8662b64438c0429e59b100d 100644 (file)
@@ -64,6 +64,7 @@
 #include "defs.h"
 #include "cache.h"
 #include "unicode.h"
 #include "defs.h"
 #include "cache.h"
 #include "unicode.h"
+#include "cookies.h"
 
 #ifndef NONCE_SIZE
 # define NONCE_SIZE 16
 
 #ifndef NONCE_SIZE
 # define NONCE_SIZE 16
@@ -107,6 +108,8 @@ struct conn {
   struct eventlog_output *lo;
   /** @brief Parent listener */
   const struct listener *l;
   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,
 };
 
 static int reader_callback(ev_source *ev,
@@ -370,39 +373,48 @@ static int c_become(struct conn *c,
   return 1;
 }
 
   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;
   union {
     struct sockaddr sa;
     struct sockaddr_in in;
     struct sockaddr_in6 in6;
   } u;
   socklen_t l;
+  int n;
   char host[1024];
 
   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);
   /* 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));
   }
   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
   } 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)
   /* find the user */
   for(n = 0; n < config->allow.n
        && strcmp(config->allow.s[n].s[0], vec[0]); ++n)
@@ -418,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(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;
       info("S%x %s connected from %s", c->tag, vec[0], host);
     sink_writes(ev_writer_sink(c->w), "230 OK\n");
     return 1;
@@ -962,7 +974,61 @@ static int c_rtp_address(struct conn *c,
     sink_writes(ev_writer_sink(c->w), "550 No RTP\n");
   return 1;
 }
     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 */
 
 #define C_AUTH         0001            /* must be authenticated */
 #define C_TRUSTED      0002            /* must be trusted user */
 
@@ -974,6 +1040,7 @@ static const struct command {
 } commands[] = {
   { "allfiles",       0, 2,       c_allfiles,       C_AUTH },
   { "become",         1, 1,       c_become,         C_AUTH|C_TRUSTED },
 } 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 },
   { "dirs",           0, 2,       c_dirs,           C_AUTH },
   { "disable",        0, 1,       c_disable,        C_AUTH },
   { "enable",         0, 0,       c_enable,         C_AUTH },
@@ -984,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 },
   { "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 },
   { "move",           2, 2,       c_move,           C_AUTH },
   { "moveafter",      1, INT_MAX, c_moveafter,      C_AUTH },
   { "new",            0, 1,       c_new,            C_AUTH },
@@ -1003,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 },
   { "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 },
   { "rtp-address",    0, 0,       c_rtp_address,    C_AUTH },
   { "scratch",        0, 1,       c_scratch,        C_AUTH },
   { "search",         1, 1,       c_search,         C_AUTH },
index 2cd5e633cdfa328a9559c618db25341892e7c109..9527a55fc1d72d2033753c8b14037e6c300b71b7 100644 (file)
@@ -30,4 +30,4 @@ check:
        ${PYTHON} ${srcdir}/alltests
 
 EXTRA_DIST=alltests dtest.py dbversion.py search.py \
        ${PYTHON} ${srcdir}/alltests
 
 EXTRA_DIST=alltests dtest.py dbversion.py search.py \
-       queue.py dump.py play.py
+       queue.py dump.py play.py cookie.py
diff --git a/tests/cookie.py b/tests/cookie.py
new file mode 100755 (executable)
index 0000000..7f00454
--- /dev/null
@@ -0,0 +1,56 @@
+#! /usr/bin/env python
+#
+# This file is part of DisOrder.
+# Copyright (C) 2007 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
+# the Free Software Foundation; either version 2 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.
+#
+# 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
+#
+import dtest,disorder
+
+def test():
+    """Exercise cookie protocol"""
+    dtest.start_daemon()
+    print " connecting"
+    c = disorder.client()
+    v = c.version()
+    print " getting cookie"
+    k = c.make_cookie()
+    print " connecting with cookie"
+    c = disorder.client()
+    c.connect(k)
+    v = c.version()
+    print " it worked"
+    print " connecting with cookie again"
+    c = disorder.client()
+    c.connect(k)
+    v = c.version()
+    print " it worked"
+    print " revoking cookie"
+    c.revoke()
+    v = c.version()
+    print " connection still works"
+    print " connecting with revoked cookie"
+    c = disorder.client()
+    try:
+      c.connect(k)
+      print "*** should not be able to connect with revoked cookie ***"
+      assert False
+    except disorder.operationError:
+      pass                              # good
+    print " revoked cookie was rejected"
+
+if __name__ == '__main__':
+    dtest.run()
index cd87c503788b9bdb58a40a2bc0ddd260658d33e9..ef25fc4bdd42307b22fd718f2db99df73fe44647 100644 (file)
@@ -300,12 +300,7 @@ def run(module=None, report=True):
     # Create some standard tracks
     stdtracks()
     try:
     # Create some standard tracks
     stdtracks()
     try:
-        try:
-            module.test()
-        except AssertionError, e:
-            global failures
-            failures += 1
-            print "assertion failed: %s" % e.message
+        module.test()
     finally:
         stop_daemon()
     if report:
     finally:
         stop_daemon()
     if report: