Merge "Allow correct interpretation of ANDROID_ACTIVE_PARTITION"
diff --git a/bouncer/Makefile b/bouncer/Makefile
index bda3d81..db66253 100644
--- a/bouncer/Makefile
+++ b/bouncer/Makefile
@@ -1,6 +1,8 @@
 default: all
 
+INSTALL?=install
 BINDIR=$(DESTDIR)/bin
+LIBDIR=$(DESTDIR)/usr/bouncer
 GPYLINT=$(shell \
     if which gpylint >/dev/null; then \
       echo gpylint; \
@@ -8,26 +10,21 @@
       echo 'echo "(gpylint-missing)" >&2'; \
     fi \
 )
+NOINSTALL=options.py
 
-TARGETS=authorizer hash_mac_addr http_bouncer
-
-HOST_TARGETS=$(addprefix host-,$(TARGETS))
-
-all: $(TARGETS) $(HOST_TARGETS)
+all:
 
 install:
-	mkdir -p $(BINDIR)
-	cp $(TARGETS) $(BINDIR)
+	mkdir -p $(LIBDIR) $(BINDIR)
+	$(INSTALL) -m 0644 $(filter-out $(NOINSTALL) $(TARGETS), $(wildcard *.py)) $(LIBDIR)/
+	for t in authorizer hash_mac_addr http_bouncer; do \
+		$(INSTALL) -m 0755 $$t.py $(LIBDIR)/; \
+		ln -fs /usr/bouncer/$$t.py $(BINDIR)/$$t; \
+	done
 
 install-libs:
 	@echo "No libs to install."
 
-%: %.py
-	ln -s $< $@
-
-host-%: %.py
-	ln -s $< $@
-
 TESTS = $(wildcard test-*.sh) $(wildcard test-*.py) $(wildcard *_test.py)
 runtests: all $(TESTS)
 	set -e; \
diff --git a/bouncer/authorizer.py b/bouncer/authorizer.py
index f355917..0c5e0b1 100755
--- a/bouncer/authorizer.py
+++ b/bouncer/authorizer.py
@@ -43,6 +43,7 @@
 
 MAX_TRIES = 300
 
+in_progress_users = {}
 known_users = {}
 
 
@@ -55,13 +56,22 @@
   return x | y
 
 
+def is_valid_acceptance(response_obj):
+  accepted_time = response_obj.get('accepted')
+  return accepted_time + (opt.max_age * 86400) > time.time()
+
+
+def allow_mac_rule(mac_addr):
+  # iptables, unlike other Linux utilities, capitalizes MAC addresses
+  return ('-m', 'mac', '--mac-source', mac_addr.upper(), '-j', 'ACCEPT')
+
+
 class Checker(object):
   """Manage checking and polling for Terms of Service acceptance."""
 
-  def __init__(self, mac_addr, hashed_mac_addr, url):
+  def __init__(self, mac_addr, url):
     self.mac_addr = mac_addr
-    self.hashed_mac_addr = hashed_mac_addr
-    self.url = url % {'mac': hashed_mac_addr}
+    self.url = url % {'mac': hash_mac_addr.hash_mac_addr(self.mac_addr)}
     self.tries = 0
     self.callback = None
 
@@ -69,41 +79,43 @@
     """Check if a remote service knows about a device with a supplied MAC."""
     logging.info('Checking TOS for %s', self.mac_addr)
     http_client = tornado.httpclient.HTTPClient()
-    response = http_client.fetch(self.url, ca_certs=opt.ca_certs)
-    response_obj = tornado.escape.json_decode(response.body)
-    accepted_time = response_obj.get('accepted')
     self.tries += 1
 
-    accepted = False
-    if accepted_time:
-      if accepted_time + (opt.max_age * 86400) > time.time():
-        accepted = True
-        if self.callback: self.callback.stop()
-        logging.info('TOS accepted for %s', self.mac_addr)
+    try:
+      response = http_client.fetch(self.url, ca_certs=opt.ca_certs)
+      response_obj = tornado.escape.json_decode(response.body)
+      valid = is_valid_acceptance(response_obj)
+    except tornado.httpclient.HTTPError as e:
+      logging.warning('Error checking authorization: %r', e)
+      valid = False
 
