diff --git a/vendor/git.autistici.org/ai3/go-common/README.md b/vendor/git.autistici.org/ai3/go-common/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..756a743b6f9008194e37d701fd4a23eb818fee6e
--- /dev/null
+++ b/vendor/git.autistici.org/ai3/go-common/README.md
@@ -0,0 +1,25 @@
+ai3/go-common
+===
+
+Common code for ai3 services and tools.
+
+A quick overview of the contents:
+
+* [client](clientutil/) and [server](serverutil/) HTTP-based
+  "RPC" implementation, just JSON POST requests but with retries,
+  backoff, timeouts, tracing, etc.
+
+* [server implementation of a generic line-based protocol over a UNIX
+  socket](unix/).
+
+* a [LDAP connection pool](ldap/).
+
+* utilities to [serialize composite data types](ldap/compositetypes/)
+  used in our LDAP database.
+
+* a [password hashing library](pwhash/) that uses fancy advanced
+  crypto by default but is also backwards compatible with old
+  libc crypto.
+
+* utilities to [manage encryption keys](userenckey/), themselves
+  encrypted with a password and a KDF.
diff --git a/vendor/git.autistici.org/ai3/go-common/clientutil/balancer.go b/vendor/git.autistici.org/ai3/go-common/clientutil/balancer.go
index f53b68e0313723d4f42eea04d779bf08d446e5c0..d2ca8270fcf27deb3e48e3526a91bb2c8cf004a1 100644
--- a/vendor/git.autistici.org/ai3/go-common/clientutil/balancer.go
+++ b/vendor/git.autistici.org/ai3/go-common/clientutil/balancer.go
@@ -98,43 +98,69 @@ func newBalancedBackend(config *BackendConfig, resolver resolver) (*balancedBack
 // with a JSON-encoded request body. It will attempt to decode the
 // response body as JSON.
 func (b *balancedBackend) Call(ctx context.Context, shard, path string, req, resp interface{}) error {
+	// Serialize the request body.
 	data, err := json.Marshal(req)
 	if err != nil {
 		return err
 	}
 
-	var tg targetGenerator = b.backendTracker
-	if b.sharded {
-		if shard == "" {
-			return fmt.Errorf("call without shard to sharded service %s", b.baseURI.String())
-		}
-		tg = newShardedGenerator(shard, b.baseURI.Host, b.resolver)
+	// Create the target sequence for this call. If there are multiple
+	// targets, reduce the timeout on each individual call accordingly to
+	// accomodate eventual failover.
+	seq, err := b.makeSequence(shard)
+	if err != nil {
+		return err
+	}
+	innerTimeout := 1 * time.Hour
+	if deadline, ok := ctx.Deadline(); ok {
+		innerTimeout = time.Until(deadline) / time.Duration(seq.Len())
 	}
-	seq := newSequence(tg)
-	b.log.Printf("%016x: initialized", seq.ID())
 
-	var httpResp *http.Response
-	err = backoff.Retry(func() error {
+	// Call the backends in the sequence until one succeeds, with an
+	// exponential backoff policy controlled by the outer Context.
+	return backoff.Retry(func() error {
 		req, rerr := b.newJSONRequest(path, shard, data)
 		if rerr != nil {
 			return rerr
 		}
-		httpResp, rerr = b.do(ctx, seq, req)
-		return rerr
+		innerCtx, cancel := context.WithTimeout(ctx, innerTimeout)
+		defer cancel()
+
+		// When do() returns successfully, we already know that the
+		// response had an HTTP status of 200.
+		httpResp, rerr := b.do(innerCtx, seq, req)
+		if rerr != nil {
+			return rerr
+		}
+		defer httpResp.Body.Close() // nolint
+
+		// Decode the response, unless the 'resp' output is nil.
+		if httpResp.Header.Get("Content-Type") != "application/json" {
+			return errors.New("not a JSON response")
+		}
+		if resp == nil {
+			return nil
+		}
+		return json.NewDecoder(httpResp.Body).Decode(resp)
 	}, backoff.WithContext(newExponentialBackOff(), ctx))
-	if err != nil {
-		return err
-	}
-	defer httpResp.Body.Close() // nolint
+}
 
-	if httpResp.Header.Get("Content-Type") != "application/json" {
-		return errors.New("not a JSON response")
+// Initialize a new target sequence.
+func (b *balancedBackend) makeSequence(shard string) (*sequence, error) {
+	var tg targetGenerator = b.backendTracker
+	if b.sharded {
+		if shard == "" {
+			return nil, fmt.Errorf("call without shard to sharded service %s", b.baseURI.String())
+		}
+		tg = newShardedGenerator(shard, b.baseURI.Host, b.resolver)
 	}
 
-	if resp == nil {
-		return nil
+	seq := newSequence(tg)
+	if seq.Len() == 0 {
+		return nil, errNoTargets
 	}
-	return json.NewDecoder(httpResp.Body).Decode(resp)
+	b.log.Printf("%016x: initialized", seq.ID())
+	return seq, nil
 }
 
 // Return the URI to be used for the request. This is used both in the
@@ -213,6 +239,8 @@ func newSequence(tg targetGenerator) *sequence {
 
 func (s *sequence) ID() uint64 { return s.id }
 
+func (s *sequence) Len() int { return len(s.targets) }
+
 func (s *sequence) reloadTargets() {
 	targets := s.tg.getTargets()
 	if len(targets) > 0 {
diff --git a/vendor/git.autistici.org/ai3/go-common/ldap/compositetypes/composite_types.go b/vendor/git.autistici.org/ai3/go-common/ldap/compositetypes/composite_types.go
index 32a1978c066daad0bf8edc452e61f73f8ac5d818..80f136673e52ad7a5e3f57820b91b36e63002d25 100644
--- a/vendor/git.autistici.org/ai3/go-common/ldap/compositetypes/composite_types.go
+++ b/vendor/git.autistici.org/ai3/go-common/ldap/compositetypes/composite_types.go
@@ -103,7 +103,8 @@ func UnmarshalEncryptedKey(s string) (*EncryptedKey, error) {
 //
 // The serialized format follows part of the U2F standard and just
 // stores 64 bytes of the public key immediately followed by the key
-// handle data, with no encoding.
+// handle data, with no encoding. Note that the public key itself is a
+// serialization of the elliptic curve parameters.
 //
 // The data in U2FRegistration is still encoded, but it can be turned
 // into a usable form (github.com/tstranex/u2f.Registration) later.
@@ -122,13 +123,13 @@ func (r *U2FRegistration) Marshal() string {
 
 // UnmarshalU2FRegistration parses a U2FRegistration from its serialized format.
 func UnmarshalU2FRegistration(s string) (*U2FRegistration, error) {
-	if len(s) < 64 {
+	if len(s) < 65 {
 		return nil, errors.New("badly encoded u2f registration")
 	}
 	b := []byte(s)
 	return &U2FRegistration{
-		PublicKey: b[:64],
-		KeyHandle: b[64:],
+		PublicKey: b[:65],
+		KeyHandle: b[65:],
 	}, nil
 }
 
diff --git a/vendor/git.autistici.org/ai3/go-common/tracing/tracing.go b/vendor/git.autistici.org/ai3/go-common/tracing/tracing.go
index 544ca1a0a0d917e47e7f06af2b75915fe4530f3b..0cd132b82e0de462fa45ced20d2f3da0d1176f26 100644
--- a/vendor/git.autistici.org/ai3/go-common/tracing/tracing.go
+++ b/vendor/git.autistici.org/ai3/go-common/tracing/tracing.go
@@ -93,6 +93,9 @@ func init() {
 }
 
 func initTracing(endpointAddr string) {
+	if !Enabled {
+		return
+	}
 	initOnce.Do(func() {
 		localEndpoint, err := openzipkin.NewEndpoint(getServiceName(), endpointAddr)
 		if err != nil {
@@ -126,6 +129,11 @@ func initTracing(endpointAddr string) {
 	})
 }
 
+// Init tracing support, if not using WrapHandler.
+func Init() {
+	initTracing("")
+}
+
 // WrapTransport optionally wraps a http.RoundTripper with OpenCensus
 // tracing functionality, if it is globally enabled.
 func WrapTransport(t http.RoundTripper) http.RoundTripper {
@@ -136,7 +144,7 @@ func WrapTransport(t http.RoundTripper) http.RoundTripper {
 }
 
 // WrapHandler wraps a http.Handler with OpenCensus tracing
-// functionality, if globally enabled.
+// functionality, if globally enabled. Automatically calls Init().
 func WrapHandler(h http.Handler, endpointAddr string) http.Handler {
 	if Enabled {
 		initTracing(endpointAddr)
diff --git a/vendor/git.autistici.org/ai3/go-common/unix/server.go b/vendor/git.autistici.org/ai3/go-common/unix/server.go
index b5d92e1ca61517865f194d0b97dbfa96b99a3168..381d1415e90a705975eb84d035380ad66755667c 100644
--- a/vendor/git.autistici.org/ai3/go-common/unix/server.go
+++ b/vendor/git.autistici.org/ai3/go-common/unix/server.go
@@ -2,6 +2,7 @@ package unix
 
 import (
 	"bufio"
+	"container/list"
 	"context"
 	"errors"
 	"io"
@@ -32,6 +33,11 @@ type SocketServer struct {
 	closing atomic.Value
 	wg      sync.WaitGroup
 	handler Handler
+
+	// Keep track of active connections so we can shut them down
+	// on Close.
+	connMx sync.Mutex
+	conns  list.List
 }
 
 func newServer(l net.Listener, lock *flock.Flock, h Handler) *SocketServer {
@@ -99,7 +105,18 @@ func NewSystemdSocketServer(h Handler) (*SocketServer, error) {
 // Waits for active connections to terminate before returning.
 func (s *SocketServer) Close() {
 	s.closing.Store(true)
+
+	// Close the listener to stop incoming connections.
 	s.l.Close() // nolint
+
+	// Close all active connections (this will return an error to
+	// the client if the connection is not idle).
+	s.connMx.Lock()
+	for el := s.conns.Front(); el != nil; el = el.Next() {
+		el.Value.(net.Conn).Close() // nolint
+	}
+	s.connMx.Unlock()
+
 	s.wg.Wait()
 	if s.lock != nil {
 		s.lock.Unlock() // nolint
@@ -120,10 +137,21 @@ func (s *SocketServer) Serve() error {
 			}
 			return err
 		}
+
 		s.wg.Add(1)
+
+		s.connMx.Lock()
+		connEl := s.conns.PushBack(conn)
+		s.connMx.Unlock()
+
 		go func() {
 			s.handler.ServeConnection(conn)
 			conn.Close() // nolint
+			if !s.isClosing() {
+				s.connMx.Lock()
+				s.conns.Remove(connEl)
+				s.connMx.Unlock()
+			}
 			s.wg.Done()
 		}()
 	}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 2dc6d90d542cd459357a6645e07a63abe53d08a7..14d3789a735011052263e3aea360474ffca7a306 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -3,52 +3,52 @@
 	"ignore": "test",
 	"package": [
 		{
-			"checksumSHA1": "pLvPnUablirQucyALgrso9hLG4E=",
+			"checksumSHA1": "oUOxU+Tw1/jOzWVP05HuGvVSC/A=",
 			"path": "git.autistici.org/ai3/go-common",
-			"revision": "438dda6c699e73e612d0b16143076998d77c03ac",
-			"revisionTime": "2018-11-22T23:09:18Z"
+			"revision": "54f0ac4c46184ae44486a31ca2705076abcc5321",
+			"revisionTime": "2019-06-30T08:30:15Z"
 		},
 		{
-			"checksumSHA1": "1ChQcW9Biu/AgiKjsbJFg/+WhjQ=",
+			"checksumSHA1": "kJwm6y9JXhybelO2zUl7UbzIdP0=",
 			"path": "git.autistici.org/ai3/go-common/clientutil",
-			"revision": "438dda6c699e73e612d0b16143076998d77c03ac",
-			"revisionTime": "2018-11-22T23:09:18Z"
+			"revision": "54f0ac4c46184ae44486a31ca2705076abcc5321",
+			"revisionTime": "2019-06-30T08:30:15Z"
 		},
 		{
 			"checksumSHA1": "d8aQcSXveyjPfFJgfB8NnM+x8dg=",
 			"path": "git.autistici.org/ai3/go-common/ldap",
-			"revision": "2934fd63c275d37b0fe60afabb484a251662bd49",
-			"revisionTime": "2019-02-17T09:01:06Z"
+			"revision": "54f0ac4c46184ae44486a31ca2705076abcc5321",
+			"revisionTime": "2019-06-30T08:30:15Z"
 		},
 		{
-			"checksumSHA1": "X14iCbFCOfaIai/TPi4VJ/OBZjc=",
+			"checksumSHA1": "ETt1H7ZXeT+mOGVuWDvgGBVx98k=",
 			"path": "git.autistici.org/ai3/go-common/ldap/compositetypes",
-			"revision": "301958e3493e263eb6ea269bf7b8644fbcd97394",
-			"revisionTime": "2019-03-21T10:42:03Z"
+			"revision": "54f0ac4c46184ae44486a31ca2705076abcc5321",
+			"revisionTime": "2019-06-30T08:30:15Z"
 		},
 		{
 			"checksumSHA1": "TKGUNmKxj7KH3qhwiCh/6quUnwc=",
 			"path": "git.autistici.org/ai3/go-common/serverutil",
-			"revision": "438dda6c699e73e612d0b16143076998d77c03ac",
-			"revisionTime": "2018-11-22T23:09:18Z"
+			"revision": "54f0ac4c46184ae44486a31ca2705076abcc5321",
+			"revisionTime": "2019-06-30T08:30:15Z"
 		},
 		{
-			"checksumSHA1": "tWzCSEieYFl5VnKhFTosgveO6Ys=",
+			"checksumSHA1": "y5pRYZ/NhfEOCFslPEuUZTYXcro=",
 			"path": "git.autistici.org/ai3/go-common/tracing",
-			"revision": "438dda6c699e73e612d0b16143076998d77c03ac",
-			"revisionTime": "2018-11-22T23:09:18Z"
+			"revision": "54f0ac4c46184ae44486a31ca2705076abcc5321",
+			"revisionTime": "2019-06-30T08:30:15Z"
 		},
 		{
-			"checksumSHA1": "R18oCPkWjuPqDxPgKvG1KhiSJns=",
+			"checksumSHA1": "jRc0JfRUtCr3xxkgwRDVppsSnl0=",
 			"path": "git.autistici.org/ai3/go-common/unix",
-			"revision": "438dda6c699e73e612d0b16143076998d77c03ac",
-			"revisionTime": "2018-11-22T23:09:18Z"
+			"revision": "54f0ac4c46184ae44486a31ca2705076abcc5321",
+			"revisionTime": "2019-06-30T08:30:15Z"
 		},
 		{
 			"checksumSHA1": "witSYnNsDhNaoA85UYilt17H+ng=",
 			"path": "git.autistici.org/ai3/go-common/userenckey",
-			"revision": "438dda6c699e73e612d0b16143076998d77c03ac",
-			"revisionTime": "2018-11-22T23:09:18Z"
+			"revision": "54f0ac4c46184ae44486a31ca2705076abcc5321",
+			"revisionTime": "2019-06-30T08:30:15Z"
 		},
 		{
 			"checksumSHA1": "MszadHmYMr3JQMX2gRg7TfsQWVc=",