| 1 | """Interface to GNU Privacy Guard (GnuPG) |
|---|
| 2 | |
|---|
| 3 | GnuPGInterface is a Python module to interface with GnuPG. |
|---|
| 4 | It concentrates on interacting with GnuPG via filehandles, |
|---|
| 5 | providing access to control GnuPG via versatile and extensible means. |
|---|
| 6 | |
|---|
| 7 | This module is based on GnuPG::Interface, a Perl module by the same author. |
|---|
| 8 | |
|---|
| 9 | Normally, using this module will involve creating a |
|---|
| 10 | GnuPG object, setting some options in it's 'options' data member |
|---|
| 11 | (which is of type Options), creating some pipes |
|---|
| 12 | to talk with GnuPG, and then calling the run() method, which will |
|---|
| 13 | connect those pipes to the GnuPG process. run() returns a |
|---|
| 14 | Process object, which contains the filehandles to talk to GnuPG with. |
|---|
| 15 | |
|---|
| 16 | Example code: |
|---|
| 17 | |
|---|
| 18 | >>> import GnuPGInterface |
|---|
| 19 | >>> |
|---|
| 20 | >>> plaintext = "Three blind mice" |
|---|
| 21 | >>> passphrase = "This is the passphrase" |
|---|
| 22 | >>> |
|---|
| 23 | >>> gnupg = GnuPGInterface.GnuPG() |
|---|
| 24 | >>> gnupg.options.armor = 1 |
|---|
| 25 | >>> gnupg.options.meta_interactive = 0 |
|---|
| 26 | >>> gnupg.options.extra_args.append('--no-secmem-warning') |
|---|
| 27 | >>> |
|---|
| 28 | >>> # Normally we might specify something in |
|---|
| 29 | >>> # gnupg.options.recipients, like |
|---|
| 30 | >>> # gnupg.options.recipients = [ '0xABCD1234', 'bob@foo.bar' ] |
|---|
| 31 | >>> # but since we're doing symmetric-only encryption, it's not needed. |
|---|
| 32 | >>> # If you are doing standard, public-key encryption, using |
|---|
| 33 | >>> # --encrypt, you will need to specify recipients before |
|---|
| 34 | >>> # calling gnupg.run() |
|---|
| 35 | >>> |
|---|
| 36 | >>> # First we'll encrypt the test_text input symmetrically |
|---|
| 37 | >>> p1 = gnupg.run(['--symmetric'], |
|---|
| 38 | ... create_fhs=['stdin', 'stdout', 'passphrase']) |
|---|
| 39 | >>> |
|---|
| 40 | >>> p1.handles['passphrase'].write(passphrase) |
|---|
| 41 | >>> p1.handles['passphrase'].close() |
|---|
| 42 | >>> |
|---|
| 43 | >>> p1.handles['stdin'].write(plaintext) |
|---|
| 44 | >>> p1.handles['stdin'].close() |
|---|
| 45 | >>> |
|---|
| 46 | >>> ciphertext = p1.handles['stdout'].read() |
|---|
| 47 | >>> p1.handles['stdout'].close() |
|---|
| 48 | >>> |
|---|
| 49 | >>> # process cleanup |
|---|
| 50 | >>> p1.wait() |
|---|
| 51 | >>> |
|---|
| 52 | >>> # Now we'll decrypt what we just encrypted it, |
|---|
| 53 | >>> # using the convience method to get the |
|---|
| 54 | >>> # passphrase to GnuPG |
|---|
| 55 | >>> gnupg.passphrase = passphrase |
|---|
| 56 | >>> |
|---|
| 57 | >>> p2 = gnupg.run(['--decrypt'], create_fhs=['stdin', 'stdout']) |
|---|
| 58 | >>> |
|---|
| 59 | >>> p2.handles['stdin'].write(ciphertext) |
|---|
| 60 | >>> p2.handles['stdin'].close() |
|---|
| 61 | >>> |
|---|
| 62 | >>> decrypted_plaintext = p2.handles['stdout'].read() |
|---|
| 63 | >>> p2.handles['stdout'].close() |
|---|
| 64 | >>> |
|---|
| 65 | >>> # process cleanup |
|---|
| 66 | >>> p2.wait() |
|---|
| 67 | >>> |
|---|
| 68 | >>> # Our decrypted plaintext: |
|---|
| 69 | >>> decrypted_plaintext |
|---|
| 70 | 'Three blind mice' |
|---|
| 71 | >>> |
|---|
| 72 | >>> # ...and see it's the same as what we orignally encrypted |
|---|
| 73 | >>> assert decrypted_plaintext == plaintext, \ |
|---|
| 74 | "GnuPG decrypted output does not match original input" |
|---|
| 75 | >>> |
|---|
| 76 | >>> |
|---|
| 77 | >>> ################################################## |
|---|
| 78 | >>> # Now let's trying using run()'s attach_fhs paramter |
|---|
| 79 | >>> |
|---|
| 80 | >>> # we're assuming we're running on a unix... |
|---|
| 81 | >>> input = open('/etc/motd') |
|---|
| 82 | >>> |
|---|
| 83 | >>> p1 = gnupg.run(['--symmetric'], create_fhs=['stdout'], |
|---|
| 84 | ... attach_fhs={'stdin': input}) |
|---|
| 85 | >>> |
|---|
| 86 | >>> # GnuPG will read the stdin from /etc/motd |
|---|
| 87 | >>> ciphertext = p1.handles['stdout'].read() |
|---|
| 88 | >>> |
|---|
| 89 | >>> # process cleanup |
|---|
| 90 | >>> p1.wait() |
|---|
| 91 | >>> |
|---|
| 92 | >>> # Now let's run the output through GnuPG |
|---|
| 93 | >>> # We'll write the output to a temporary file, |
|---|
| 94 | >>> import tempfile |
|---|
| 95 | >>> temp = tempfile.TemporaryFile() |
|---|
| 96 | >>> |
|---|
| 97 | >>> p2 = gnupg.run(['--decrypt'], create_fhs=['stdin'], |
|---|
| 98 | ... attach_fhs={'stdout': temp}) |
|---|
| 99 | >>> |
|---|
| 100 | >>> # give GnuPG our encrypted stuff from the first run |
|---|
| 101 | >>> p2.handles['stdin'].write(ciphertext) |
|---|
| 102 | >>> p2.handles['stdin'].close() |
|---|
| 103 | >>> |
|---|
| 104 | >>> # process cleanup |
|---|
| 105 | >>> p2.wait() |
|---|
| 106 | >>> |
|---|
| 107 | >>> # rewind the tempfile and see what GnuPG gave us |
|---|
| 108 | >>> temp.seek(0) |
|---|
| 109 | >>> decrypted_plaintext = temp.read() |
|---|
| 110 | >>> |
|---|
| 111 | >>> # compare what GnuPG decrypted with our original input |
|---|
| 112 | >>> input.seek(0) |
|---|
| 113 | >>> input_data = input.read() |
|---|
| 114 | >>> |
|---|
| 115 | >>> assert decrypted_plaintext == input_data, \ |
|---|
| 116 | "GnuPG decrypted output does not match original input" |
|---|
| 117 | |
|---|
| 118 | To do things like public-key encryption, simply pass do something |
|---|
| 119 | like: |
|---|
| 120 | |
|---|
| 121 | gnupg.passphrase = 'My passphrase' |
|---|
| 122 | gnupg.options.recipients = [ 'bob@foobar.com' ] |
|---|
| 123 | gnupg.run( ['--sign', '--encrypt'], create_fhs=..., attach_fhs=...) |
|---|
| 124 | |
|---|
| 125 | Here is an example of subclassing GnuPGInterface.GnuPG, |
|---|
| 126 | so that it has an encrypt_string() method that returns |
|---|
| 127 | ciphertext. |
|---|
| 128 | |
|---|
| 129 | >>> import GnuPGInterface |
|---|
| 130 | >>> |
|---|
| 131 | >>> class MyGnuPG(GnuPGInterface.GnuPG): |
|---|
| 132 | ... |
|---|
| 133 | ... def __init__(self): |
|---|
| 134 | ... GnuPGInterface.GnuPG.__init__(self) |
|---|
| 135 | ... self.setup_my_options() |
|---|
| 136 | ... |
|---|
| 137 | ... def setup_my_options(self): |
|---|
| 138 | ... self.options.armor = 1 |
|---|
| 139 | ... self.options.meta_interactive = 0 |
|---|
| 140 | ... self.options.extra_args.append('--no-secmem-warning') |
|---|
| 141 | ... |
|---|
| 142 | ... def encrypt_string(self, string, recipients): |
|---|
| 143 | ... gnupg.options.recipients = recipients # a list! |
|---|
| 144 | ... |
|---|
| 145 | ... proc = gnupg.run(['--encrypt'], create_fhs=['stdin', 'stdout']) |
|---|
| 146 | ... |
|---|
| 147 | ... proc.handles['stdin'].write(string) |
|---|
| 148 | ... proc.handles['stdin'].close() |
|---|
| 149 | ... |
|---|
| 150 | ... output = proc.handles['stdout'].read() |
|---|
| 151 | ... proc.handles['stdout'].close() |
|---|
| 152 | ... |
|---|
| 153 | ... proc.wait() |
|---|
| 154 | ... return output |
|---|
| 155 | ... |
|---|
| 156 | >>> gnupg = MyGnuPG() |
|---|
| 157 | >>> ciphertext = gnupg.encrypt_string("The secret", ['0x260C4FA3']) |
|---|
| 158 | >>> |
|---|
| 159 | >>> # just a small sanity test here for doctest |
|---|
| 160 | >>> import types |
|---|
| 161 | >>> assert isinstance(ciphertext, types.StringType), \ |
|---|
| 162 | "What GnuPG gave back is not a string!" |
|---|
| 163 | |
|---|
| 164 | Here is an example of generating a key: |
|---|
| 165 | >>> import GnuPGInterface |
|---|
| 166 | >>> gnupg = GnuPGInterface.GnuPG() |
|---|
| 167 | >>> gnupg.options.meta_interactive = 0 |
|---|
| 168 | >>> |
|---|
| 169 | >>> # We will be creative and use the logger filehandle to capture |
|---|
| 170 | >>> # what GnuPG says this time, instead stderr; no stdout to listen to, |
|---|
| 171 | >>> # but we capture logger to surpress the dry-run command. |
|---|
| 172 | >>> # We also have to capture stdout since otherwise doctest complains; |
|---|
| 173 | >>> # Normally you can let stdout through when generating a key. |
|---|
| 174 | >>> |
|---|
| 175 | >>> proc = gnupg.run(['--gen-key'], create_fhs=['stdin', 'stdout', |
|---|
| 176 | ... 'logger']) |
|---|
| 177 | >>> |
|---|
| 178 | >>> proc.handles['stdin'].write('''Key-Type: DSA |
|---|
| 179 | ... Key-Length: 1024 |
|---|
| 180 | ... # We are only testing syntax this time, so dry-run |
|---|
| 181 | ... %dry-run |
|---|
| 182 | ... Subkey-Type: ELG-E |
|---|
| 183 | ... Subkey-Length: 1024 |
|---|
| 184 | ... Name-Real: Joe Tester |
|---|
| 185 | ... Name-Comment: with stupid passphrase |
|---|
| 186 | ... Name-Email: joe@foo.bar |
|---|
| 187 | ... Expire-Date: 2y |
|---|
| 188 | ... Passphrase: abc |
|---|
| 189 | ... %pubring foo.pub |
|---|
| 190 | ... %secring foo.sec |
|---|
| 191 | ... ''') |
|---|
| 192 | >>> |
|---|
| 193 | >>> proc.handles['stdin'].close() |
|---|
| 194 | >>> |
|---|
| 195 | >>> report = proc.handles['logger'].read() |
|---|
| 196 | >>> proc.handles['logger'].close() |
|---|
| 197 | >>> |
|---|
| 198 | >>> proc.wait() |
|---|
| 199 | |
|---|
| 200 | |
|---|
| 201 | COPYRIGHT: |
|---|
| 202 | |
|---|
| 203 | Copyright (C) 2001 Frank J. Tobin, ftobin@neverending.org |
|---|
| 204 | |
|---|
| 205 | LICENSE: |
|---|
| 206 | |
|---|
| 207 | This library is free software; you can redistribute it and/or |
|---|
| 208 | modify it under the terms of the GNU Lesser General Public |
|---|
| 209 | License as published by the Free Software Foundation; either |
|---|
| 210 | version 2.1 of the License, or (at your option) any later version. |
|---|
| 211 | |
|---|
| 212 | This library is distributed in the hope that it will be useful, |
|---|
| 213 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 214 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|---|
| 215 | Lesser General Public License for more details. |
|---|
| 216 | |
|---|
| 217 | You should have received a copy of the GNU Lesser General Public |
|---|
| 218 | License along with this library; if not, write to the Free Software |
|---|
| 219 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|---|
| 220 | or see http://www.gnu.org/copyleft/lesser.html |
|---|
| 221 | """ |
|---|
| 222 | |
|---|
| 223 | import os |
|---|
| 224 | import sys |
|---|
| 225 | import fcntl |
|---|
| 226 | |
|---|
| 227 | __author__ = "Frank J. Tobin, ftobin@neverending.org" |
|---|
| 228 | __version__ = "0.3.2" |
|---|
| 229 | __revision__ = "$Id: GnuPGInterface.py,v 1.22 2002/01/11 20:22:04 ftobin Exp $" |
|---|
| 230 | |
|---|
| 231 | # "standard" filehandles attached to processes |
|---|
| 232 | _stds = [ 'stdin', 'stdout', 'stderr' ] |
|---|
| 233 | |
|---|
| 234 | # the permissions each type of fh needs to be opened with |
|---|
| 235 | _fd_modes = { 'stdin': 'w', |
|---|
| 236 | 'stdout': 'r', |
|---|
| 237 | 'stderr': 'r', |
|---|
| 238 | 'passphrase': 'w', |
|---|
| 239 | 'command': 'w', |
|---|
| 240 | 'logger': 'r', |
|---|
| 241 | 'status': 'r' |
|---|
| 242 | } |
|---|
| 243 | |
|---|
| 244 | # correlation between handle names and the arguments we'll pass |
|---|
| 245 | _fd_options = { 'passphrase': '--passphrase-fd', |
|---|
| 246 | 'logger': '--logger-fd', |
|---|
| 247 | 'status': '--status-fd', |
|---|
| 248 | 'command': '--command-fd' } |
|---|
| 249 | |
|---|
| 250 | class GnuPG: |
|---|
| 251 | """Class instances represent GnuPG. |
|---|
| 252 | |
|---|
| 253 | Instance attributes of a GnuPG object are: |
|---|
| 254 | |
|---|
| 255 | * call -- string to call GnuPG with. Defaults to "gpg" |
|---|
| 256 | |
|---|
| 257 | * passphrase -- Since it is a common operation |
|---|
| 258 | to pass in a passphrase to GnuPG, |
|---|
| 259 | and working with the passphrase filehandle mechanism directly |
|---|
| 260 | can be mundane, if set, the passphrase attribute |
|---|
| 261 | works in a special manner. If the passphrase attribute is set, |
|---|
| 262 | and no passphrase file object is sent in to run(), |
|---|
| 263 | then GnuPG instnace will take care of sending the passphrase to |
|---|
| 264 | GnuPG, the executable instead of having the user sent it in manually. |
|---|
| 265 | |
|---|
| 266 | * options -- Object of type GnuPGInterface.Options. |
|---|
| 267 | Attribute-setting in options determines |
|---|
| 268 | the command-line options used when calling GnuPG. |
|---|
| 269 | """ |
|---|
| 270 | |
|---|
| 271 | def __init__(self): |
|---|
| 272 | self.call = 'gpg' |
|---|
| 273 | self.passphrase = None |
|---|
| 274 | self.options = Options() |
|---|
| 275 | |
|---|
| 276 | def run(self, gnupg_commands, args=None, create_fhs=None, attach_fhs=None): |
|---|
| 277 | """Calls GnuPG with the list of string commands gnupg_commands, |
|---|
| 278 | complete with prefixing dashes. |
|---|
| 279 | For example, gnupg_commands could be |
|---|
| 280 | '["--sign", "--encrypt"]' |
|---|
| 281 | Returns a GnuPGInterface.Process object. |
|---|
| 282 | |
|---|
| 283 | args is an optional list of GnuPG command arguments (not options), |
|---|
| 284 | such as keyID's to export, filenames to process, etc. |
|---|
| 285 | |
|---|
| 286 | create_fhs is an optional list of GnuPG filehandle |
|---|
| 287 | names that will be set as keys of the returned Process object's |
|---|
| 288 | 'handles' attribute. The generated filehandles can be used |
|---|
| 289 | to communicate with GnuPG via standard input, standard output, |
|---|
| 290 | the status-fd, passphrase-fd, etc. |
|---|
| 291 | |
|---|
| 292 | Valid GnuPG filehandle names are: |
|---|
| 293 | * stdin |
|---|
| 294 | * stdout |
|---|
| 295 | * stderr |
|---|
| 296 | * status |
|---|
| 297 | * passphase |
|---|
| 298 | * command |
|---|
| 299 | * logger |
|---|
| 300 | |
|---|
| 301 | The purpose of each filehandle is described in the GnuPG |
|---|
| 302 | documentation. |
|---|
| 303 | |
|---|
| 304 | attach_fhs is an optional dictionary with GnuPG filehandle |
|---|
| 305 | names mapping to opened files. GnuPG will read or write |
|---|
| 306 | to the file accordingly. For example, if 'my_file' is an |
|---|
| 307 | opened file and 'attach_fhs[stdin] is my_file', then GnuPG |
|---|
| 308 | will read its standard input from my_file. This is useful |
|---|
| 309 | if you want GnuPG to read/write to/from an existing file. |
|---|
| 310 | For instance: |
|---|
| 311 | |
|---|
| 312 | f = open("encrypted.gpg") |
|---|
| 313 | gnupg.run(["--decrypt"], attach_fhs={'stdin': f}) |
|---|
| 314 | |
|---|
| 315 | Using attach_fhs also helps avoid system buffering |
|---|
| 316 | issues that can arise when using create_fhs, which |
|---|
| 317 | can cause the process to deadlock. |
|---|
| 318 | |
|---|
| 319 | If not mentioned in create_fhs or attach_fhs, |
|---|
| 320 | GnuPG filehandles which are a std* (stdin, stdout, stderr) |
|---|
| 321 | are defaulted to the running process' version of handle. |
|---|
| 322 | Otherwise, that type of handle is simply not used when calling GnuPG. |
|---|
| 323 | For example, if you do not care about getting data from GnuPG's |
|---|
| 324 | status filehandle, simply do not specify it. |
|---|
| 325 | |
|---|
| 326 | run() returns a Process() object which has a 'handles' |
|---|
| 327 | which is a dictionary mapping from the handle name |
|---|
| 328 | (such as 'stdin' or 'stdout') to the respective |
|---|
| 329 | newly-created FileObject connected to the running GnuPG process. |
|---|
| 330 | For instance, if the call was |
|---|
| 331 | |
|---|
| 332 | process = gnupg.run(["--decrypt"], stdin=1) |
|---|
| 333 | |
|---|
| 334 | after run returns 'process.handles["stdin"]' |
|---|
| 335 | is a FileObject connected to GnuPG's standard input, |
|---|
| 336 | and can be written to. |
|---|
| 337 | """ |
|---|
| 338 | |
|---|
| 339 | if args == None: args = [] |
|---|
| 340 | if create_fhs == None: create_fhs = [] |
|---|
| 341 | if attach_fhs == None: attach_fhs = {} |
|---|
| 342 | |
|---|
| 343 | for std in _stds: |
|---|
| 344 | if not attach_fhs.has_key(std) \ |
|---|
| 345 | and std not in create_fhs: |
|---|
| 346 | attach_fhs.setdefault(std, getattr(sys, std)) |
|---|
| 347 | |
|---|
| 348 | handle_passphrase = 0 |
|---|
| 349 | |
|---|
| 350 | if self.passphrase != None \ |
|---|
| 351 | and not attach_fhs.has_key('passphrase') \ |
|---|
| 352 | and 'passphrase' not in create_fhs: |
|---|
| 353 | handle_passphrase = 1 |
|---|
| 354 | create_fhs.append('passphrase') |
|---|
| 355 | |
|---|
| 356 | process = self._attach_fork_exec(gnupg_commands, args, |
|---|
| 357 | create_fhs, attach_fhs) |
|---|
| 358 | |
|---|
| 359 | if handle_passphrase: |
|---|
| 360 | passphrase_fh = process.handles['passphrase'] |
|---|
| 361 | passphrase_fh.write( self.passphrase ) |
|---|
| 362 | passphrase_fh.close() |
|---|
| 363 | del process.handles['passphrase'] |
|---|
| 364 | |
|---|
| 365 | return process |
|---|
| 366 | |
|---|
| 367 | |
|---|
| 368 | def _attach_fork_exec(self, gnupg_commands, args, create_fhs, attach_fhs): |
|---|
| 369 | """This is like run(), but without the passphrase-helping |
|---|
| 370 | (note that run() calls this).""" |
|---|
| 371 | |
|---|
| 372 | process = Process() |
|---|
| 373 | |
|---|
| 374 | for fh_name in create_fhs + attach_fhs.keys(): |
|---|
| 375 | if not _fd_modes.has_key(fh_name): |
|---|
| 376 | raise KeyError, \ |
|---|
| 377 | "unrecognized filehandle name '%s'; must be one of %s" \ |
|---|
| 378 | % (fh_name, _fd_modes.keys()) |
|---|
| 379 | |
|---|
| 380 | for fh_name in create_fhs: |
|---|
| 381 | # make sure the user doesn't specify a filehandle |
|---|
| 382 | # to be created *and* attached |
|---|
| 383 | if attach_fhs.has_key(fh_name): |
|---|
| 384 | raise ValueError, \ |
|---|
| 385 | "cannot have filehandle '%s' in both create_fhs and attach_fhs" \ |
|---|
| 386 | % fh_name |
|---|
| 387 | |
|---|
| 388 | pipe = os.pipe() |
|---|
| 389 | # fix by drt@un.bewaff.net noting |
|---|
| 390 | # that since pipes are unidirectional on some systems, |
|---|
| 391 | # so we have to 'turn the pipe around' |
|---|
| 392 | # if we are writing |
|---|
| 393 | if _fd_modes[fh_name] == 'w': pipe = (pipe[1], pipe[0]) |
|---|
| 394 | process._pipes[fh_name] = Pipe(pipe[0], pipe[1], 0) |
|---|
| 395 | |
|---|
| 396 | for fh_name, fh in attach_fhs.items(): |
|---|
| 397 | process._pipes[fh_name] = Pipe(fh.fileno(), fh.fileno(), 1) |
|---|
| 398 | |
|---|
| 399 | process.pid = os.fork() |
|---|
| 400 | |
|---|
| 401 | if process.pid == 0: self._as_child(process, gnupg_commands, args) |
|---|
| 402 | return self._as_parent(process) |
|---|
| 403 | |
|---|
| 404 | |
|---|
| 405 | def _as_parent(self, process): |
|---|
| 406 | """Stuff run after forking in parent""" |
|---|
| 407 | for k, p in process._pipes.items(): |
|---|
| 408 | if not p.direct: |
|---|
| 409 | os.close(p.child) |
|---|
| 410 | process.handles[k] = os.fdopen(p.parent, _fd_modes[k]) |
|---|
| 411 | |
|---|
| 412 | # user doesn't need these |
|---|
| 413 | del process._pipes |
|---|
| 414 | |
|---|
| 415 | return process |
|---|
| 416 | |
|---|
| 417 | |
|---|
| 418 | def _as_child(self, process, gnupg_commands, args): |
|---|
| 419 | """Stuff run after forking in child""" |
|---|
| 420 | # child |
|---|
| 421 | for std in _stds: |
|---|
| 422 | p = process._pipes[std] |
|---|
| 423 | os.dup2( p.child, getattr(sys, "__%s__" % std).fileno() ) |
|---|
| 424 | |
|---|
| 425 | for k, p in process._pipes.items(): |
|---|
| 426 | if p.direct and k not in _stds: |
|---|
| 427 | # we want the fh to stay open after execing |
|---|
| 428 | fcntl.fcntl( p.child, fcntl.F_SETFD, 0 ) |
|---|
| 429 | |
|---|
| 430 | fd_args = [] |
|---|
| 431 | |
|---|
| 432 | for k, p in process._pipes.items(): |
|---|
| 433 | # set command-line options for non-standard fds |
|---|
| 434 | if k not in _stds: |
|---|
| 435 | fd_args.extend([ _fd_options[k], "%d" % p.child ]) |
|---|
| 436 | |
|---|
| 437 | if not p.direct: os.close(p.parent) |
|---|
| 438 | |
|---|
| 439 | command = [ self.call ] + fd_args + self.options.get_args() \ |
|---|
| 440 | + gnupg_commands + args |
|---|
| 441 | |
|---|
| 442 | os.execvp( command[0], command ) |
|---|
| 443 | |
|---|
| 444 | |
|---|
| 445 | class Pipe: |
|---|
| 446 | """simple struct holding stuff about pipes we use""" |
|---|
| 447 | def __init__(self, parent, child, direct): |
|---|
| 448 | self.parent = parent |
|---|
| 449 | self.child = child |
|---|
| 450 | self.direct = direct |
|---|
| 451 | |
|---|
| 452 | |
|---|
| 453 | class Options: |
|---|
| 454 | """Objects of this class encompass options passed to GnuPG. |
|---|
| 455 | This class is responsible for determining command-line arguments |
|---|
| 456 | which are based on options. It can be said that a GnuPG |
|---|
| 457 | object has-a Options object in its options attribute. |
|---|
| 458 | |
|---|
| 459 | Attributes which correlate directly to GnuPG options: |
|---|
| 460 | |
|---|
| 461 | Each option here defaults to false or None, and is described in |
|---|
| 462 | GnuPG documentation. |
|---|
| 463 | |
|---|
| 464 | Booleans (set these attributes to booleans) |
|---|
| 465 | |
|---|
| 466 | * armor |
|---|
| 467 | * no_greeting |
|---|
| 468 | * no_verbose |
|---|
| 469 | * quiet |
|---|
| 470 | * batch |
|---|
| 471 | * always_trust |
|---|
| 472 | * rfc1991 |
|---|
| 473 | * openpgp |
|---|
| 474 | * force_v3_sigs |
|---|
| 475 | * no_options |
|---|
| 476 | * textmode |
|---|
| 477 | |
|---|
| 478 | Strings (set these attributes to strings) |
|---|
| 479 | |
|---|
| 480 | * homedir |
|---|
| 481 | * default_key |
|---|
| 482 | * comment |
|---|
| 483 | * compress_algo |
|---|
| 484 | * options |
|---|
| 485 | |
|---|
| 486 | Lists (set these attributes to lists) |
|---|
| 487 | |
|---|
| 488 | * recipients (***NOTE*** plural of 'recipient') |
|---|
| 489 | * encrypt_to |
|---|
| 490 | |
|---|
| 491 | Meta options |
|---|
| 492 | |
|---|
| 493 | Meta options are options provided by this module that do |
|---|
| 494 | not correlate directly to any GnuPG option by name, |
|---|
| 495 | but are rather bundle of options used to accomplish |
|---|
| 496 | a specific goal, such as obtaining compatibility with PGP 5. |
|---|
| 497 | The actual arguments each of these reflects may change with time. Each |
|---|
| 498 | defaults to false unless otherwise specified. |
|---|
| 499 | |
|---|
| 500 | meta_pgp_5_compatible -- If true, arguments are generated to try |
|---|
| 501 | to be compatible with PGP 5.x. |
|---|
| 502 | |
|---|
| 503 | meta_pgp_2_compatible -- If true, arguments are generated to try |
|---|
| 504 | to be compatible with PGP 2.x. |
|---|
| 505 | |
|---|
| 506 | meta_interactive -- If false, arguments are generated to try to |
|---|
| 507 | help the using program use GnuPG in a non-interactive |
|---|
| 508 | environment, such as CGI scripts. Default is true. |
|---|
| 509 | |
|---|
| 510 | extra_args -- Extra option arguments may be passed in |
|---|
| 511 | via the attribute extra_args, a list. |
|---|
| 512 | |
|---|
| 513 | >>> import GnuPGInterface |
|---|
| 514 | >>> |
|---|
| 515 | >>> gnupg = GnuPGInterface.GnuPG() |
|---|
| 516 | >>> gnupg.options.armor = 1 |
|---|
| 517 | >>> gnupg.options.recipients = ['Alice', 'Bob'] |
|---|
| 518 | >>> gnupg.options.extra_args = ['--no-secmem-warning'] |
|---|
| 519 | >>> |
|---|
| 520 | >>> # no need for users to call this normally; just for show here |
|---|
| 521 | >>> gnupg.options.get_args() |
|---|
| 522 | ['--armor', '--recipient', 'Alice', '--recipient', 'Bob', '--no-secmem-warning'] |
|---|
| 523 | """ |
|---|
| 524 | |
|---|
| 525 | def __init__(self): |
|---|
| 526 | # booleans |
|---|
| 527 | self.armor = 0 |
|---|
| 528 | self.no_greeting = 0 |
|---|
| 529 | self.verbose = 0 |
|---|
| 530 | self.no_verbose = 0 |
|---|
| 531 | self.quiet = 0 |
|---|
| 532 | self.batch = 0 |
|---|
| 533 | self.always_trust = 0 |
|---|
| 534 | self.rfc1991 = 0 |
|---|
| 535 | self.openpgp = 0 |
|---|
| 536 | self.force_v3_sigs = 0 |
|---|
| 537 | self.no_options = 0 |
|---|
| 538 | self.textmode = 0 |
|---|
| 539 | |
|---|
| 540 | # meta-option booleans |
|---|
| 541 | self.meta_pgp_5_compatible = 0 |
|---|
| 542 | self.meta_pgp_2_compatible = 0 |
|---|
| 543 | self.meta_interactive = 1 |
|---|
| 544 | |
|---|
| 545 | # strings |
|---|
| 546 | self.homedir = None |
|---|
| 547 | self.default_key = None |
|---|
| 548 | self.comment = None |
|---|
| 549 | self.compress_algo = None |
|---|
| 550 | self.options = None |
|---|
| 551 | |
|---|
| 552 | # lists |
|---|
| 553 | self.encrypt_to = [] |
|---|
| 554 | self.recipients = [] |
|---|
| 555 | |
|---|
| 556 | # miscellaneous arguments |
|---|
| 557 | self.extra_args = [] |
|---|
| 558 | |
|---|
| 559 | def get_args( self ): |
|---|
| 560 | """Generate a list of GnuPG arguments based upon attributes.""" |
|---|
| 561 | |
|---|
| 562 | return self.get_meta_args() + self.get_standard_args() + self.extra_args |
|---|
| 563 | |
|---|
| 564 | def get_standard_args( self ): |
|---|
| 565 | """Generate a list of standard, non-meta or extra arguments""" |
|---|
| 566 | args = [] |
|---|
| 567 | if self.homedir != None: args.extend( [ '--homedir', self.homedir ] ) |
|---|
| 568 | if self.options != None: args.extend( [ '--options', self.options ] ) |
|---|
| 569 | if self.comment != None: args.extend( [ '--comment', self.comment ] ) |
|---|
| 570 | if self.compress_algo != None: args.extend( [ '--compress-algo', self.compress_algo ] ) |
|---|
| 571 | if self.default_key != None: args.extend( [ '--default-key', self.default_key ] ) |
|---|
| 572 | |
|---|
| 573 | if self.no_options: args.append( '--no-options' ) |
|---|
| 574 | if self.armor: args.append( '--armor' ) |
|---|
| 575 | if self.textmode: args.append( '--textmode' ) |
|---|
| 576 | if self.no_greeting: args.append( '--no-greeting' ) |
|---|
| 577 | if self.verbose: args.append( '--verbose' ) |
|---|
| 578 | if self.no_verbose: args.append( '--no-verbose' ) |
|---|
| 579 | if self.quiet: args.append( '--quiet' ) |
|---|
| 580 | if self.batch: args.append( '--batch' ) |
|---|
| 581 | if self.always_trust: args.append( '--always-trust' ) |
|---|
| 582 | if self.force_v3_sigs: args.append( '--force-v3-sigs' ) |
|---|
| 583 | if self.rfc1991: args.append( '--rfc1991' ) |
|---|
| 584 | if self.openpgp: args.append( '--openpgp' ) |
|---|
| 585 | |
|---|
| 586 | for r in self.recipients: args.extend( [ '--recipient', r ] ) |
|---|
| 587 | for r in self.encrypt_to: args.extend( [ '--encrypt-to', r ] ) |
|---|
| 588 | |
|---|
| 589 | return args |
|---|
| 590 | |
|---|
| 591 | def get_meta_args( self ): |
|---|
| 592 | """Get a list of generated meta-arguments""" |
|---|
| 593 | args = [] |
|---|
| 594 | |
|---|
| 595 | if self.meta_pgp_5_compatible: args.extend( [ '--compress-algo', '1', |
|---|
| 596 | '--force-v3-sigs' |
|---|
| 597 | ] ) |
|---|
| 598 | if self.meta_pgp_2_compatible: args.append( '--rfc1991' ) |
|---|
| 599 | if not self.meta_interactive: args.extend( [ '--batch', '--no-tty' ] ) |
|---|
| 600 | |
|---|
| 601 | return args |
|---|
| 602 | |
|---|
| 603 | |
|---|
| 604 | class Process: |
|---|
| 605 | """Objects of this class encompass properties of a GnuPG |
|---|
| 606 | process spawned by GnuPG.run(). |
|---|
| 607 | |
|---|
| 608 | # gnupg is a GnuPG object |
|---|
| 609 | process = gnupg.run( [ '--decrypt' ], stdout = 1 ) |
|---|
| 610 | out = process.handles['stdout'].read() |
|---|
| 611 | ... |
|---|
| 612 | os.waitpid( process.pid, 0 ) |
|---|
| 613 | |
|---|
| 614 | Data Attributes |
|---|
| 615 | |
|---|
| 616 | handles -- This is a map of filehandle-names to |
|---|
| 617 | the file handles, if any, that were requested via run() and hence |
|---|
| 618 | are connected to the running GnuPG process. Valid names |
|---|
| 619 | of this map are only those handles that were requested. |
|---|
| 620 | |
|---|
| 621 | pid -- The PID of the spawned GnuPG process. |
|---|
| 622 | Useful to know, since once should call |
|---|
| 623 | os.waitpid() to clean up the process, especially |
|---|
| 624 | if multiple calls are made to run(). |
|---|
| 625 | """ |
|---|
| 626 | |
|---|
| 627 | def __init__(self): |
|---|
| 628 | self._pipes = {} |
|---|
| 629 | self.handles = {} |
|---|
| 630 | self.pid = None |
|---|
| 631 | self._waited = None |
|---|
| 632 | |
|---|
| 633 | def wait(self): |
|---|
| 634 | """Wait on the process to exit, allowing for child cleanup. |
|---|
| 635 | Will raise an IOError if the process exits non-zero.""" |
|---|
| 636 | |
|---|
| 637 | e = os.waitpid(self.pid, 0)[1] |
|---|
| 638 | if e != 0: |
|---|
| 639 | raise IOError, "GnuPG exited non-zero, with code %d" % (e << 8) |
|---|
| 640 | |
|---|
| 641 | def _run_doctests(): |
|---|
| 642 | import doctest, GnuPGInterface |
|---|
| 643 | return doctest.testmod(GnuPGInterface) |
|---|
| 644 | |
|---|
| 645 | # deprecated |
|---|
| 646 | GnuPGInterface = GnuPG |
|---|
| 647 | |
|---|
| 648 | if __name__ == '__main__': |
|---|
| 649 | _run_doctests() |
|---|