chiark / gitweb /
support alternative hashes for authentication
authorRichard Kettlewell <rjk@greenend.org.uk>
Tue, 2 Oct 2007 10:23:41 +0000 (11:23 +0100)
committerRichard Kettlewell <rjk@greenend.org.uk>
Tue, 2 Oct 2007 10:23:41 +0000 (11:23 +0100)
doc/disorder_config.5.in
doc/disorder_protocol.5.in
lib/authhash.c
lib/authhash.h
lib/client.c
lib/configuration.c
lib/configuration.h
lib/eclient.c
server/Makefile.am
server/server.c

index 4cde04fcf7bd388c7453a47c6cdca0580e6f50c7..b1ea0b1fa6de6a84e5b91edc5d9be10bc16ed0c6 100644 (file)
@@ -142,6 +142,12 @@ automatically included, but should include the proper extension.
 .IP
 The default is \fB{/artist}{/album}{/title}{ext}\fR.
 .TP
+.B authorization_algorthm \fIALGORITHM\fR
+Defines the algorithm used to authenticate clients.  The valid options
+are sha1 (the default), sha256, sha384 and sha512.  See
+.BR disorder_protocol (5)
+for more details.
+.TP
 .B broadcast \fIADDRESS\fR \fIPORT\fR
 Transmit sound data to \fIADDRESS\fR using UDP port \fIPORT\fR.  This implies
 \fBspeaker_backend network\fR.
index 07fada9bd9694505170f347e7ced345dd9cc5008..9e64d947b6c811f4c2c3f7f791074db2e0aaff6d 100644 (file)
@@ -231,10 +231,10 @@ Unset a global preference.
 Authenticate as \fIUSER\fR.
 .IP
 When a connection is made the server sends a \fB221\fR response before any
-command is received.  As its first field this contains a challenge string
-encoded in hex.
+command is received.  This contains an algorithm name and a challenge encoded
+in hex.  Currently the algorithm name is omitted if it is "sha1".
 .IP
-The \fIRESPONSE\fR consists of the SHA-1 hash of the user's password
+The \fIRESPONSE\fR consists of the selected hash of the user's password
 concatenated with the challenge, encoded in hex.
 .TP
 .B version
index b97204d9364a76aee5479419c4c8374d17045ce5..1eaf27f2f504f840d69618d06eab50ad6a414557 100644 (file)
 #include "log.h"
 #include "authhash.h"
 
