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..534838bfb168f391f01354ac1d74d976bd66489b
--- /dev/null
+++ b/vendor/git.autistici.org/ai3/go-common/README.md
@@ -0,0 +1,22 @@
+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 line-based protocol over a UNIX socket](unix/)
+
+* a [LDAP connection pool](ldap/)
+
+* 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..84633ac271887a35a7575805f8a6633b983949af 100644
--- a/vendor/git.autistici.org/ai3/go-common/clientutil/balancer.go
+++ b/vendor/git.autistici.org/ai3/go-common/clientutil/balancer.go
@@ -98,28 +98,35 @@ 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())
 
+	// Call the backends in the sequence until one succeeds, with an
+	// exponential backoff policy controlled by the outer Context.
 	var httpResp *http.Response
 	err = backoff.Retry(func() error {
 		req, rerr := b.newJSONRequest(path, shard, data)
 		if rerr != nil {
 			return rerr
 		}
-		httpResp, rerr = b.do(ctx, seq, req)
+		innerCtx, cancel := context.WithTimeout(ctx, innerTimeout)
+		httpResp, rerr = b.do(innerCtx, seq, req)
+		cancel()
 		return rerr
 	}, backoff.WithContext(newExponentialBackOff(), ctx))
 	if err != nil {
@@ -127,16 +134,34 @@ func (b *balancedBackend) Call(ctx context.Context, shard, path string, req, res
 	}
 	defer httpResp.Body.Close() // nolint
 
+	// Decode the response.
 	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)
 }
 
+// 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)
+	}
+
+	seq := newSequence(tg)
+	if seq.Len() == 0 {
+		return nil, errNoTargets
+	}
+	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
 // Host HTTP header and as the TLS server name used to pick a server
 // certificate (if using TLS).
@@ -213,6 +238,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/tracing/tracing.go b/vendor/git.autistici.org/ai3/go-common/tracing/tracing.go
index df6144b7dd054b4dfde6640d95351f872050275a..0cd132b82e0de462fa45ced20d2f3da0d1176f26 100644
--- a/vendor/git.autistici.org/ai3/go-common/tracing/tracing.go
+++ b/vendor/git.autistici.org/ai3/go-common/tracing/tracing.go
@@ -8,6 +8,7 @@ import (
 	"net/http"
 	"os"
 	"path/filepath"
+	"strconv"
 	"sync"
 
 	openzipkin "github.com/openzipkin/zipkin-go"
@@ -31,6 +32,7 @@ const globalTracingConfigPath = "/etc/tracing/client.conf"
 
 type tracingConfig struct {
 	ReportURL string `json:"report_url"`
+	Sample    string `json:"sample"`
 }
 
 // Read the global tracing configuration file. Its location is
@@ -91,6 +93,9 @@ func init() {
 }
 
 func initTracing(endpointAddr string) {
+	if !Enabled {
+		return
+	}
 	initOnce.Do(func() {
 		localEndpoint, err := openzipkin.NewEndpoint(getServiceName(), endpointAddr)
 		if err != nil {
@@ -100,9 +105,23 @@ func initTracing(endpointAddr string) {
 
 		reporter := zipkinHTTP.NewReporter(config.ReportURL)
 		ze := zipkin.NewExporter(reporter, localEndpoint)
-
 		trace.RegisterExporter(ze)
-		trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
+
+		var tc trace.Config
+		switch config.Sample {
+		case "", "always":
+			tc.DefaultSampler = trace.AlwaysSample()
+		case "never":
+			tc.DefaultSampler = trace.NeverSample()
+		default:
+			frac, err := strconv.ParseFloat(config.Sample, 64)
+			if err != nil {
+				log.Printf("warning: error in tracing configuration: sample: %v, tracing disabled", err)
+				return
+			}
+			tc.DefaultSampler = trace.ProbabilitySampler(frac)
+		}
+		trace.ApplyConfig(tc)
 
 		log.Printf("tracing enabled (report_url %s)", config.ReportURL)
 
@@ -110,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 {
@@ -120,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/vendor.json b/vendor/vendor.json
index a62ab612faa86ff1c972a54961baaf358e915715..cfdc0f14c72981366f5b98f84fee045400922bd9 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -3,28 +3,28 @@
 	"ignore": "test",
 	"package": [
 		{
-			"checksumSHA1": "pLvPnUablirQucyALgrso9hLG4E=",
+			"checksumSHA1": "mqNsLVty/oAcBDXv2DZeqMtGeaY=",
 			"path": "git.autistici.org/ai3/go-common",
-			"revision": "1f95fcdd58ebf63d338f05ceae29d2de811a2d2f",
-			"revisionTime": "2018-11-18T16:11:30Z"
+			"revision": "0868f2647fefb9c3855bb6242aae8aab6d0ddc6d",
+			"revisionTime": "2019-01-29T12:17:45Z"
 		},
 		{
-			"checksumSHA1": "1ChQcW9Biu/AgiKjsbJFg/+WhjQ=",
+			"checksumSHA1": "hKJhn/0mTkaYIHwFTy+W9TLr09M=",
 			"path": "git.autistici.org/ai3/go-common/clientutil",
-			"revision": "1f95fcdd58ebf63d338f05ceae29d2de811a2d2f",
-			"revisionTime": "2018-11-18T16:11:30Z"
+			"revision": "0868f2647fefb9c3855bb6242aae8aab6d0ddc6d",
+			"revisionTime": "2019-01-29T12:17:45Z"
 		},
 		{
 			"checksumSHA1": "TKGUNmKxj7KH3qhwiCh/6quUnwc=",
 			"path": "git.autistici.org/ai3/go-common/serverutil",
-			"revision": "1f95fcdd58ebf63d338f05ceae29d2de811a2d2f",
-			"revisionTime": "2018-11-18T16:11:30Z"
+			"revision": "0868f2647fefb9c3855bb6242aae8aab6d0ddc6d",
+			"revisionTime": "2019-01-29T12:17:45Z"
 		},
 		{
-			"checksumSHA1": "WvuSF0pz3rk7bu+5g9lqTqq97Ow=",
+			"checksumSHA1": "y5pRYZ/NhfEOCFslPEuUZTYXcro=",
 			"path": "git.autistici.org/ai3/go-common/tracing",
-			"revision": "1f95fcdd58ebf63d338f05ceae29d2de811a2d2f",
-			"revisionTime": "2018-11-18T16:11:30Z"
+			"revision": "0868f2647fefb9c3855bb6242aae8aab6d0ddc6d",
+			"revisionTime": "2019-01-29T12:17:45Z"
 		},
 		{
 			"checksumSHA1": "6D5Xt9WoGSeTJE3XFw6P2/nKYrQ=",