]> git.decadent.org.uk Git - dak.git/blobdiff - daklib/utils.py
Merge key-auto-fetch branch.
[dak.git] / daklib / utils.py
index a32b0c468d023f74e48d841e92c8f18b897a2178..230391a192a2e046b1ead07b38a722182270a279 100644 (file)
@@ -865,10 +865,80 @@ def gpgv_get_status_output(cmd, status_read, status_write):
 
     return output, status, exit_status
 
 
     return output, status, exit_status
 
-############################################################
+################################################################################
 
 
+def process_gpgv_output(status):
+    # Process the status-fd output
+    keywords = {}
+    internal_error = ""
+    for line in status.split('\n'):
+        line = line.strip()
+        if line == "":
+            continue
+        split = line.split()
+        if len(split) < 2:
+            internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
+            continue
+        (gnupg, keyword) = split[:2]
+        if gnupg != "[GNUPG:]":
+            internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
+            continue
+        args = split[2:]
+        if keywords.has_key(keyword) and (keyword != "NODATA" and keyword != "SIGEXPIRED"):
+            internal_error += "found duplicate status token ('%s').\n" % (keyword)
+            continue
+        else:
+            keywords[keyword] = args
+
+    return (keywords, internal_error)
+
+################################################################################
+
+def retrieve_key (filename, keyserver=None, keyring=None):
+    """Retrieve the key that signed 'filename' from 'keyserver' and
+add it to 'keyring'.  Returns nothing on success, or an error message
+on error."""
+
+    # Defaults for keyserver and keyring
+    if not keyserver:
+        keyserver = Cnf["Dinstall::KeyServer"]
+    if not keyring:
+        keyring = Cnf["Dinstall::GPGKeyring"]
+
+    # Ensure the filename contains no shell meta-characters or other badness
+    if not re_taint_free.match(filename):
+        return "%s: tainted filename" % (filename)
+
+    # Invoke gpgv on the file
+    status_read, status_write = os.pipe(); 
+    cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
+    (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
 
 
-def check_signature (sig_filename, reject, data_filename="", keyrings=None):
+    # Process the status-fd output
+    (keywords, internal_error) = process_gpgv_output(status)
+    if internal_error:
+        return internal_error
+
+    if not keywords.has_key("NO_PUBKEY"):
+        return "didn't find expected NO_PUBKEY in gpgv status-fd output"
+
+    fingerprint = keywords["NO_PUBKEY"][0]
+    # XXX - gpg sucks.  You can't use --secret-keyring=/dev/null as
+    # it'll try to create a lockfile in /dev.  A better solution might
+    # be a tempfile or something.
+    cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
+          % (Cnf["Dinstall::SigningKeyring"])
+    cmd += " --keyring %s --keyserver %s --recv-key %s" \
+           % (keyring, keyserver, fingerprint)
+    (result, output) = commands.getstatusoutput(cmd)
+    if (result != 0):
+        return "'%s' failed with exit code %s" % (cmd, result)
+
+    return ""
+
+################################################################################
+
+def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
     """Check the signature of a file and return the fingerprint if the
 signature is valid or 'None' if it's not.  The first argument is the
 filename whose signature should be checked.  The second argument is a
     """Check the signature of a file and return the fingerprint if the
 signature is valid or 'None' if it's not.  The first argument is the
 filename whose signature should be checked.  The second argument is a
@@ -878,8 +948,9 @@ the second is an optional prefix string.  It's possible for reject()
 to be called more than once during an invocation of check_signature().
 The third argument is optional and is the name of the files the
 detached signature applies to.  The fourth argument is optional and is
 to be called more than once during an invocation of check_signature().
 The third argument is optional and is the name of the files the
 detached signature applies to.  The fourth argument is optional and is
-a *list* of keyrings to use.
-"""
+a *list* of keyrings to use.  'autofetch' can either be None, True or
+False.  If None, the default behaviour specified in the config will be
+used."""
 
     # Ensure the filename contains no shell meta-characters or other badness
     if not re_taint_free.match(sig_filename):
 
     # Ensure the filename contains no shell meta-characters or other badness
     if not re_taint_free.match(sig_filename):
@@ -893,6 +964,15 @@ a *list* of keyrings to use.
     if not keyrings:
         keyrings = (Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"])
 
     if not keyrings:
         keyrings = (Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"])
 
+    # Autofetch the signing key if that's enabled
+    if autofetch == None:
+        autofetch = Cnf.get("Dinstall::KeyAutoFetch")
+    if autofetch:
+        error_msg = retrieve_key(sig_filename)
+        if error_msg:
+            reject(error_msg)
+            return None
+
     # Build the command line
     status_read, status_write = os.pipe(); 
     cmd = "gpgv --status-fd %s" % (status_write)
     # Build the command line
     status_read, status_write = os.pipe(); 
     cmd = "gpgv --status-fd %s" % (status_write)
@@ -903,26 +983,7 @@ a *list* of keyrings to use.
     (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
 
     # Process the status-fd output
     (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
 
     # Process the status-fd output
-    keywords = {}
-    bad = internal_error = ""
-    for line in status.split('\n'):
-        line = line.strip()
-        if line == "":
-            continue
-        split = line.split()
-        if len(split) < 2:
-            internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
-            continue
-        (gnupg, keyword) = split[:2]
-        if gnupg != "[GNUPG:]":
-            internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
-            continue
-        args = split[2:]
-        if keywords.has_key(keyword) and (keyword != "NODATA" and keyword != "SIGEXPIRED"):
-            internal_error += "found duplicate status token ('%s').\n" % (keyword)
-            continue
-        else:
-            keywords[keyword] = args
+    (keywords, internal_error) = process_gpgv_output(status)
 
     # If we failed to parse the status-fd output, let's just whine and bail now
     if internal_error:
 
     # If we failed to parse the status-fd output, let's just whine and bail now
     if internal_error:
@@ -931,6 +992,7 @@ a *list* of keyrings to use.
         reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
         return None
 
         reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
         return None
 
+    bad = ""
     # Now check for obviously bad things in the processed output
     if keywords.has_key("SIGEXPIRED"):
         reject("The key used to sign %s has expired." % (sig_filename))
     # Now check for obviously bad things in the processed output
     if keywords.has_key("SIGEXPIRED"):
         reject("The key used to sign %s has expired." % (sig_filename))