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