chiark / gitweb /
lib/configuration.c, etc.: Replace `config_userconf()' by a variable.
[disorder] / lib / configuration.c
index e2af880368ee04290c147167c7be6b988ef83e08..469804d41826b9c47c279849e974da5a81f08fef 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2004-2009 Richard Kettlewell
+ * Copyright (C) 2004-2011, 2013 Richard Kettlewell
  * Portions copyright (C) 2007 Mark Wooding
  *
  * This program is free software: you can redistribute it and/or modify
 #include <errno.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <unistd.h>
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
 #include <ctype.h>
 #include <stddef.h>
-#include <pwd.h>
-#include <langinfo.h>
-#include <pcre.h>
+#if HAVE_PWD_H
+# include <pwd.h>
+#endif
+#if HAVE_LANGINFO_H
+# include <langinfo.h>
+#endif
+
 #include <signal.h>
 
 #include "rights.h"
 #include "log.h"
 #include "split.h"
 #include "syscalls.h"
+#include "home.h"
 #include "table.h"
 #include "inputline.h"
 #include "charset.h"
 #include "defs.h"
 #include "printf.h"
+#include "regexp.h"
 #include "regsub.h"
 #include "signame.h"
 #include "authhash.h"
 #include "vector.h"
+#if !_WIN32
 #include "uaudio.h"
+#endif
 
 /** @brief Path to config file 
  *
- * set_configfile() sets the deafult if it is null.
+ * set_configfile() sets the default if it is null.
  */
 char *configfile;
 
+/** @brief Path to user's config file
+ *
+ * set_configfile() sets the default if it is null.
+ */
+char *userconfigfile;
+
 /** @brief Read user configuration
  *
  * If clear, the user-specific configuration is not read.
  */
 int config_per_user = 1;
 
+#if !_WIN32
 /** @brief Table of audio APIs
  *
  * Only set in server processes.
  */
 const struct uaudio *const *config_uaudio_apis;
+#endif
 
 /** @brief Config file parser state */
 struct config_state {
@@ -200,8 +218,13 @@ static int set_collections(const struct config_state *cs,
   /* Defaults */
   if(!module)
     module = "fs";
+#if HAVE_LANGINFO_H
   if(!encoding)
     encoding = nl_langinfo(CODESET);
+#else
+  if(!encoding)
+    encoding = "ascii";
+#endif
   cl = ADDRESS(cs->config, struct collectionlist);
   ++cl->n;
   cl->s = xrealloc(cl->s, cl->n * sizeof (struct collection));
@@ -240,26 +263,11 @@ static int set_string(const struct config_state *cs,
                   cs->path, cs->line, whoami->name);
     return -1;
   }
+  xfree(VALUE(cs->config, char *));
   VALUE(cs->config, char *) = xstrdup(vec[0]);
   return 0;
 }
 
-static int set_stringlist(const struct config_state *cs,
-                         const struct conf *whoami,
-                         int nvec, char **vec) {
-  int n;
-  struct stringlist *sl;
-
-  sl = ADDRESS(cs->config, struct stringlist);
-  sl->n = 0;
-  for(n = 0; n < nvec; ++n) {
-    sl->n++;
-    sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
-    sl->s[sl->n - 1] = xstrdup(vec[n]);
-  }
-  return 0;
-}
-
 static int set_integer(const struct config_state *cs,
                       const struct conf *whoami,
                       int nvec, char **vec) {
@@ -322,33 +330,6 @@ static int set_string_accum(const struct config_state *cs,
   return 0;
 }
 
-static int set_restrict(const struct config_state *cs,
-                       const struct conf *whoami,
-                       int nvec, char **vec) {
-  unsigned r = 0;
-  int n, i;
-  
-  static const struct restriction {
-    const char *name;
-    unsigned bit;
-  } restrictions[] = {
-    { "remove", RESTRICT_REMOVE },
-    { "scratch", RESTRICT_SCRATCH },
-    { "move", RESTRICT_MOVE },
-  };
-
-  for(n = 0; n < nvec; ++n) {
-    if((i = TABLE_FIND(restrictions, name, vec[n])) < 0) {
-      disorder_error(0, "%s:%d: invalid restriction '%s'",
-                    cs->path, cs->line, vec[n]);
-      return -1;
-    }
-    r |= restrictions[i].bit;
-  }
-  VALUE(cs->config, unsigned) = r;
-  return 0;
-}
-
 static int parse_sample_format(const struct config_state *cs,
                               struct stream_header *format,
                               int nvec, char **vec) {
@@ -369,13 +350,13 @@ static int parse_sample_format(const struct config_state *cs,
                   cs->path, cs->line, t);
     return -1;
   }
-  if(format) format->bits = t;
+  if(format) format->bits = (uint8_t)t;
   switch (*p) {
     case 'l': case 'L': t = ENDIAN_LITTLE; p++; break;
     case 'b': case 'B': t = ENDIAN_BIG; p++; break;
     default: t = ENDIAN_NATIVE; break;
   }
-  if(format) format->endian = t;
+  if(format) format->endian = (uint8_t)t;
   if(*p != '/') {
     disorder_error(errno, "%s:%d: expected `/' after bits-per-sample",
          cs->path, cs->line);