-        known_users[self.mac_addr] = response_obj
-        result = ip46tables('-A', opt.filter_chain, '-m', 'mac',
-                            '--mac-source', self.mac_addr, '-j', 'ACCEPT')
-        result |= ip46tables('-t', 'nat', '-A', opt.nat_chain, '-m', 'mac',
-                             '--mac-source', self.mac_addr, '-j', 'ACCEPT')
-        if result:
-          logging.error('Could not update firewall for device %s',
-                        self.mac_addr)
-      else:
-        logging.info('TOS accepted too long ago for %s: %r',
-                     self.mac_addr, accepted_time)
+    if valid:
+      logging.info('TOS accepted for %s', self.mac_addr)
 
-    elif self.callback and self.tries > MAX_TRIES:
-      if not accepted:
-        logging.info('TOS not accepted for %s before timeout.',
-                     self.mac_addr)
-      self.callback.stop()
+      known_users[self.mac_addr] = response_obj
+      result = ip46tables('-A', opt.filter_chain,
+                          *allow_mac_rule(self.mac_addr))
+      result |= ip46tables('-t', 'nat', '-A', opt.nat_chain,
+                           *allow_mac_rule(self.mac_addr))
+      if result:
+        logging.error('Could not update firewall for device %s',
+                      self.mac_addr)
 
-    return response, accepted
+    if valid or self.tries > MAX_TRIES:
+      if self.callback:
+        self.callback.stop()
+      if self.mac_addr in in_progress_users:
+        del in_progress_users[self.mac_addr]
+    else:
+      in_progress_users[self.mac_addr] = self
+      self.poll()
+
+    return response
 
   def poll(self):
-    self.callback = tornado.ioloop.PeriodicCallback(self.check, 1000)
-    self.callback.start()
+    if not self.callback:
+      self.callback = tornado.ioloop.PeriodicCallback(self.check, 1000)
+      self.callback.start()
 
 
 def accept(connection, unused_address):
@@ -112,27 +124,46 @@
 
   maybe_mac_addr = cf.readline().strip()
   try:
-    mac_addr, hashed_mac_addr = hash_mac_addr.hash_mac_addr(maybe_mac_addr)
+    mac_addr = hash_mac_addr.normalize_mac_addr(maybe_mac_addr)
   except ValueError:
     logging.warning('can only check authorization for a MAC address.')
     cf.write('{}')
     return
 
   if mac_addr in known_users:
-    logging.info('TOS accepted (cached) for %s', mac_addr)
     cached_response = known_users[mac_addr]
-    cached_response['cached'] = True
-    cf.write(tornado.escape.json_encode(cached_response))
-    return
+    if is_valid_acceptance(cached_response):
+      logging.info('TOS accepted (cached) for %s', mac_addr)
+      cached_response['cached'] = True
+      cf.write(tornado.escape.json_encode(cached_response))
+      return
 
-  checker = Checker(mac_addr, hashed_mac_addr, opt.url)
-  response, accepted = checker.check()
-  if not accepted:
-    checker.poll()
+  if mac_addr in in_progress_users:
+    checker = in_progress_users[mac_addr]
+  else:
+    checker = Checker(mac_addr, opt.url)
 
+  response = checker.check()
   cf.write(response.body)
 
 
+def expire_cache():
+  """Remove users whose authorization has expired from the cache."""
+  expired_users = set(mac_addr for mac_addr, cached_response
+                      in known_users.items()
+                      if not is_valid_acceptance(cached_response))
+
+  for mac_addr in expired_users:
+    logging.info('Removing expired user %s', mac_addr)
+    del known_users[mac_addr]
+
+    result = ip46tables('-D', opt.filter_chain, *allow_mac_rule(mac_addr))
+    result |= ip46tables('-t', 'nat', '-D', opt.nat_chain,
+                         *allow_mac_rule(mac_addr))
+    if result:
+      logging.warning('Error removing expired user %s !', mac_addr)
+
+
 if __name__ == '__main__':
   o = options.Options(optspec)
   opt, flags, extra = o.parse(sys.argv[1:])
@@ -157,3 +188,6 @@
   logging.info('Started authorizer.')
   ioloop.start()
 
