]> git.decadent.org.uk Git - dak.git/commitdiff
Add (not enabled by default) support for auto-fetching keys from a keyserver. Origin...
authorJames Troup <james@nocrew.org>
Mon, 22 May 2006 00:07:27 +0000 (19:07 -0500)
committerJames Troup <james@nocrew.org>
Mon, 22 May 2006 00:07:27 +0000 (19:07 -0500)
ChangeLog
daklib/utils.py
docs/README.config

index 556e156b856c6079434d255dd7754c85aaf3f49c..23566093a0a5c8bea288929d4c75419519507cdb 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2006-05-21  James Troup  <james@nocrew.org>
+
+       * daklib/utils.py (process_gpgv_output): new function, split out
+       of check_signature().
+       (check_signature): adapt accordingly.
+       (retrieve_key): new function that will try to retrieve the key
+       that signed a given file from a keyserver.
+       (check_signature): add 'autofetch' argument that if not set
+       defaults to the value of Dinstall::KeyAutoFetch (if that exists).
+       If 'autofetch' is true, invoke retrieve_key().  
+
+       * docs/README.config: document Dinstall::KeyAutoFetch and
+       Dinstall:KeyServer.
+
 2006-05-20  James Troup  <james@nocrew.org>
 
        * dak/find_null_maintainers.py (main):
 2006-05-20  James Troup  <james@nocrew.org>
 
        * dak/find_null_maintainers.py (main):
index c4c13669d92ca3a2aca761c2124e800ecf55e95a..47978228663aa5ee42033a9b95556ddf671305a0 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))
index 645c22ad57be63a926ae4a98e597f72d1e9cb0e5..52588bdc80ad2651416e79f1bca8e4df625a0b7a 100644 (file)
@@ -291,6 +291,15 @@ SkipTime (required): an integer value which is the number of seconds that a
 file must be older than (via it's last modified timestamp) before dak process-unchecked
 will REJECT rather than SKIP the package.
 
 file must be older than (via it's last modified timestamp) before dak process-unchecked
 will REJECT rather than SKIP the package.
 
+KeyAutoFetch (optional): boolean (default: false), which if set (and
+not overriden by explicit argument to check_signature()) will enable
+auto key retrieval.  Requires KeyServer and SigningKeyIds variables be
+set.  NB: you should only enable this variable on production systems
+if you have strict control of your upload queue.
+
+KeyServer (optional): keyserver used for key auto-retrieval
+(c.f. KeyAutoFetch).
+
 ================================================================================
 
 Archive
 ================================================================================
 
 Archive