@@ -406,7 +387,7 @@ static int parse_sample_format(const struct config_state *cs,
                   cs->path, cs->line, t);
     return -1;
   }
-  if(format) format->channels = t;
+  if(format) format->channels = (uint8_t)t;
   if(*p) {
     disorder_error(0, "%s:%d: junk after channels", cs->path, cs->line);
     return -1;
@@ -426,9 +407,10 @@ static int set_namepart(const struct config_state *cs,
                        int nvec, char **vec) {
   struct namepartlist *npl = ADDRESS(cs->config, struct namepartlist);
   unsigned reflags;
-  const char *errstr;
-  int erroffset, n;
-  pcre *re;
+  regexp *re;
+  char errstr[RXCERR_LEN];
+  size_t erroffset;
+  int n;
 
   if(nvec < 3) {
     disorder_error(0, "%s:%d: namepart needs at least 3 arguments",
@@ -441,11 +423,10 @@ static int set_namepart(const struct config_state *cs,
     return -1;
   }
   reflags = nvec >= 5 ? regsub_flags(vec[4]) : 0;
-  if(!(re = pcre_compile(vec[1],
-                        PCRE_UTF8
-                        |regsub_compile_options(reflags),
-                        &errstr, &erroffset, 0))) {
-    disorder_error(0, "%s:%d: compiling regexp /%s/: %s (offset %d)",
+  if(!(re = regexp_compile(vec[1], regsub_compile_options(reflags),
+                          errstr, sizeof(errstr), &erroffset)))
+  {
+    disorder_error(0, "%s:%d: compiling regexp /%s/: %s (offset %zu)",
                   cs->path, cs->line, vec[1], errstr, erroffset);
     return -1;
   }
@@ -473,10 +454,10 @@ static int set_transform(const struct config_state *cs,
                         const struct conf *whoami,
                         int nvec, char **vec) {
   struct transformlist *tl = ADDRESS(cs->config, struct transformlist);
-  pcre *re;
+  regexp *re;
+  char errstr[RXCERR_LEN];
   unsigned reflags;
-  const char *errstr;
-  int erroffset;
+  size_t erroffset;
 
   if(nvec < 3) {
     disorder_error(0, "%s:%d: transform needs at least 3 arguments",
@@ -489,11 +470,10 @@ static int set_transform(const struct config_state *cs,
     return -1;
   }
   reflags = (nvec >= 5 ? regsub_flags(vec[4]) : 0);
-  if(!(re = pcre_compile(vec[1],
-                        PCRE_UTF8
-                        |regsub_compile_options(reflags),
-                        &errstr, &erroffset, 0))) {
-    disorder_error(0, "%s:%d: compiling regexp /%s/: %s (offset %d)",
+  if(!(re = regexp_compile(vec[1], regsub_compile_options(reflags),
+                          errstr, sizeof(errstr), &erroffset)))
+  {
+    disorder_error(0, "%s:%d: compiling regexp /%s/: %s (offset %zu)",
                   cs->path, cs->line, vec[1], errstr, erroffset);
     return -1;
   }
@@ -520,8 +500,7 @@ static int set_rights(const struct config_state *cs,
                   cs->path, cs->line, vec[0]);
     return -1;
   }
-  *ADDRESS(cs->config, char *) = vec[0];
-  return 0;
+  return set_string(cs, whoami, nvec, vec);
 }
 
 static int set_netaddress(const struct config_state *cs,
@@ -597,7 +576,7 @@ static void free_namepartlist(struct config *c,
   for(n = 0; n < npl->n; ++n) {
     np = &npl->s[n];
     xfree(np->part);
-    pcre_free(np->re);                 /* ...whatever pcre_free is set to. */
+    regexp_free(np->re);
     xfree(np->res);
     xfree(np->replace);
     xfree(np->context);
@@ -614,7 +593,7 @@ static void free_transformlist(struct config *c,
   for(n = 0; n < tl->n; ++n) {
     t = &tl->t[n];
     xfree(t->type);
-    pcre_free(t->re);                  /* ...whatever pcre_free is set to. */
+    regexp_free(t->re);
     xfree(t->replace);
     xfree(t->context);
   }
@@ -635,16 +614,14 @@ static const struct conftype
   type_collections = { set_collections, free_collectionlist },
   type_boolean = { set_boolean, free_none },
   type_string = { set_string, free_string },
-  type_stringlist = { set_stringlist, free_stringlist },
   type_integer = { set_integer, free_none },
   type_stringlist_accum = { set_stringlist_accum, free_stringlistlist },
   type_string_accum = { set_string_accum, free_stringlist },
   type_sample_format = { set_sample_format, free_none },
-  type_restrict = { set_restrict, free_none },
   type_namepart = { set_namepart, free_namepartlist },
   type_transform = { set_transform, free_transformlist },
   type_netaddress = { set_netaddress, free_netaddress },
-  type_rights = { set_rights, free_none };
+  type_rights = { set_rights, free_string };
 
 /* specific validation routine */
 
@@ -685,7 +662,7 @@ static int validate_isabspath(const struct config_state *cs,
 
   for(n = 0; n < nvec; ++n)
     if(vec[n][0] != '/') {
-      disorder_error(errno, "%s:%d: %s: not an absolute path", 
+      disorder_error(0, "%s:%d: %s: not an absolute path", 
                     cs->path, cs->line, vec[n]);
       return -1;
     }
@@ -725,7 +702,7 @@ static int validate_isreg(const struct config_state *cs,
 static int validate_player(const struct config_state *cs,
                           int nvec,
                           char attribute((unused)) **vec) {
-  if(nvec < 2) {
+  if(nvec && nvec < 2) {
     disorder_error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
                   cs->path, cs->line);
     return -1;
@@ -742,7 +719,7 @@ static int validate_player(const struct config_state *cs,
 static int validate_tracklength(const struct config_state *cs,
                                int nvec,
                                char attribute((unused)) **vec) {
-  if(nvec < 2) {
+  if(nvec && nvec < 2) {
     disorder_error(0, "%s:%d: should be at least 'tracklength PATTERN MODULE'",
                   cs->path, cs->line);
     return -1;
@@ -750,19 +727,27 @@ static int validate_tracklength(const struct config_state *cs,
   return 0;
 }
 
-/** @brief Validate an allow directive
+/** @brief Common code for validating integer values
  * @param cs Configuration state
  * @param nvec Length of (proposed) new value
  * @param vec Elements of new value
- * @return 0 on success, non-0 on error
- *
- * Obsolete - only used for parsing legacy configuration.
+ * @param n_out Where to put the value
  */
-static int validate_allow(const struct config_state *cs,
-                         int nvec,
-                         char attribute((unused)) **vec) {
-  if(nvec != 2) {
-    disorder_error(0, "%s:%d: must be 'allow NAME PASS'", cs->path, cs->line);
+static int common_validate_integer(const struct config_state *cs,
+                                  int nvec, char **vec, long *n_out) {
+  char errbuf[1024];
+
+  if(nvec < 1) {
+    disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
+    return -1;
+  }
+  if(nvec > 1) {
+    disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
+    return -1;
+  }
+  if(xstrtol(n_out, vec[0], 0, 0)) {
+    disorder_error(0, "%s:%d: %s", cs->path, cs->line,
+                   format_error(ec_errno, errno, errbuf, sizeof errbuf));
     return -1;
   }
   return 0;
@@ -777,19 +762,7 @@ static int validate_allow(const struct config_state *cs,
 static int validate_non_negative(const struct config_state *cs,
                                 int nvec, char **vec) {
   long n;
-
-  if(nvec < 1) {
-    disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
-    return -1;
-  }
-  if(nvec > 1) {
-    disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
-    return -1;
-  }
-  if(xstrtol(&n, vec[0], 0, 0)) {
-    disorder_error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
-    return -1;
-  }
+  if(common_validate_integer(cs, nvec, vec, &n)) return -1;
   if(n < 0) {
     disorder_error(0, "%s:%d: must not be negative", cs->path, cs->line);
     return -1;
@@ -806,19 +779,7 @@ static int validate_non_negative(const struct config_state *cs,
 static int validate_positive(const struct config_state *cs,
                          int nvec, char **vec) {
   long n;
-
-  if(nvec < 1) {
-    disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
-    return -1;
-  }
-  if(nvec > 1) {
-    disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
-    return -1;
-  }
-  if(xstrtol(&n, vec[0], 0, 0)) {
-    disorder_error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
-    return -1;
-  }
+  if(common_validate_integer(cs, nvec, vec, &n)) return -1;
   if(n <= 0) {
     disorder_error(0, "%s:%d: must be positive", cs->path, cs->line);
     return -1;
@@ -826,6 +787,7 @@ static int validate_positive(const struct config_state *cs,
   return 0;
 }
 
+#if !_WIN32
 /** @brief Validate a system username
  * @param cs Configuration state
  * @param nvec Length of (proposed) new value
@@ -835,14 +797,13 @@ static int validate_positive(const struct config_state *cs,
 static int validate_isauser(const struct config_state *cs,
                            int attribute((unused)) nvec,
                            char **vec) {
-  struct passwd *pw;
-
-  if(!(pw = getpwnam(vec[0]))) {
+  if(!getpwnam(vec[0])) {
     disorder_error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
     return -1;
   }
   return 0;
 }
+#endif
 
 /** @brief Validate a sample format string
  * @param cs Configuration state
@@ -981,6 +942,7 @@ static int validate_algo(const struct config_state attribute((unused)) *cs,
   return 0;
 }
 
+#if !_WIN32
 /** @brief Validate a playback backend name
  * @param cs Configuration state
  * @param nvec Length of (proposed) new value
@@ -1009,6 +971,7 @@ static int validate_backend(const struct config_state attribute((unused)) *cs,
   /* In non-server processes we have no idea what's valid */
   return 0;
 }
+#endif
 
 /** @brief Validate a pause mode string
  * @param cs Configuration state
@@ -1025,6 +988,24 @@ static int validate_pausemode(const struct config_state attribute((unused)) *cs,
   return -1;
 }
 
+/** @brief Validate an MTU-discovery setting
+ * @param cs Configuration state
+ * @param nvec Length of (proposed) new value
+ * @param vec Elements of new value
+ * @return 0 on success, non-0 on error
+ */
+static int validate_mtu_discovery(const struct config_state attribute((unused)) *cs,
+                                 int nvec,
+                                 char **vec) {
+  if (nvec == 1 &&
+      (!strcmp(vec[0], "default") ||
+       !strcmp(vec[0], "yes") ||
+       !strcmp(vec[0], "no")))
+    return 0;
+  disorder_error(0, "%s:%d: invalid MTU-discovery setting", cs->path, cs->line);
+  return -1;
+}
+
 /** @brief Validate a destination network address
  * @param cs Configuration state
  * @param nvec Length of (proposed) new value
@@ -1047,6 +1028,33 @@ static int validate_destaddr(const struct config_state attribute((unused)) *cs,
     disorder_error(0, "%s:%d: destination address required", cs->path, cs->line);
     return -1;
   }
+  xfree(na->address);
+  return 0;
+}
+
+/** @brief Validate an internet address
+ * @param cs Configuration state
+ * @param nvec Length of (proposed) new value
+ * @param vec Elements of new value
+ * @return 0 on success, non-0 on error
+ *
+ * By a destination address, it is meant that it must be either IPv4 or IPv6.
+ */
+static int validate_inetaddr(const struct config_state *cs,
+                            int nvec, char **vec) {
+  struct netaddress na[1];
+
+  if(netaddress_parse(na, nvec, vec)) {
+    disorder_error(0, "%s:%d: invalid network address", cs->path, cs->line);
+    return -1;
+  }
+  switch(na->af) {
+    case AF_INET: case AF_INET6: case AF_UNSPEC: break;
+    default:
+      disorder_error(0, "%s:%d: must be an intenet address",
+                    cs->path, cs->line);
+      return -1;
+  }
   return 0;
 }
 
@@ -1058,8 +1066,9 @@ static int validate_destaddr(const struct config_state attribute((unused)) *cs,
 /** @brief All configuration items */
 static const struct conf conf[] = {
   { C(alias),            &type_string,           validate_alias },
-  { C(allow),            &type_stringlist_accum, validate_allow },
+#if !_WIN32
   { C(api),              &type_string,           validate_backend },
+#endif
   { C(authorization_algorithm), &type_string,    validate_algo },
   { C(broadcast),        &type_netaddress,       validate_destaddr },
   { C(broadcast_from),   &type_netaddress,       validate_any },
@@ -1068,18 +1077,19 @@ static const struct conf conf[] = {
   { C(checkpoint_min),   &type_integer,          validate_non_negative },
   { C(collection),       &type_collections,      validate_any },
   { C(connect),          &type_netaddress,       validate_destaddr },
-  { C(cookie_login_lifetime),  &type_integer,    validate_positive },
   { C(cookie_key_lifetime),  &type_integer,      validate_positive },
+  { C(cookie_login_lifetime),  &type_integer,    validate_positive },
   { C(dbversion),        &type_integer,          validate_positive },
   { C(default_rights),   &type_rights,           validate_any },
   { C(device),           &type_string,           validate_any },
-  { C(gap),              &type_integer,          validate_non_negative },
   { C(history),          &type_integer,          validate_positive },
+#if !_WIN32
   { C(home),             &type_string,           validate_isabspath },
+#endif
   { C(listen),           &type_netaddress,       validate_any },
-  { C(lock),             &type_boolean,          validate_any },
   { C(mail_sender),      &type_string,           validate_any },
   { C(mixer),            &type_string,           validate_any },
+  { C(mount_rescan),     &type_boolean,          validate_any },
   { C(multicast_loop),   &type_boolean,          validate_any },
   { C(multicast_ttl),    &type_integer,          validate_non_negative },
   { C(namepart),         &type_namepart,         validate_any },
@@ -1097,30 +1107,44 @@ static const struct conf conf[] = {
   { C(playlist_lock_timeout), &type_integer,     validate_positive },
   { C(playlist_max) ,    &type_integer,          validate_positive },
   { C(plugins),          &type_string_accum,     validate_isdir },
-  { C(prefsync),         &type_integer,          validate_positive },
   { C(queue_pad),        &type_integer,          validate_positive },
-  { C(replay_min),       &type_integer,          validate_non_negative },
   { C(refresh),          &type_integer,          validate_positive },
+  { C(refresh_min),      &type_integer,          validate_non_negative },
   { C(reminder_interval), &type_integer,         validate_positive },
   { C(remote_userman),   &type_boolean,          validate_any },
-  { C2(restrict, restrictions),         &type_restrict,         validate_any },
+  { C(replay_min),       &type_integer,          validate_non_negative },
+  { C(rtp_always_request), &type_boolean,       validate_any },
   { C(rtp_delay_threshold), &type_integer,       validate_positive },
+  { C(rtp_instance_name), &type_string,                 validate_any },
+  { C(rtp_max_payload),         &type_integer,          validate_positive },
+  { C(rtp_maxbuffer),   &type_integer,          validate_non_negative },
+  { C(rtp_minbuffer),   &type_integer,          validate_non_negative },
+  { C(rtp_mode),         &type_string,           validate_any },
+  { C(rtp_mtu_discovery), &type_string,                 validate_mtu_discovery },
+  { C(rtp_rcvbuf),      &type_integer,          validate_non_negative },
+  { C(rtp_request_address), &type_netaddress,   validate_inetaddr },
+  { C(rtp_verbose),      &type_boolean,          validate_any },
   { C(sample_format),    &type_sample_format,    validate_sample_format },
   { C(scratch),          &type_string_accum,     validate_isreg },
+#if !_WIN32
   { C(sendmail),         &type_string,           validate_isabspath },
+#endif
   { C(short_display),    &type_integer,          validate_positive },
   { C(signal),           &type_signal,           validate_any },
   { C(smtp_server),      &type_string,           validate_any },
   { C(sox_generation),   &type_integer,          validate_non_negative },
+#if !_WIN32
   { C2(speaker_backend, api),  &type_string,     validate_backend },
+#endif
   { C(speaker_command),  &type_string,           validate_any },
   { C(stopword),         &type_string_accum,     validate_any },
   { C(templates),        &type_string_accum,     validate_isdir },
   { C(tracklength),      &type_stringlist_accum, validate_tracklength },
   { C(transform),        &type_transform,        validate_any },
-  { C(trust),            &type_string_accum,     validate_any },
   { C(url),              &type_string,           validate_url },
+#if !_WIN32
   { C(user),             &type_string,           validate_isauser },
+#endif
   { C(username),         &type_string,           validate_any },
 };
 
@@ -1166,6 +1190,7 @@ static int config_set_args(const struct config_state *cs,
   va_list ap;
   struct vector v[1];
   char *s;
+  int rc;
 
   vector_init(v);
   vector_append(v, (char *)which);
@@ -1174,7 +1199,9 @@ static int config_set_args(const struct config_state *cs,
     vector_append(v, s);
   va_end(ap);
   vector_terminate(v);
-  return config_set(cs, v->nvec, v->vec);
+  rc = config_set(cs, v->nvec, v->vec);
+  xfree(v->vec);
+  return rc;
 }
 
 /** @brief Error callback used by config_include()
@@ -1320,8 +1347,10 @@ static const char *const default_players[] = {
  */
 static struct config *config_default(void) {
   struct config *c = xmalloc(sizeof *c);
+#if !_WIN32
   const char *logname;
   struct passwd *pw;
+#endif
   struct config_state cs;
   size_t n;
 
@@ -1329,18 +1358,32 @@ static struct config *config_default(void) {
   cs.line = 0;
   cs.config = c;
   /* Strings had better be xstrdup'd as they will get freed at some point. */
-  c->gap = 0;
   c->history = 60;
+#if !_WIN32
   c->home = xstrdup(pkgstatedir);
+#endif
+#if _WIN32
+  {
+    char buffer[128];
+    DWORD bufsize = sizeof buffer;
+    if(!GetUserNameA(buffer, &bufsize))
+      disorder_fatal(0, "cannot determine our username");
+    c->username = xstrdup(buffer);
+  }
+#else
   if(!(pw = getpwuid(getuid())))
     disorder_fatal(0, "cannot determine our username");
   logname = pw->pw_name;
   c->username = xstrdup(logname);
+#endif
   c->refresh = 15;
-  c->prefsync = 0;
+  c->refresh_min = 1;
+#ifdef SIGKILL
   c->signal = SIGKILL;
+#else
+  c->signal = SIGTERM;
+#endif
   c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
-  c->lock = 1;
   c->device = xstrdup("default");
   c->nice_rescan = 10;
   c->speaker_command = 0;
@@ -1361,8 +1404,10 @@ static struct config *config_default(void) {
   c->dbversion = 2;
   c->cookie_login_lifetime = 86400;
   c->cookie_key_lifetime = 86400 * 7;
+#if !_WIN32
   if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
     c->sendmail = xstrdup(sendmail_binary);
+#endif
   c->smtp_server = xstrdup("127.0.0.1");
   c->new_max = 100;
   c->reminder_interval = 600;          /* 10m */
@@ -1371,6 +1416,7 @@ static struct config *config_default(void) {
   c->sox_generation = DEFAULT_SOX_GENERATION;
   c->playlist_max = INT_MAX;            /* effectively no limit */
   c->playlist_lock_timeout = 10;        /* 10s */
+  c->mount_rescan = 1;
   /* Default stopwords */
   if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
     exit(1);
@@ -1387,9 +1433,13 @@ static struct config *config_default(void) {
   c->broadcast_from.af = -1;
   c->listen.af = -1;
   c->connect.af = -1;
+  c->rtp_mode = xstrdup("auto");
+  c->rtp_max_payload = -1;
+  c->rtp_mtu_discovery = xstrdup("default");
   return c;
 }
 
+#if !_WIN32
 /** @brief Construct a filename
  * @param c Configuration
  * @param name Base filename
@@ -1403,11 +1453,24 @@ char *config_get_file2(struct config *c, const char *name) {
   byte_xasprintf(&s, "%s/%s", c->home, name);
   return s;
 }
+#endif
 
 /** @brief Set the default configuration file */
 static void set_configfile(void) {
-  if(!configfile)
-    byte_xasprintf(&configfile, "%s/config", pkgconfdir);
+  char *t;
+
+#if !_WIN32
+  if(!configfile) {
+    configfile = getenv("DISORDER_CONFIG");
+    if(!configfile)
+      byte_xasprintf(&configfile, "%s/config", pkgconfdir);
+  }
+#endif
+  if(!userconfigfile && config_per_user) {
+    if((t = getenv("DISORDER_USERCONFIG"))) userconfigfile = xstrdup(t);
+    else if(!(userconfigfile = profile_filename("passwd")))
+      disorder_fatal(0, "failed to find user profile directory");
+  }
 }
 
 /** @brief Free a configuration object
@@ -1415,7 +1478,7 @@ static void set_configfile(void) {
  *
  * @p c is indeterminate after this function is called.
  */
-static void config_free(struct config *c) {
+void config_free(struct config *c) {
   int n;
 
   if(c) {
@@ -1477,8 +1540,11 @@ static void config_postdefaults(struct config *c,
       c->api = xstrdup("command");
     else if(c->broadcast.af != -1)
       c->api = xstrdup("rtp");
+#if !_WIN32
     else if(config_uaudio_apis)
-      c->api = xstrdup(config_uaudio_apis[0]->name);
+      c->api = xstrdup(uaudio_default(config_uaudio_apis,
+                                      UAUDIO_API_SERVER)->name);
+#endif
     else
       c->api = xstrdup("<none>");
   }
@@ -1487,8 +1553,10 @@ static void config_postdefaults(struct config *c,
   if(server) {
     if(!strcmp(c->api, "command") && !c->speaker_command)
       disorder_fatal(0, "'api command' but speaker_command is not set");
-    if((!strcmp(c->api, "rtp")) && c->broadcast.af == -1)
-      disorder_fatal(0, "'api rtp' but broadcast is not set");
+    if((!strcmp(c->api, "rtp")) &&
+       c->broadcast.af == -1 && strcmp(c->rtp_mode, "request"))
+      disorder_fatal(0, "'api rtp' but broadcast is not set "
+                    "and mode is not not 'request'");
   }
   /* Override sample format */
   if(!strcmp(c->api, "rtp")) {
@@ -1508,18 +1576,7 @@ static void config_postdefaults(struct config *c,
                                     |RIGHT_MOVE__MASK
                                     |RIGHT_SCRATCH__MASK
                                     |RIGHT_REMOVE__MASK);
-    /* The idea is to approximate the meaning of the old 'restrict' directive
-     * in the default rights if they are not overridden. */
-    if(c->restrictions & RESTRICT_SCRATCH)
-      r |= RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_RANDOM;
-    else
-      r |= RIGHT_SCRATCH_ANY;
-    if(!(c->restrictions & RESTRICT_MOVE))
-      r |= RIGHT_MOVE_ANY;
-    if(c->restrictions & RESTRICT_REMOVE)
-      r |= RIGHT_REMOVE_MINE;
-    else
-      r |= RIGHT_REMOVE_ANY;
+    r |= RIGHT_SCRATCH_ANY|RIGHT_MOVE_ANY|RIGHT_REMOVE_ANY;
     c->default_rights = rights_string(r);
   }
 }
@@ -1536,14 +1593,15 @@ int config_read(int server,
                 const struct config *oldconfig) {
   struct config *c;
   char *privconf;
-  struct passwd *pw;
+  struct passwd *pw = NULL;
 
   set_configfile();
   c = config_default();
-  /* standalone Disobedience installs might not have a global config file */
-  if(access(configfile, F_OK) == 0)
-    if(config_include(c, configfile))
-      return -1;
+  /* standalone client installs might not have a global config file */
+  if(configfile)
+    if(access(configfile, F_OK) == 0)
+      if(config_include(c, configfile))
+        return -1;
   /* if we can read the private config file, do */
   if((privconf = config_private())
      && access(privconf, R_OK) == 0
@@ -1552,6 +1610,7 @@ int config_read(int server,
   xfree(privconf);
   /* if there's a per-user system config file for this user, read it */
   if(config_per_user) {
+#if !_WIN32
     if(!(pw = getpwuid(getuid())))
       disorder_fatal(0, "cannot determine our username");
     if((privconf = config_usersysconf(pw))
@@ -1559,21 +1618,22 @@ int config_read(int server,
        && config_include(c, privconf))
       return -1;
     xfree(privconf);
+#endif
     /* if we have a password file, read it */
-    if((privconf = config_userconf(0, pw))
-       && access(privconf, F_OK) == 0
-       && config_include(c, privconf))
+    if(access(userconfigfile, F_OK) == 0
+       && config_include(c, userconfigfile))
       return -1;
-    xfree(privconf);
   }
   /* install default namepart and transform settings */
   config_postdefaults(c, server);
   if(oldconfig)  {
     int failed = 0;
+#if !_WIN32
     if(strcmp(c->home, oldconfig->home)) {
       disorder_error(0, "'home' cannot be changed without a restart");
       failed = 1;
     }
+#endif
     if(strcmp(c->alias, oldconfig->alias)) {
       disorder_error(0, "'alias' cannot be changed without a restart");
       failed = 1;
@@ -1606,47 +1666,33 @@ int config_read(int server,
   /* everything is good so we shall use the new config */
   config_free(config);
   /* warn about obsolete directives */
-  if(c->restrictions)
-    disorder_error(0, "'restrict' will be removed in a future version");
-  if(c->allow.n)
-    disorder_error(0, "'allow' will be removed in a future version");
-  if(c->trust.n)
-    disorder_error(0, "'trust' will be removed in a future version");
-  if(!c->lock)
-    disorder_error(0, "'lock' will be removed in a future version");
-  if(c->gap)
-    disorder_error(0, "'gap' will be removed in a future version");
-  if(c->prefsync)
-    disorder_error(0, "'prefsync' will be removed in a future version");
   config = c;
   return 0;
 }
 
 /** @brief Return the path to the private configuration file */
 char *config_private(void) {
+#if _WIN32
+  return NULL;
+#else
   char *s;
 
+  if((s = getenv("DISORDER_PRIVCONFIG"))) return xstrdup(s);
   set_configfile();
   byte_xasprintf(&s, "%s.private", configfile);
   return s;
+#endif
 }
 
-/** @brief Return the path to user's personal configuration file */
-char *config_userconf(const char *home, const struct passwd *pw) {
-  char *s;
-
-  if(!home && !pw && !(pw = getpwuid(getuid())))
-    disorder_fatal(0, "cannot determine our username");
-  byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
-  return s;
-}
-
+#if !_WIN32
 /** @brief Return the path to user-specific system configuration */
 char *config_usersysconf(const struct passwd *pw) {
   char *s;
 
   set_configfile();
-  if(!strchr(pw->pw_name, '/')) {
+  if((s = getenv("DISORDER_USERCONFIG_SYS")))
+    return xstrdup(s);
+  else if(!strchr(pw->pw_name, '/')) {
     byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
     return s;
   } else
@@ -1660,6 +1706,7 @@ char *config_usersysconf(const struct passwd *pw) {
 char *config_get_file(const char *name) {
   return config_get_file2(config, name);
 }
+#endif
 
 /** @brief Order two stringlists
  * @param a First stringlist
@@ -1729,6 +1776,20 @@ static int namepartlist_compare(const struct namepartlist *a,
     return 0;
 }
 
+/** @brief Verify configuration table.
+ * @return The number of problems found
+*/
+int config_verify(void) {
+  int fails = 0;
+  size_t n;
+  for(n = 1; n < sizeof conf / sizeof *conf; ++n)
+    if(strcmp(conf[n-1].name, conf[n].name) >= 0) {
+      fprintf(stderr, "%s >= %s\n", conf[n-1].name, conf[n].name);
+      ++fails;
+    }
+  return fails;
+}
+
 /*
 Local Variables:
 c-basic-offset:2