293df097fb2903f1e3d22746d7e85ba30be8f6cf
[gpgme.git] / lang / python / pyme / core.py
1 # Copyright (C) 2016 g10 Code GmbH
2 # Copyright (C) 2004,2008 Igor Belyi <belyi@users.sourceforge.net>
3 # Copyright (C) 2002 John Goerzen <jgoerzen@complete.org>
4 #
5 #    This library is free software; you can redistribute it and/or
6 #    modify it under the terms of the GNU Lesser General Public
7 #    License as published by the Free Software Foundation; either
8 #    version 2.1 of the License, or (at your option) any later version.
9 #
10 #    This library is distributed in the hope that it will be useful,
11 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #    Lesser General Public License for more details.
14 #
15 #    You should have received a copy of the GNU Lesser General Public
16 #    License along with this library; if not, write to the Free Software
17 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
18
19 """Core functionality
20
21 Core functionality of GPGME wrapped in a object-oriented fashion.
22 Provides the 'Context' class for performing cryptographic operations,
23 and the 'Data' class describing buffers of data.
24
25 """
26
27 import weakref
28 from . import pygpgme
29 from .errors import errorcheck, GPGMEError
30 from . import errors
31
32 class GpgmeWrapper(object):
33     """Base wrapper class
34
35     Not to be instantiated directly.
36
37     """
38
39     def __init__(self, wrapped):
40         self._callback_excinfo = None
41         self.wrapped = wrapped
42
43     def __repr__(self):
44         return '<instance of %s.%s with GPG object at %s>' % \
45                (__name__, self.__class__.__name__,
46                 self.wrapped)
47
48     def __str__(self):
49         return repr(self)
50
51     def __hash__(self):
52         return hash(repr(self.wrapped))
53
54     def __eq__(self, other):
55         if other == None:
56             return False
57         else:
58             return repr(self.wrapped) == repr(other.wrapped)
59
60     def _getctype(self):
61         """Must be implemented by child classes.
62
63         Must return the name of the c type."""
64         raise NotImplementedError()
65
66     def _getnameprepend(self):
67         """Must be implemented by child classes.
68
69         Must return the prefix of all c functions mapped to methods of
70         this class."""
71         raise NotImplementedError()
72
73     def _errorcheck(self, name):
74         """Must be implemented by child classes.
75
76         This function must return a trueish value for all c functions
77         returning gpgme_error_t."""
78         raise NotImplementedError()
79
80     def __getattr__(self, key):
81         """On-the-fly generation of wrapper methods."""
82         if key[0] == '_' or self._getnameprepend() == None:
83             return None
84         name = self._getnameprepend() + key
85         func = getattr(pygpgme, name)
86
87         if self._errorcheck(name):
88             def _funcwrap(slf, *args, **kwargs):
89                 result = func(slf.wrapped, *args, **kwargs)
90                 if slf._callback_excinfo:
91                     pygpgme.pygpgme_raise_callback_exception(slf)
92                 return errorcheck(result, "Invocation of " + name)
93         else:
94             def _funcwrap(slf, *args, **kwargs):
95                 result = func(slf.wrapped, *args, **kwargs)
96                 if slf._callback_excinfo:
97                     pygpgme.pygpgme_raise_callback_exception(slf)
98                 return result
99
100         _funcwrap.__doc__ = getattr(func, "__doc__")
101
102         # Monkey-patch the class.
103         setattr(self.__class__, key, _funcwrap)
104
105         # Bind the method to 'self'.
106         def wrapper(*args, **kwargs):
107             return _funcwrap(self, *args, **kwargs)
108         _funcwrap.__doc__ = getattr(func, "__doc__")
109
110         return wrapper
111
112 class Context(GpgmeWrapper):
113     """Context for cryptographic operations
114
115     All cryptographic operations in GPGME are performed within a
116     context, which contains the internal state of the operation as
117     well as configuration parameters.  By using several contexts you
118     can run several cryptographic operations in parallel, with
119     different configuration.
120
121     Access to a context must be synchronized.
122
123     """
124
125     def _getctype(self):
126         return 'gpgme_ctx_t'
127
128     def _getnameprepend(self):
129         return 'gpgme_'
130
131     def _errorcheck(self, name):
132         """This function should list all functions returning gpgme_error_t"""
133         if (name.startswith('gpgme_op_') and \
134             not name.endswith('_result')) or \
135             name == 'gpgme_signers_add' or \
136             name == 'gpgme_set_locale' or \
137             name == 'gpgme_set_keylist_mode' or \
138             name == 'gpgme_set_protocol':
139             return 1
140         return 0
141
142     def __init__(self, wrapped=None):
143         if wrapped:
144             self.own = False
145         else:
146             tmp = pygpgme.new_gpgme_ctx_t_p()
147             errorcheck(pygpgme.gpgme_new(tmp))
148             wrapped = pygpgme.gpgme_ctx_t_p_value(tmp)
149             pygpgme.delete_gpgme_ctx_t_p(tmp)
150             self.own = True
151         super().__init__(wrapped)
152         self.last_passcb = None
153         self.last_progresscb = None
154         self.last_statuscb = None
155
156     def __del__(self):
157         if not pygpgme:
158             # At interpreter shutdown, pygpgme is set to NONE.
159             return
160
161         self._free_passcb()
162         self._free_progresscb()
163         self._free_statuscb()
164         if self.own and self.wrapped and pygpgme.gpgme_release:
165             pygpgme.gpgme_release(self.wrapped)
166             self.wrapped = None
167
168     # Implement the context manager protocol.
169     def __enter__(self):
170         return self
171     def __exit__(self, type, value, tb):
172         self.__del__()
173
174     def _free_passcb(self):
175         if self.last_passcb != None:
176             if pygpgme.pygpgme_clear_generic_cb:
177                 pygpgme.pygpgme_clear_generic_cb(self.last_passcb)
178             if pygpgme.delete_PyObject_p_p:
179                 pygpgme.delete_PyObject_p_p(self.last_passcb)
180             self.last_passcb = None
181
182     def _free_progresscb(self):
183         if self.last_progresscb != None:
184             if pygpgme.pygpgme_clear_generic_cb:
185                 pygpgme.pygpgme_clear_generic_cb(self.last_progresscb)
186             if pygpgme.delete_PyObject_p_p:
187                 pygpgme.delete_PyObject_p_p(self.last_progresscb)
188             self.last_progresscb = None
189
190     def _free_statuscb(self):
191         if self.last_statuscb != None:
192             if pygpgme.pygpgme_clear_generic_cb:
193                 pygpgme.pygpgme_clear_generic_cb(self.last_statuscb)
194             if pygpgme.delete_PyObject_p_p:
195                 pygpgme.delete_PyObject_p_p(self.last_statuscb)
196             self.last_statuscb = None
197
198     def op_keylist_all(self, *args, **kwargs):
199         self.op_keylist_start(*args, **kwargs)
200         key = self.op_keylist_next()
201         while key:
202             yield key
203             key = self.op_keylist_next()
204
205     def op_keylist_next(self):
206         """Returns the next key in the list created
207         by a call to op_keylist_start().  The object returned
208         is of type Key."""
209         ptr = pygpgme.new_gpgme_key_t_p()
210         try:
211             errorcheck(pygpgme.gpgme_op_keylist_next(self.wrapped, ptr))
212             key = pygpgme.gpgme_key_t_p_value(ptr)
213         except errors.GPGMEError as excp:
214             key = None
215             if excp.getcode() != errors.EOF:
216                 raise excp
217         pygpgme.delete_gpgme_key_t_p(ptr)
218         if key:
219             key.__del__ = lambda self: pygpgme.gpgme_key_unref(self)
220             return key
221
222     def get_key(self, fpr, secret):
223         """Return the key corresponding to the fingerprint 'fpr'"""
224         ptr = pygpgme.new_gpgme_key_t_p()
225         errorcheck(pygpgme.gpgme_get_key(self.wrapped, fpr, ptr, secret))
226         key = pygpgme.gpgme_key_t_p_value(ptr)
227         pygpgme.delete_gpgme_key_t_p(ptr)
228         if key:
229             key.__del__ = lambda self: pygpgme.gpgme_key_unref(self)
230             return key
231
232     def op_trustlist_all(self, *args, **kwargs):
233         self.op_trustlist_start(*args, **kwargs)
234         trust = self.ctx.op_trustlist_next()
235         while trust:
236             yield trust
237             trust = self.ctx.op_trustlist_next()
238
239     def op_trustlist_next(self):
240         """Returns the next trust item in the list created
241         by a call to op_trustlist_start().  The object returned
242         is of type TrustItem."""
243         ptr = pygpgme.new_gpgme_trust_item_t_p()
244         try:
245             errorcheck(pygpgme.gpgme_op_trustlist_next(self.wrapped, ptr))
246             trust = pygpgme.gpgme_trust_item_t_p_value(ptr)
247         except errors.GPGMEError as excp:
248             trust = None
249             if excp.getcode() != errors.EOF:
250                 raise
251         pygpgme.delete_gpgme_trust_item_t_p(ptr)
252         return trust
253
254     def set_passphrase_cb(self, func, hook=None):
255         """Sets the passphrase callback to the function specified by func.
256
257         When the system needs a passphrase, it will call func with three args:
258         hint, a string describing the key it needs the passphrase for;
259         desc, a string describing the passphrase it needs;
260         prev_bad, a boolean equal True if this is a call made after
261         unsuccessful previous attempt.
262
263         If hook has a value other than None it will be passed into the func
264         as a forth argument.
265
266         Please see the GPGME manual for more information.
267         """
268         self._free_passcb()
269         if func == None:
270             hookdata = None
271         else:
272             self.last_passcb = pygpgme.new_PyObject_p_p()
273             if hook == None:
274                 hookdata = (weakref.ref(self), func)
275             else:
276                 hookdata = (weakref.ref(self), func, hook)
277         pygpgme.pygpgme_set_passphrase_cb(self.wrapped, hookdata, self.last_passcb)
278
279     def set_progress_cb(self, func, hook=None):
280         """Sets the progress meter callback to the function specified by FUNC.
281         If FUNC is None, the callback will be cleared.
282
283         This function will be called to provide an interactive update
284         of the system's progress.  The function will be called with
285         three arguments, type, total, and current.  If HOOK is not
286         None, it will be supplied as fourth argument.
287
288         Please see the GPGME manual for more information.
289
290         """
291         self._free_progresscb()
292         if func == None:
293             hookdata = None
294         else:
295             self.last_progresscb = pygpgme.new_PyObject_p_p()
296             if hook == None:
297                 hookdata = (weakref.ref(self), func)
298             else:
299                 hookdata = (weakref.ref(self), func, hook)
300         pygpgme.pygpgme_set_progress_cb(self.wrapped, hookdata, self.last_progresscb)
301
302     def set_status_cb(self, func, hook=None):
303         """Sets the status callback to the function specified by FUNC.  If
304         FUNC is None, the callback will be cleared.
305
306         The function will be called with two arguments, keyword and
307         args.  If HOOK is not None, it will be supplied as third
308         argument.
309
310         Please see the GPGME manual for more information.
311
312         """
313         self._free_statuscb()
314         if func == None:
315             hookdata = None
316         else:
317             self.last_statuscb = pygpgme.new_PyObject_p_p()
318             if hook == None:
319                 hookdata = (weakref.ref(self), func)
320             else:
321                 hookdata = (weakref.ref(self), func, hook)
322         pygpgme.pygpgme_set_status_cb(self.wrapped, hookdata,
323                                       self.last_statuscb)
324
325     def get_engine_info(self):
326         """Returns this context specific engine info"""
327         return pygpgme.gpgme_ctx_get_engine_info(self.wrapped)
328
329     def set_engine_info(self, proto, file_name, home_dir=None):
330         """Changes the configuration of the crypto engine implementing the
331     protocol 'proto' for the context. 'file_name' is the file name of
332     the executable program implementing this protocol. 'home_dir' is the
333     directory name of the configuration directory (engine's default is
334     used if omitted)."""
335         errorcheck(pygpgme.gpgme_ctx_set_engine_info(self.wrapped, proto, file_name, home_dir))
336
337     def wait(self, hang):
338         """Wait for asynchronous call to finish. Wait forever if hang is True.
339         Raises an exception on errors.
340
341         Please read the GPGME manual for more information.
342
343         """
344         ptr = pygpgme.new_gpgme_error_t_p()
345         pygpgme.gpgme_wait(self.wrapped, ptr, hang)
346         status = pygpgme.gpgme_error_t_p_value(ptr)
347         pygpgme.delete_gpgme_error_t_p(ptr)
348         errorcheck(status)
349
350     def op_edit(self, key, func, fnc_value, out):
351         """Start key editing using supplied callback function"""
352         if key == None:
353             raise ValueError("op_edit: First argument cannot be None")
354         if fnc_value:
355             opaquedata = (weakref.ref(self), func, fnc_value)
356         else:
357             opaquedata = (weakref.ref(self), func)
358
359         result = pygpgme.gpgme_op_edit(self.wrapped, key, opaquedata, out)
360         if self._callback_excinfo:
361             pygpgme.pygpgme_raise_callback_exception(self)
362         errorcheck(result)
363
364 class Data(GpgmeWrapper):
365     """Data buffer
366
367     A lot of data has to be exchanged between the user and the crypto
368     engine, like plaintext messages, ciphertext, signatures and
369     information about the keys.  The technical details about
370     exchanging the data information are completely abstracted by
371     GPGME.  The user provides and receives the data via `gpgme_data_t'
372     objects, regardless of the communication protocol between GPGME
373     and the crypto engine in use.
374
375     This Data class is the implementation of the GpgmeData objects.
376
377     Please see the information about __init__ for instantiation.
378
379     """
380
381     def _getctype(self):
382         return 'gpgme_data_t'
383
384     def _getnameprepend(self):
385         return 'gpgme_data_'
386
387     def _errorcheck(self, name):
388         """This function should list all functions returning gpgme_error_t"""
389         return name not in {
390             'gpgme_data_release_and_get_mem',
391             'gpgme_data_get_encoding',
392             'gpgme_data_seek',
393             'gpgme_data_get_file_name',
394         }
395
396     def __init__(self, string=None, file=None, offset=None,
397                  length=None, cbs=None, copy=True):
398         """Initialize a new gpgme_data_t object.
399
400         If no args are specified, make it an empty object.
401
402         If string alone is specified, initialize it with the data
403         contained there.
404
405         If file, offset, and length are all specified, file must
406         be either a filename or a file-like object, and the object
407         will be initialized by reading the specified chunk from the file.
408
409         If cbs is specified, it MUST be a tuple of the form:
410
411         (read_cb, write_cb, seek_cb, release_cb[, hook])
412
413         where the first four items are functions implementing reading,
414         writing, seeking the data, and releasing any resources once
415         the data object is deallocated.  The functions must match the
416         following prototypes:
417
418             def read(amount, hook=None):
419                 return <a b"bytes" object>
420
421             def write(data, hook=None):
422                 return <the number of bytes written>
423
424             def seek(offset, whence, hook=None):
425                 return <the new file position>
426
427             def release(hook=None):
428                 <return value and exceptions are ignored>
429
430         The functions may be bound methods.  In that case, you can
431         simply use the 'self' reference instead of using a hook.
432
433         If file is specified without any other arguments, then
434         it must be a filename, and the object will be initialized from
435         that file.
436
437         """
438         super().__init__(None)
439         self.data_cbs = None
440
441         if cbs != None:
442             self.new_from_cbs(*cbs)
443         elif string != None:
444             self.new_from_mem(string, copy)
445         elif file != None and offset != None and length != None:
446             self.new_from_filepart(file, offset, length)
447         elif file != None:
448             if type(file) == type("x"):
449                 self.new_from_file(file, copy)
450             else:
451                 self.new_from_fd(file)
452         else:
453             self.new()
454
455     def __del__(self):
456         if not pygpgme:
457             # At interpreter shutdown, pygpgme is set to NONE.
458             return
459
460         if self.wrapped != None and pygpgme.gpgme_data_release:
461             pygpgme.gpgme_data_release(self.wrapped)
462             if self._callback_excinfo:
463                 pygpgme.pygpgme_raise_callback_exception(self)
464             self.wrapped = None
465         self._free_datacbs()
466
467     # Implement the context manager protocol.
468     def __enter__(self):
469         return self
470     def __exit__(self, type, value, tb):
471         self.__del__()
472
473     def _free_datacbs(self):
474         if self.data_cbs != None:
475             if pygpgme.pygpgme_clear_generic_cb:
476                 pygpgme.pygpgme_clear_generic_cb(self.data_cbs)
477             if pygpgme.delete_PyObject_p_p:
478                 pygpgme.delete_PyObject_p_p(self.data_cbs)
479             self.data_cbs = None
480
481     def new(self):
482         tmp = pygpgme.new_gpgme_data_t_p()
483         errorcheck(pygpgme.gpgme_data_new(tmp))
484         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
485         pygpgme.delete_gpgme_data_t_p(tmp)
486
487     def new_from_mem(self, string, copy=True):
488         tmp = pygpgme.new_gpgme_data_t_p()
489         errorcheck(pygpgme.gpgme_data_new_from_mem(tmp,string,len(string),copy))
490         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
491         pygpgme.delete_gpgme_data_t_p(tmp)
492
493     def new_from_file(self, filename, copy=True):
494         tmp = pygpgme.new_gpgme_data_t_p()
495         try:
496             errorcheck(pygpgme.gpgme_data_new_from_file(tmp, filename, copy))
497         except errors.GPGMEError as e:
498             if e.getcode() == errors.INV_VALUE and not copy:
499                 raise ValueError("delayed reads are not yet supported")
500             else:
501                 raise e
502         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
503         pygpgme.delete_gpgme_data_t_p(tmp)
504
505     def new_from_cbs(self, read_cb, write_cb, seek_cb, release_cb, hook=None):
506         assert self.data_cbs == None
507         self.data_cbs = pygpgme.new_PyObject_p_p()
508         tmp = pygpgme.new_gpgme_data_t_p()
509         if hook != None:
510             hookdata = (weakref.ref(self),
511                         read_cb, write_cb, seek_cb, release_cb, hook)
512         else:
513             hookdata = (weakref.ref(self),
514                         read_cb, write_cb, seek_cb, release_cb)
515         errorcheck(
516             pygpgme.pygpgme_data_new_from_cbs(tmp, hookdata, self.data_cbs))
517         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
518         pygpgme.delete_gpgme_data_t_p(tmp)
519
520     def new_from_filepart(self, file, offset, length):
521         """This wraps the GPGME gpgme_data_new_from_filepart() function.
522         The argument "file" may be:
523
524         * a string specifying a file name, or
525         * a file-like object supporting the fileno() and the mode attribute.
526
527         """
528
529         tmp = pygpgme.new_gpgme_data_t_p()
530         filename = None
531         fp = None
532
533         if type(file) == type("x"):
534             filename = file
535         else:
536             fp = pygpgme.fdopen(file.fileno(), file.mode)
537             if fp == None:
538                 raise ValueError("Failed to open file from %s arg %s" % \
539                       (str(type(file)), str(file)))
540
541         errorcheck(pygpgme.gpgme_data_new_from_filepart(tmp, filename, fp,
542                                                       offset, length))
543         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
544         pygpgme.delete_gpgme_data_t_p(tmp)
545
546     def new_from_fd(self, file):
547         """This wraps the GPGME gpgme_data_new_from_fd() function.  The
548         argument "file" must be a file-like object, supporting the
549         fileno() method.
550
551         """
552         tmp = pygpgme.new_gpgme_data_t_p()
553         errorcheck(pygpgme.gpgme_data_new_from_fd(tmp, file.fileno()))
554         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
555         pygpgme.delete_gpgme_data_t_p(tmp)
556
557     def new_from_stream(self, file):
558         """This wrap around gpgme_data_new_from_stream is an alias for
559         new_from_fd() method since in python there's not difference
560         between file stream and file descriptor"""
561         self.new_from_fd(file)
562
563     def write(self, buffer):
564         """Write buffer given as string or bytes.
565
566         If a string is given, it is implicitly encoded using UTF-8."""
567         written = pygpgme.gpgme_data_write(self.wrapped, buffer)
568         if written < 0:
569             if self._callback_excinfo:
570                 pygpgme.pygpgme_raise_callback_exception(self)
571             else:
572                 raise GPGMEError.fromSyserror()
573         return written
574
575     def read(self, size = -1):
576         """Read at most size bytes, returned as bytes.
577
578         If the size argument is negative or omitted, read until EOF is reached.
579
580         Returns the data read, or the empty string if there was no data
581         to read before EOF was reached."""
582
583         if size == 0:
584             return ''
585
586         if size > 0:
587             try:
588                 result = pygpgme.gpgme_data_read(self.wrapped, size)
589             except:
590                 if self._callback_excinfo:
591                     pygpgme.pygpgme_raise_callback_exception(self)
592                 else:
593                     raise
594             return result
595         else:
596             chunks = []
597             while True:
598                 try:
599                     result = pygpgme.gpgme_data_read(self.wrapped, 4096)
600                 except:
601                     if self._callback_excinfo:
602                         pygpgme.pygpgme_raise_callback_exception(self)
603                     else:
604                         raise
605                 if len(result) == 0:
606                     break
607                 chunks.append(result)
608             return b''.join(chunks)
609
610 def pubkey_algo_name(algo):
611     return pygpgme.gpgme_pubkey_algo_name(algo)
612
613 def hash_algo_name(algo):
614     return pygpgme.gpgme_hash_algo_name(algo)
615
616 def get_protocol_name(proto):
617     return pygpgme.gpgme_get_protocol_name(proto)
618
619 def check_version(version=None):
620     return pygpgme.gpgme_check_version(version)
621
622 # check_version also makes sure that several subsystems are properly
623 # initialized, and it must be run at least once before invoking any
624 # other function.  We do it here so that the user does not have to do
625 # it unless she really wants to check for a certain version.
626 check_version()
627
628 def engine_check_version (proto):
629     try:
630         errorcheck(pygpgme.gpgme_engine_check_version(proto))
631         return True
632     except errors.GPGMEError:
633         return False
634
635 def get_engine_info():
636     ptr = pygpgme.new_gpgme_engine_info_t_p()
637     try:
638         errorcheck(pygpgme.gpgme_get_engine_info(ptr))
639         info = pygpgme.gpgme_engine_info_t_p_value(ptr)
640     except errors.GPGMEError:
641         info = None
642     pygpgme.delete_gpgme_engine_info_t_p(ptr)
643     return info
644
645 def set_engine_info(proto, file_name, home_dir=None):
646     """Changes the default configuration of the crypto engine implementing
647     the protocol 'proto'. 'file_name' is the file name of
648     the executable program implementing this protocol. 'home_dir' is the
649     directory name of the configuration directory (engine's default is
650     used if omitted)."""
651     errorcheck(pygpgme.gpgme_set_engine_info(proto, file_name, home_dir))
652
653 def set_locale(category, value):
654     """Sets the default locale used by contexts"""
655     errorcheck(pygpgme.gpgme_set_locale(None, category, value))
656
657 def wait(hang):
658     """Wait for asynchronous call on any Context  to finish.
659     Wait forever if hang is True.
660
661     For finished anynch calls it returns a tuple (status, context):
662         status  - status return by asnynchronous call.
663         context - context which caused this call to return.
664
665     Please read the GPGME manual of more information."""
666     ptr = pygpgme.new_gpgme_error_t_p()
667     context = pygpgme.gpgme_wait(None, ptr, hang)
668     status = pygpgme.gpgme_error_t_p_value(ptr)
669     pygpgme.delete_gpgme_error_t_p(ptr)
670     if context == None:
671         errorcheck(status)
672     else:
673         context = Context(context)
674     return (status, context)