1.1 --- a/MoinMessage.py Sun Jul 22 02:00:31 2012 +0200
1.2 +++ b/MoinMessage.py Fri Oct 19 00:33:37 2012 +0200
1.3 @@ -6,7 +6,6 @@
1.4 @license: GNU GPL (v2 or later), see COPYING.txt for details.
1.5 """
1.6
1.7 -from MoinMoin.log import getLogger
1.8 from email import message_from_string
1.9 from email.encoders import encode_noop
1.10 from email.mime.multipart import MIMEMultipart
1.11 @@ -14,7 +13,9 @@
1.12 from email.mime.base import MIMEBase
1.13 from email.mime.text import MIMEText
1.14 from subprocess import Popen, PIPE
1.15 +from tempfile import mkstemp
1.16 import httplib
1.17 +import os
1.18
1.19 class Message:
1.20
1.21 @@ -46,76 +47,129 @@
1.22 class MoinMessageError(Exception):
1.23 pass
1.24
1.25 -def gpg(args, text):
1.26 +class GPG:
1.27 +
1.28 + "A wrapper around the gpg command using a particular configuration."
1.29
1.30 - "Invoke gpg with the given 'args', supplying the given 'text'."
1.31 + def __init__(self, homedir=None):
1.32 + self.conf_args = []
1.33
1.34 - cmd = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
1.35 + if homedir:
1.36 + self.conf_args += ["--homedir", homedir]
1.37 +
1.38 + self.errors = None
1.39
1.40 - cmd.stdin.write(text)
1.41 - cmd.stdin.close()
1.42 + def run(self, args, text=None):
1.43
1.44 - errors = cmd.stderr.read()
1.45 - if errors:
1.46 - getLogger(__name__).warning(errors)
1.47 + """
1.48 + Invoke gpg with the given 'args', supplying the given 'text' to the
1.49 + command directly or, if 'text' is omitted, using a file provided as part
1.50 + of the 'args' if appropriate.
1.51
1.52 - try:
1.53 - text = cmd.stdout.read()
1.54 + Failure to complete the operation will result in a MoinMessageError
1.55 + being raised.
1.56 + """
1.57 +
1.58 + cmd = Popen(["gpg"] + self.conf_args + list(args), stdin=PIPE, stdout=PIPE, stderr=PIPE)
1.59
1.60 - # Test for a zero result.
1.61 + if text:
1.62 + cmd.stdin.write(text)
1.63 + cmd.stdin.close()
1.64
1.65 - if not cmd.wait():
1.66 - return text
1.67 - else:
1.68 - raise MoinMessageError, errors
1.69 + self.errors = cmd.stderr.read()
1.70 +
1.71 + try:
1.72 + text = cmd.stdout.read()
1.73 +
1.74 + # Test for a zero result.
1.75
1.76 - finally:
1.77 - cmd.stdout.close()
1.78 - cmd.stderr.close()
1.79 + if not cmd.wait():
1.80 + return text
1.81 + else:
1.82 + raise MoinMessageError, errors
1.83 +
1.84 + finally:
1.85 + cmd.stdout.close()
1.86 + cmd.stderr.close()
1.87
1.88 -def signMessage(message, keyid):
1.89 + def verifyMessage(self, signature, content):
1.90 +
1.91 + "Using the given 'signature', verify the given message 'content'."
1.92
1.93 - """
1.94 - Return a signed 'message' using the given 'keyid'.
1.95 - """
1.96 + # Write the detached signature and content to files.
1.97 +
1.98 + signature_fd, signature_filename = mkstemp()
1.99 + content_fd, content_filename = mkstemp()
1.100
1.101 - text = message.as_string()
1.102 - signature = gpg(["gpg", "--armor", "-u", keyid, "--detach-sig"], text)
1.103 + try:
1.104 + signature_fp = os.fdopen(signature_fd, "w")
1.105 + content_fp = os.fdopen(content_fd, "w")
1.106 + try:
1.107 + signature_fp.write(signature)
1.108 + content_fp.write(content)
1.109 + finally:
1.110 + signature_fp.close()
1.111 + content_fp.close()
1.112
1.113 - # Make the container for the message.
1.114 + # Verify the message text.
1.115
1.116 - signed_message = MIMEMultipart("signed", protocol="application/pgp-signature")
1.117 - signed_message.attach(message)
1.118 + self.run(["--verify", signature_filename, content_filename])
1.119
1.120 - signature_part = MIMEBase("application", "pgp-signature")
1.121 - signature_part.set_payload(signature)
1.122 - signed_message.attach(signature_part)
1.123 + finally:
1.124 + os.remove(signature_filename)
1.125 + os.remove(content_filename)
1.126 +
1.127 + def signMessage(self, message, keyid):
1.128
1.129 - return signed_message
1.130 + """
1.131 + Return a signed version of 'message' using the given 'keyid'.
1.132 + """
1.133
1.134 -def encryptMessage(message, keyid):
1.135 + text = message.as_string()
1.136 + signature = self.run(["--armor", "-u", keyid, "--detach-sig"], text)
1.137 +
1.138 + # Make the container for the message.
1.139 +
1.140 + signed_message = MIMEMultipart("signed", protocol="application/pgp-signature")
1.141 + signed_message.attach(message)
1.142
1.143 - """
1.144 - Return an encrypted 'message' using the given 'keyid'.
1.145 - """
1.146 + signature_part = MIMEBase("application", "pgp-signature")
1.147 + signature_part.set_payload(signature)
1.148 + signed_message.attach(signature_part)
1.149 +
1.150 + return signed_message
1.151 +
1.152 + def decryptMessage(self, message):
1.153
1.154 - text = message.as_string()
1.155 - encrypted = gpg(["gpg", "--armor", "-r", keyid, "--encrypt", "--trust-model", "always"], text)
1.156 + "Return a decrypted version of 'message'."
1.157 +
1.158 + return self.run(["--decrypt"], message)
1.159
1.160 - # Make the container for the message.
1.161 + def encryptMessage(self, message, keyid):
1.162
1.163 - encrypted_message = MIMEMultipart("encrypted", protocol="application/pgp-encrypted")
1.164 + """
1.165 + Return an encrypted version of 'message' using the given 'keyid'.
1.166 + """
1.167
1.168 - # For encrypted content, add the declaration and content.
1.169 + text = message.as_string()
1.170 + encrypted = self.run(["--armor", "-r", keyid, "--encrypt", "--trust-model", "always"], text)
1.171 +
1.172 + # Make the container for the message.
1.173 +
1.174 + encrypted_message = MIMEMultipart("encrypted", protocol="application/pgp-encrypted")
1.175 +
1.176 + # For encrypted content, add the declaration and content.
1.177
1.178 - declaration = MIMEBase("application", "pgp-encrypted")
1.179 - declaration.set_payload("Version: 1")
1.180 - encrypted_message.attach(declaration)
1.181 + declaration = MIMEBase("application", "pgp-encrypted")
1.182 + declaration.set_payload("Version: 1")
1.183 + encrypted_message.attach(declaration)
1.184
1.185 - content = MIMEApplication(encrypted, "octet-stream", encode_noop)
1.186 - encrypted_message.attach(content)
1.187 + content = MIMEApplication(encrypted, "octet-stream", encode_noop)
1.188 + encrypted_message.attach(content)
1.189
1.190 - return encrypted_message
1.191 + return encrypted_message
1.192 +
1.193 +# Communications functions.
1.194
1.195 def sendMessage(message, host, path):
1.196