~ed.so/duplicity/reuse-passphrase-for-signing-fix

« back to all changes in this revision

Viewing changes to duplicity/GnuPGInterface.py

  • Committer: bescoto
  • Date: 2002-10-29 01:49:46 UTC
  • Revision ID: vcs-imports@canonical.com-20021029014946-3m4rmm5plom7pl6q
Initial checkin

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.1 2002/10/29 01:51:36 bescoto 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()