+  expirer = tornado.ioloop.PeriodicCallback(expire_cache, 60 * 60 * 1000)
+  expirer.start()
+
diff --git a/bouncer/hash_mac_addr.py b/bouncer/hash_mac_addr.py
index 961bf10..23b45d1 100755
--- a/bouncer/hash_mac_addr.py
+++ b/bouncer/hash_mac_addr.py
@@ -15,13 +15,16 @@
 """
 
 
-def hash_mac_addr(maybe_mac_addr):
+def normalize_mac_addr(maybe_mac_addr):
   if re.match('([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$', maybe_mac_addr):
-    mac_addr = maybe_mac_addr.lower()
+    return maybe_mac_addr.lower()
   else:
     raise ValueError('%r not a MAC address' % maybe_mac_addr)
 
-  return mac_addr, hashlib.sha1(mac_addr).hexdigest()
+
+def hash_mac_addr(maybe_mac_addr):
+  mac_addr = normalize_mac_addr(maybe_mac_addr)
+  return hashlib.sha1(mac_addr).hexdigest()
 
 
 if __name__ == '__main__':
@@ -32,7 +35,7 @@
     o.usage()
 
   try:
-    _, hashed_mac_addr = hash_mac_addr(str(opt.addr))
+    hashed_mac_addr = hash_mac_addr(str(opt.addr))
     print hashed_mac_addr
   except ValueError as e:
     print >>sys.stderr, 'error:', e.message
diff --git a/bouncer/http_bouncer.py b/bouncer/http_bouncer.py
index 380da4b..5fe0a53 100755
--- a/bouncer/http_bouncer.py
+++ b/bouncer/http_bouncer.py
@@ -68,8 +68,7 @@
     else:
       if self.substitute_mac:
         mac = mac_for_ip(self.request.remote_ip)
-        _, hashed_mac = hash_mac_addr.hash_mac_addr(mac)
-        self.redirect(opt.url % {'mac': hashed_mac})
+        self.redirect(opt.url % {'mac': hash_mac_addr.hash_mac_addr(mac)})
 
         if opt.unix_path:
           try:
diff --git a/bouncer/test-hash_mac_addr.sh b/bouncer/test-hash_mac_addr.sh
index 3c84424..db6f10b 100755
--- a/bouncer/test-hash_mac_addr.sh
+++ b/bouncer/test-hash_mac_addr.sh
@@ -4,7 +4,7 @@
 
 WVSTART "hash_mac_addr test"
 
-HASH_MAC_ADDR=./host-hash_mac_addr
+HASH_MAC_ADDR=./hash_mac_addr.py
 
 WVFAIL $HASH_MAC_ADDR
 WVFAIL $HASH_MAC_ADDR -a nonsense
diff --git a/bouncer/test-http_bouncer.sh b/bouncer/test-http_bouncer.sh
index dedd742..b5bd72a 100755
--- a/bouncer/test-http_bouncer.sh
+++ b/bouncer/test-http_bouncer.sh
@@ -5,7 +5,7 @@
 
 . ./wvtest/wvtest.sh
 
-HTTP_BOUNCER=./host-http_bouncer
+HTTP_BOUNCER=./http_bouncer.py
 PORT="1337"
 URL="http://example.com"
 
diff --git a/conman/connection_manager.py b/conman/connection_manager.py
index 8307efd..c27ef8d 100755
--- a/conman/connection_manager.py
+++ b/conman/connection_manager.py
@@ -620,9 +620,6 @@
       route = ifc.current_routes().get('default', None)
       if route:
         metric = route.get('metric', 0)
-        # Skip temporary connection_check routes.
-        if metric == '99':
-          continue
         candidate = (metric, ifc)
         if (lowest_metric_interface is None or
             candidate < lowest_metric_interface):
diff --git a/logupload/client/debian/init b/logupload/client/debian/init
index b349b5e..bbf7cad 100755
--- a/logupload/client/debian/init
+++ b/logupload/client/debian/init
@@ -36,10 +36,11 @@
     # Our hostnames are something like hostname.cluster.whatever.com.
     # We want the hostname.cluster part to be part of the certname, but
     # sadly the name field in our certs isn't super happy about that, so
-    # let's use _ instead of dot, where relevant.
+    # let's use _ instead of dot and strip hyphens, where relevant.
     hostname -f |
       sed -e 's/\([^.]*\.[^.]*\).*/\1/' \
-          -e 's/\./_/g' |
+          -e 's/\./_/g' \
+          -e 's/-//g' |
       atomic_stdin /tmp/serial
     cd /
     upload-logs-loop </dev/null &