-#ifndef AUTHHASH
-/** @brief Which hash function to use */
-# define AUTHHASH GCRY_MD_SHA1
-#endif
+/** @brief Structure of algorithm lookup table */
+struct algorithm {
+  const char *name;
+  int id;
+};
+
+/** @brief Algorithm lookup table
+ *
+ * We don't use gcry_md_map_name() since that would import gcrypt's API into
+ * the disorder protocol.
+ */
+static const struct algorithm algorithms[] = {
+  { "SHA1", GCRY_MD_SHA1 },
+  { "sha1", GCRY_MD_SHA1 },
+  { "SHA256", GCRY_MD_SHA256 },
+  { "sha256", GCRY_MD_SHA256 },
+  { "SHA384", GCRY_MD_SHA384 },
+  { "sha384", GCRY_MD_SHA384 },
+  { "SHA512", GCRY_MD_SHA512 },
+  { "sha512", GCRY_MD_SHA512 },
+};
+
+/** @brief Number of supported algorithms */
+#define NALGORITHMS (sizeof algorithms / sizeof *algorithms)
 
 /** @brief Perform the authorization hash function
  * @param challenge Pointer to challange
  * @param nchallenge Size of challenge
  * @param password Password
+ * @param algo Algorithm to use
  *
  * Computes H(challenge|password) and returns it as a newly allocated hex
- * string.  Currently the hash function is SHA-1, but this may be changed in
- * future versions; see @ref AUTHHASH.
+ * string, or returns NULL on error.
  */
 const char *authhash(const void *challenge, size_t nchallenge,
-                    const char *password) {
+                    const char *password, const char *algo) {
   gcrypt_hash_handle h;
   const char *res;
+  size_t n;
+  int id;
 
   assert(challenge != 0);
   assert(password != 0);
+  assert(algo != 0);
+  for(n = 0; n < NALGORITHMS; ++n)
+    if(!strcmp(algo, algorithms[n].name))
+      break;
+  if(n >= NALGORITHMS)
+    return NULL;
+  id = algorithms[n].id;
 #if HAVE_GCRY_ERROR_T
   {
     gcry_error_t e;
     
-    if((e = gcry_md_open(&h, AUTHHASH, 0))) {
+    if((e = gcry_md_open(&h, id, 0))) {
       error(0, "gcry_md_open: %s", gcry_strerror(e));
       return 0;
     }
   }
 #else
-  h = gcry_md_open(AUTHHASH, 0);
+  h = gcry_md_open(id, 0);
 #endif
   gcry_md_write(h, password, strlen(password));
   gcry_md_write(h, challenge, nchallenge);
-  res = hex(gcry_md_read(h, AUTHHASH), gcry_md_get_algo_dlen(AUTHHASH));
+  res = hex(gcry_md_read(h, id), gcry_md_get_algo_dlen(id));
   gcry_md_close(h);
   return res;
 }
 
+/** @brief Return non-zero if @p algo is a valid algorithm */
+int valid_authhash(const char *algo) {
+  size_t n;
+
+  for(n = 0; n < NALGORITHMS; ++n)
+    if(!strcmp(algo, algorithms[n].name))
+      return 1;
+  return 0;
+}
+
 /*
 Local Variables:
 c-basic-offset:2
index 99992793b6edeb612aeb18bc3d31c7e64dc96eb9..38981e10b4f700c8f53f0fea10aa122c80473f0b 100644 (file)
@@ -21,7 +21,8 @@
 #define AUTHHASH_H
 
 const char *authhash(const void *challenge, size_t nchallenge,
-                    const char *user);
+                    const char *user, const char *algo);
+int valid_authhash(const char *algo);
 
 #endif /* AUTHHASH_H */
 
index 97dde93825ddef6c019e32f5b6bfb6498cba747a..979e54ab9432b20c5c28d781ec05cb95babb74b0 100644 (file)
@@ -207,11 +207,12 @@ int disorder_connect_sock(disorder_client *c,
                          const char *username,
                          const char *password,
                          const char *ident) {
-  int fd = -1, fd2 = -1;
+  int fd = -1, fd2 = -1, nrvec;
   unsigned char *nonce;
   size_t nl;
   const char *res;
-  char *r;
+  char *r, **rvec;
+  const char *algo = "SHA1";
 
   if(!password) {
     error(0, "no password found");
@@ -243,9 +244,17 @@ int disorder_connect_sock(disorder_client *c,
   c->ident = xstrdup(ident);
   if(disorder_simple(c, &r, 0, (const char *)0))
     return -1;
-  if(!(nonce = unhex(r, &nl)))
+  if(!(rvec = split(r, &nrvec, SPLIT_QUOTES, 0, 0)))
+    return -1;
+  if(nrvec > 1) {
+    algo = *rvec++;
+    --nrvec;
+  }
+  if(!nrvec)
+    return -1;
+  if(!(nonce = unhex(*rvec, &nl)))
     return -1;
-  if(!(res = authhash(nonce, nl, password))) goto error;
+  if(!(res = authhash(nonce, nl, password, algo))) goto error;
   if(disorder_simple(c, 0, "user", username, res, (char *)0))
     return -1;
   c->user = xstrdup(username);
index ae71d6321c32943e28e680f25f79effd839bf319..ad410245b5e752031b803f1f73a5e46836b5d9c5 100644 (file)
@@ -51,6 +51,7 @@
 #include "printf.h"
 #include "regsub.h"
 #include "signame.h"
+#include "authhash.h"
 
 /** @brief Path to config file 
  *
@@ -812,6 +813,20 @@ static int validate_port(const struct config_state attribute((unused)) *cs,
   }
 }
 
+static int validate_algo(const struct config_state attribute((unused)) *cs,
+                        int nvec,
+                        char **vec) {
+  if(nvec != 1) {
+    error(0, "%s:%d: invalid algorithm specification", cs->path, cs->line);
+    return -1;
+  }
+  if(!valid_authhash(vec[0])) {
+    error(0, "%s:%d: unsuported algorithm '%s'", cs->path, cs->line, vec[0]);
+    return -1;
+  }
+  return 0;
+}
+
 /** @brief Item name and and offset */
 #define C(x) #x, offsetof(struct config, x)
 /** @brief Item name and and offset */
@@ -821,6 +836,7 @@ static int validate_port(const struct config_state attribute((unused)) *cs,
 static const struct conf conf[] = {
   { C(alias),            &type_string,           validate_alias },
   { C(allow),            &type_stringlist_accum, validate_allow },
+  { C(authorization_algorithm), &type_string,    validate_algo },
   { C(broadcast),        &type_stringlist,       validate_addrport },
   { C(broadcast_from),   &type_stringlist,       validate_addrport },
   { C(channel),          &type_string,           validate_channel },
@@ -975,6 +991,7 @@ static struct config *config_default(void) {
   c->queue_pad = 10;
   c->speaker_backend = -1;
   c->multicast_ttl = 1;
+  c->authorization_algorithm = xstrdup("sha1");
   return c;
 }
 
index 84b1eb858de8601fd82620fb84cf35d190c9124a..e9893f0f0dd16bd6c6c22013ecd57ee86b6c49b6 100644 (file)
@@ -96,6 +96,9 @@ struct transformlist {
 struct config {
   /* server config */
 
+  /** @brief Authorization algorithm */
+  char *authorization_algorithm;
+  
   /** @brief All players */
   struct stringlistlist player;
 
index e5b723de7f8b824cbf2201f82a45a93d07d1969f..766167958a16a845469ad7539d7fb201f7cc1cba 100644 (file)
@@ -503,16 +503,31 @@ static void authbanner_opcallback(disorder_eclient *c,
   size_t nonce_len;
   const unsigned char *nonce;
   const char *res;
+  char **rvec;
+  int nrvec;
+  const char *algo = "SHA1";
   
   D(("authbanner_opcallback"));
-  if(c->rc / 100 != 2) {
-    /* Banner told us to go away.  We cannot proceed. */
+  if(c->rc / 100 != 2
+     || !(rvec = split(c->line + 4, &nrvec, SPLIT_QUOTES, 0, 0))
+     || nrvec < 1) {
+    /* Banner told us to go away, or was malformed.  We cannot proceed. */
     protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
     disorder_eclient_close(c);
     return;
   }
+  if(nrvec > 1) {
+    algo = *rvec++;
+    --nrvec;
+  }
   nonce = unhex(c->line +  4, &nonce_len);
-  res = authhash(nonce, nonce_len, config->password);
+  res = authhash(nonce, nonce_len, config->password, algo);
+  if(!res) {
+    protocol_error(c, op, c->rc, "%s: unknown authentication algorithm '%s'",
+                   c->ident, algo);
+    disorder_eclient_close(c);
+    return;
+  }
   stash_command(c, 1/*queuejump*/, authuser_opcallback, 0/*completed*/, 0/*v*/,
                 "user", quoteutf8(config->username), quoteutf8(res),
                 (char *)0);
index e63f6aa1966efbda29aa3debbff721cda6a8b8af..2b655956128aada719294b48f18fd1a5cb932c42 100644 (file)
@@ -41,7 +41,7 @@ disorderd_DEPENDENCIES=../lib/libdisorder.a
 disorder_deadlock_SOURCES=deadlock.c                    \
        trackdb.c trackdb.h
 disorder_deadlock_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
-       $(LIBDB) $(LIBPCRE) $(LIBICONV)
+       $(LIBDB) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT)
 disorder_deadlock_DEPENDENCIES=../lib/libdisorder.a
 
 disorder_speaker_SOURCES=speaker.c speaker.h \
@@ -58,7 +58,8 @@ disorder_decode_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
 disorder_decode_DEPENDENCIES=../lib/libdisorder.a
 
 disorder_normalize_SOURCES=normalize.c
-disorder_normalize_LDADD=$(LIBOBJS) ../lib/libdisorder.a $(LIBPCRE) $(LIBICONV)
+disorder_normalize_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
+       $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT)
 disorder_normalize_DEPENDENCIES=../lib/libdisorder.a
 
 disorder_rescan_SOURCES=rescan.c                        \
@@ -66,7 +67,7 @@ disorder_rescan_SOURCES=rescan.c                        \
        trackdb.c trackdb.h exports.c                   \
        ../lib/memgc.c
 disorder_rescan_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
-       $(LIBDB) $(LIBGC) $(LIBPCRE) $(LIBICONV)
+       $(LIBDB) $(LIBGC) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT)
 disorder_rescan_LDFLAGS=-export-dynamic
 disorder_rescan_DEPENDENCIES=../lib/libdisorder.a
 
@@ -74,7 +75,7 @@ disorder_dump_SOURCES=dump.c                                  \
         trackdb.c trackdb.h                            \
        ../lib/memgc.c
 disorder_dump_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
-       $(LIBPCRE) $(LIBDB) $(LIBICONV) $(LIBGC)
+       $(LIBPCRE) $(LIBDB) $(LIBICONV) $(LIBGC) $(LIBGCRYPT)
 disorder_dump_DEPENDENCIES=$(LIBOBJS) ../lib/libdisorder.a
 
 disorder_cgi_SOURCES=dcgi.c dcgi.h                     \
@@ -86,7 +87,7 @@ disorder_cgi_LDFLAGS=-export-dynamic
 disorder_cgi_DEPENDENCIES=../lib/libdisorder.a
 
 trackname_SOURCES=trackname.c
-trackname_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBICONV)
+trackname_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT)
 trackname_DEPENDENCIES=../lib/libdisorder.a
 
 install-exec-hook:
index a30e97828b4f30a1e9a646171a7464c2d591b0c5..6ee11c962c221daa067e0fa53563fa62a547a6cb 100644 (file)
@@ -391,7 +391,8 @@ static int c_user(struct conn *c,
     sink_writes(ev_writer_sink(c->w), "530 authentication failed\n");
     return 1;
   }
-  res = authhash(c->nonce, sizeof c->nonce, config->allow.s[n].s[1]);
+  res = authhash(c->nonce, sizeof c->nonce, config->allow.s[n].s[1],
+                config->authorization_algorithm);
   if(wideopen || (res && !strcmp(res, vec[1]))) {
     c->who = vec[0];
     /* currently we only bother logging remote connections */
@@ -1084,7 +1085,15 @@ static int listen_callback(ev_source *ev,
   c->reader = reader_callback;
   c->l = l;
   gcry_randomize(c->nonce, sizeof c->nonce, GCRY_STRONG_RANDOM);
-  sink_printf(ev_writer_sink(c->w), "231 %s\n", hex(c->nonce, sizeof c->nonce));
+  if(!strcmp(config->authorization_algorithm, "sha1")
+     || !strcmp(config->authorization_algorithm, "SHA1")) {
+    sink_printf(ev_writer_sink(c->w), "231 %s\n",
+               hex(c->nonce, sizeof c->nonce));
+  } else {
+    sink_printf(ev_writer_sink(c->w), "231 %s %s\n",
+               config->authorization_algorithm,
+               hex(c->nonce, sizeof c->nonce));
+  }
   return 0;
 }