check_target_files_signatures revision 75f1736469d6ebfb51f7d2abfa74039d9be54d8d
175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker#!/usr/bin/env python
275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker#
375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker# Copyright (C) 2009 The Android Open Source Project
475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker#
575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker# Licensed under the Apache License, Version 2.0 (the "License");
675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker# you may not use this file except in compliance with the License.
775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker# You may obtain a copy of the License at
875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker#
975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker#      http://www.apache.org/licenses/LICENSE-2.0
1075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker#
1175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker# Unless required by applicable law or agreed to in writing, software
1275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker# distributed under the License is distributed on an "AS IS" BASIS,
1375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker# See the License for the specific language governing permissions and
1575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker# limitations under the License.
1675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
1775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker"""
1875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug ZongkerCheck the signatures of all APKs in a target_files .zip file.  With
1975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker-c, compare the signatures of each package to the ones in a separate
2075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkertarget_files (usually a previously distributed build for the same
2175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerdevice) and flag any changes.
2275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
2375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug ZongkerUsage:  check_target_file_signatures [flags] target_files
2475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
2575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  -c  (--compare_with)  <other_target_files>
2675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      Look for compatibility problems between the two sets of target
2775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      files (eg., packages whose keys have changed).
2875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
2975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  -l  (--local_cert_dirs)  <dir,dir,...>
3075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      Comma-separated list of top-level directories to scan for
3175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      .x509.pem files.  Defaults to "vendor,build".  Where cert files
3275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      can be found that match APK signatures, the filename will be
3375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      printed as the cert name, otherwise a hash of the cert plus its
3475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      subject string will be printed instead.
3575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
3675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  -t  (--text)
3775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      Dump the certificate information for both packages in comparison
3875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      mode (this output is normally suppressed).
3975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
4075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker"""
4175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
4275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerimport sys
4375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
4475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerif sys.hexversion < 0x02040000:
4575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  print >> sys.stderr, "Python 2.4 or newer is required."
4675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  sys.exit(1)
4775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
4875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerimport os
4975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerimport re
5075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerimport sha
5175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerimport shutil
5275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerimport subprocess
5375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerimport tempfile
5475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerimport zipfile
5575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
5675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerimport common
5775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
5875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker# Work around a bug in python's zipfile module that prevents opening
5975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker# of zipfiles if any entry has an extra field of between 1 and 3 bytes
6075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker# (which is common with zipaligned APKs).  This overrides the
6175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker# ZipInfo._decodeExtra() method (which contains the bug) with an empty
6275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker# version (since we don't need to decode the extra field anyway).
6375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerclass MyZipInfo(zipfile.ZipInfo):
6475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  def _decodeExtra(self):
6575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    pass
6675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerzipfile.ZipInfo = MyZipInfo
6775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
6875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug ZongkerOPTIONS = common.OPTIONS
6975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
7075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug ZongkerOPTIONS.text = False
7175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug ZongkerOPTIONS.compare_with = None
7275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug ZongkerOPTIONS.local_cert_dirs = ("vendor", "build")
7375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
7475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug ZongkerPROBLEMS = []
7575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug ZongkerPROBLEM_PREFIX = []
7675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
7775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerdef AddProblem(msg):
7875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  PROBLEMS.append(" ".join(PROBLEM_PREFIX) + " " + msg)
7975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerdef Push(msg):
8075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  PROBLEM_PREFIX.append(msg)
8175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerdef Pop():
8275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  PROBLEM_PREFIX.pop()
8375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
8475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
8575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerdef Banner(msg):
8675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  print "-" * 70
8775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  print "  ", msg
8875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  print "-" * 70
8975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
9075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
9175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerdef GetCertSubject(cert):
9275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  p = common.Run(["openssl", "x509", "-inform", "DER", "-text"],
9375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                 stdin=subprocess.PIPE,
9475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                 stdout=subprocess.PIPE)
9575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  out, err = p.communicate(cert)
9675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  if err and not err.strip():
9775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    return "(error reading cert subject)"
9875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  for line in out.split("\n"):
9975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    line = line.strip()
10075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    if line.startswith("Subject:"):
10175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      return line[8:].strip()
10275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  return "(unknown cert subject)"
10375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
10475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
10575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerclass CertDB(object):
10675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  def __init__(self):
10775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    self.certs = {}
10875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
10975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  def Add(self, cert, name=None):
11075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    if cert in self.certs:
11175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      if name:
11275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        self.certs[cert] = self.certs[cert] + "," + name
11375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    else:
11475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      if name is None:
11575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        name = "unknown cert %s (%s)" % (sha.sha(cert).hexdigest()[:12],
11675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                                         GetCertSubject(cert))
11775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      self.certs[cert] = name
11875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
11975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  def Get(self, cert):
12075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    """Return the name for a given cert."""
12175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    return self.certs.get(cert, None)
12275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
12375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  def FindLocalCerts(self):
12475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    to_load = []
12575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    for top in OPTIONS.local_cert_dirs:
12675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      for dirpath, dirnames, filenames in os.walk(top):
12775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        certs = [os.path.join(dirpath, i)
12875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                 for i in filenames if i.endswith(".x509.pem")]
12975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        if certs:
13075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          to_load.extend(certs)
13175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
13275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    for i in to_load:
13375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      f = open(i)
13475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      cert = ParseCertificate(f.read())
13575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      f.close()
13675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      name, _ = os.path.splitext(i)
13775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      name, _ = os.path.splitext(name)
13875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      self.Add(cert, name)
13975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
14075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug ZongkerALL_CERTS = CertDB()
14175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
14275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
14375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerdef ParseCertificate(data):
14475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  """Parse a PEM-format certificate."""
14575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  cert = []
14675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  save = False
14775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  for line in data.split("\n"):
14875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    if "--END CERTIFICATE--" in line:
14975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      break
15075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    if save:
15175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      cert.append(line)
15275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    if "--BEGIN CERTIFICATE--" in line:
15375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      save = True
15475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  cert = "".join(cert).decode('base64')
15575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  return cert
15675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
15775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
15875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerdef CertFromPKCS7(data, filename):
15975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  """Read the cert out of a PKCS#7-format file (which is what is
16075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  stored in a signed .apk)."""
16175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  Push(filename + ":")
16275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  try:
16375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    p = common.Run(["openssl", "pkcs7",
16475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                    "-inform", "DER",
16575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                    "-outform", "PEM",
16675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                    "-print_certs"],
16775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                   stdin=subprocess.PIPE,
16875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                   stdout=subprocess.PIPE)
16975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    out, err = p.communicate(data)
17075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    if err and not err.strip():
17175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      AddProblem("error reading cert:\n" + err)
17275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      return None
17375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
17475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    cert = ParseCertificate(out)
17575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    if not cert:
17675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      AddProblem("error parsing cert output")
17775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      return None
17875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    return cert
17975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  finally:
18075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    Pop()
18175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
18275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
18375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerclass APK(object):
18475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  def __init__(self, full_filename, filename):
18575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    self.filename = filename
18675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    self.cert = None
18775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    Push(filename+":")
18875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    try:
18975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      self.RecordCert(full_filename)
19075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      self.ReadManifest(full_filename)
19175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    finally:
19275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      Pop()
19375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
19475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  def RecordCert(self, full_filename):
19575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    try:
19675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      f = open(full_filename)
19775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      apk = zipfile.ZipFile(f, "r")
19875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      pkcs7 = None
19975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      for info in apk.infolist():
20075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        if info.filename.startswith("META-INF/") and \
20175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker           (info.filename.endswith(".DSA") or info.filename.endswith(".RSA")):
20275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          if pkcs7 is not None:
20375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker            AddProblem("multiple certs")
20475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          pkcs7 = apk.read(info.filename)
20575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          self.cert = CertFromPKCS7(pkcs7, info.filename)
20675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          ALL_CERTS.Add(self.cert)
20775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      if not pkcs7:
20875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        AddProblem("no signature")
20975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    finally:
21075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      f.close()
21175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
21275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  def ReadManifest(self, full_filename):
21375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    p = common.Run(["aapt", "dump", "xmltree", full_filename,
21475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                    "AndroidManifest.xml"],
21575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                   stdout=subprocess.PIPE)
21675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    manifest, err = p.communicate()
21775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    if err:
21875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      AddProblem("failed to read manifest")
21975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      return
22075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
22175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    self.shared_uid = None
22275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    self.package = None
22375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
22475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    for line in manifest.split("\n"):
22575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      line = line.strip()
22675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      m = re.search('A: (\S*?)(?:\(0x[0-9a-f]+\))?="(.*?)" \(Raw', line)
22775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      if m:
22875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        name = m.group(1)
22975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        if name == "android:sharedUserId":
23075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          if self.shared_uid is not None:
23175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker            AddProblem("multiple sharedUserId declarations")
23275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          self.shared_uid = m.group(2)
23375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        elif name == "package":
23475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          if self.package is not None:
23575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker            AddProblem("multiple package declarations")
23675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          self.package = m.group(2)
23775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
23875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    if self.package is None:
23975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      AddProblem("no package declaration")
24075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
24175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
24275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerclass TargetFiles(object):
24375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  def __init__(self):
24475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    self.max_pkg_len = 30
24575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    self.max_fn_len = 20
24675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
24775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  def LoadZipFile(self, filename):
24875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    d = common.UnzipTemp(filename, '*.apk')
24975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    try:
25075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      self.apks = {}
25175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      for dirpath, dirnames, filenames in os.walk(d):
25275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        for fn in filenames:
25375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          if fn.endswith(".apk"):
25475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker            fullname = os.path.join(dirpath, fn)
25575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker            displayname = fullname[len(d)+1:]
25675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker            apk = APK(fullname, displayname)
25775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker            self.apks[apk.package] = apk
25875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
25975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker            self.max_pkg_len = max(self.max_pkg_len, len(apk.package))
26075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker            self.max_fn_len = max(self.max_fn_len, len(apk.filename))
26175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    finally:
26275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      shutil.rmtree(d)
26375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
26475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  def CheckSharedUids(self):
26575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    """Look for any instances where packages signed with different
26675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    certs request the same sharedUserId."""
26775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    apks_by_uid = {}
26875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    for apk in self.apks.itervalues():
26975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      if apk.shared_uid:
27075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        apks_by_uid.setdefault(apk.shared_uid, []).append(apk)
27175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
27275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    for uid in sorted(apks_by_uid.keys()):
27375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      apks = apks_by_uid[uid]
27475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      for apk in apks[1:]:
27575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        if apk.cert != apks[0].cert:
27675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          break
27775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      else:
27875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        # all the certs are the same; this uid is fine
27975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        continue
28075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
28175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      AddProblem("uid %s shared across multiple certs" % (uid,))
28275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
28375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      print "uid %s is shared by packages with different certs:" % (uid,)
28475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      x = [(i.cert, i.package, i) for i in apks]
28575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      x.sort()
28675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      lastcert = None
28775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      for cert, _, apk in x:
28875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        if cert != lastcert:
28975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          lastcert = cert
29075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          print "    %s:" % (ALL_CERTS.Get(cert),)
29175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        print "        %-*s  [%s]" % (self.max_pkg_len,
29275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                                      apk.package, apk.filename)
29375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      print
29475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
29575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  def PrintCerts(self):
29675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    """Display a table of packages grouped by cert."""
29775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    by_cert = {}
29875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    for apk in self.apks.itervalues():
29975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      by_cert.setdefault(apk.cert, []).append((apk.package, apk))
30075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
30175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    order = [(-len(v), k) for (k, v) in by_cert.iteritems()]
30275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    order.sort()
30375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
30475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    for _, cert in order:
30575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      print "%s:" % (ALL_CERTS.Get(cert),)
30675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      apks = by_cert[cert]
30775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      apks.sort()
30875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      for _, apk in apks:
30975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        if apk.shared_uid:
31075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          print "  %-*s  %-*s  [%s]" % (self.max_fn_len, apk.filename,
31175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                                        self.max_pkg_len, apk.package,
31275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                                        apk.shared_uid)
31375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        else:
31475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          print "  %-*s  %-*s" % (self.max_fn_len, apk.filename,
31575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                                  self.max_pkg_len, apk.package)
31675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      print
31775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
31875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  def CompareWith(self, other):
31975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    """Look for instances where a given package that exists in both
32075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    self and other have different certs."""
32175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
32275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    all = set(self.apks.keys())
32375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    all.update(other.apks.keys())
32475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
32575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    max_pkg_len = max(self.max_pkg_len, other.max_pkg_len)
32675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
32775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    by_certpair = {}
32875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
32975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    for i in all:
33075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      if i in self.apks:
33175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        if i in other.apks:
33275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          # in both; should have the same cert
33375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          if self.apks[i].cert != other.apks[i].cert:
33475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker            by_certpair.setdefault((other.apks[i].cert,
33575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                                    self.apks[i].cert), []).append(i)
33675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        else:
33775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          print "%s [%s]: new APK (not in comparison target_files)" % (
33875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker              i, self.apks[i].filename)
33975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      else:
34075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        if i in other.apks:
34175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          print "%s [%s]: removed APK (only in comparison target_files)" % (
34275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker              i, other.apks[i].filename)
34375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
34475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    if by_certpair:
34575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      AddProblem("some APKs changed certs")
34675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      Banner("APK signing differences")
34775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      for (old, new), packages in sorted(by_certpair.items()):
34875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        print "was", ALL_CERTS.Get(old)
34975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        print "now", ALL_CERTS.Get(new)
35075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        for i in sorted(packages):
35175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          old_fn = other.apks[i].filename
35275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          new_fn = self.apks[i].filename
35375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          if old_fn == new_fn:
35475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker            print "  %-*s  [%s]" % (max_pkg_len, i, old_fn)
35575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker          else:
35675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker            print "  %-*s  [was: %s; now: %s]" % (max_pkg_len, i,
35775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                                                  old_fn, new_fn)
35875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker        print
35975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
36075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
36175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerdef main(argv):
36275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  def option_handler(o, a):
36375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    if o in ("-c", "--compare_with"):
36475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      OPTIONS.compare_with = a
36575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    elif o in ("-l", "--local_cert_dirs"):
36675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      OPTIONS.local_cert_dirs = [i.strip() for i in a.split(",")]
36775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    elif o in ("-t", "--text"):
36875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      OPTIONS.text = True
36975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    else:
37075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      return False
37175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    return True
37275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
37375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  args = common.ParseOptions(argv, __doc__,
37475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                             extra_opts="c:l:t",
37575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                             extra_long_opts=["compare_with=",
37675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                                              "local_cert_dirs="],
37775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker                             extra_option_handler=option_handler)
37875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
37975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  if len(args) != 1:
38075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    common.Usage(__doc__)
38175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    sys.exit(1)
38275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
38375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  ALL_CERTS.FindLocalCerts()
38475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
38575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  Push("input target_files:")
38675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  try:
38775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    target_files = TargetFiles()
38875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    target_files.LoadZipFile(args[0])
38975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  finally:
39075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    Pop()
39175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
39275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  compare_files = None
39375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  if OPTIONS.compare_with:
39475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    Push("comparison target_files:")
39575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    try:
39675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      compare_files = TargetFiles()
39775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      compare_files.LoadZipFile(OPTIONS.compare_with)
39875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    finally:
39975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      Pop()
40075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
40175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  if OPTIONS.text or not compare_files:
40275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    Banner("target files")
40375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    target_files.PrintCerts()
40475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  target_files.CheckSharedUids()
40575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  if compare_files:
40675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    if OPTIONS.text:
40775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      Banner("comparison files")
40875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      compare_files.PrintCerts()
40975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    target_files.CompareWith(compare_files)
41075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
41175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  if PROBLEMS:
41275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    print "%d problem(s) found:\n" % (len(PROBLEMS),)
41375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    for p in PROBLEMS:
41475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker      print p
41575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    return 1
41675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
41775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  return 0
41875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
41975f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker
42075f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongkerif __name__ == '__main__':
42175f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  try:
42275f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    r = main(sys.argv[1:])
42375f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    sys.exit(r)
42475f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker  except common.ExternalError, e:
42575f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    print
42675f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    print "   ERROR: %s" % (e,)
42775f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    print
42875f1736469d6ebfb51f7d2abfa74039d9be54d8dDoug Zongker    sys.exit(1)
429