python: doc: Add suffix for org files.
[gpgme.git] / lang / python / doc / src / gpgme-python-howto.org
index 07026b8..e2e7e71 100644 (file)
@@ -1,3 +1,4 @@
+# -*- mode: org -*-
 #+TITLE: GNU Privacy Guard (GnuPG) Made Easy Python Bindings HOWTO (English)
 #+AUTHOR: Ben McGinnes
 #+LATEX_COMPILER: xelatex
   :CUSTOM_ID: intro
   :END:
 
-| Version:        | 0.1.4                                    |
-| GPGME Version:  | 1.12.0                                   |
-| Author:         | [[https://gnupg.org/people/index.html#sec-1-5][Ben McGinnes]] <ben@gnupg.org>             |
+| Version:        | 0.1.5                                    |
+| GPGME Version:  | 1.13.0                                   |
+| Author:         | Ben McGinnes <ben@gnupg.org>             |
 | Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D |
 | Language:       | Australian English, British English      |
-| xml:lang:       | en-AU, en-GB, en                         |
+| Language codes: | en-AU, en-GB, en                         |
 
 This document provides basic instruction in how to use the GPGME
 Python bindings to programmatically leverage the GPGME library.
@@ -76,78 +77,25 @@ major releases.
    :CUSTOM_ID: new-stuff
    :END:
 
-The most obviously new point for those reading this guide is this
-section on other new things, but that's hardly important.  Not given
-all the other things which spurred the need for adding this section
-and its subsections.
+Full details of what is new are now available in the [[file:what-is-new.org][What's New]] file
+and archives of the preceding /What's New/ sections are available in
+the [[file:what-was-new][What Was New]] file.
+
+
+*** New in GPGME 1·13·0
+    :PROPERTIES:
+    :CUSTOM_ID: new-stuff-1-13-0
+    :END:
+
+See the [[file:what-is-new#new-stuff-1-13-0][What's New]] document for what is new in version 1.13.0.
+
 
 *** New in GPGME 1·12·0
     :PROPERTIES:
     :CUSTOM_ID: new-stuff-1-12-0
     :END:
 
-There have been quite a number of additions to GPGME and the Python
-bindings to it since the last release of GPGME with versions 1.11.0
-and 1.11.1 in April, 2018.
-
-The bullet points of new additiions are:
-
-- an expanded section on [[#installation][installing]] and [[#snafu][troubleshooting]] the Python
-  bindings.
-- The release of Python 3.7.0; which appears to be working just fine
-  with our bindings, in spite of intermittent reports of problems for
-  many other Python projects with that new release.
-- Python 3.7 has been moved to the head of the specified python
-  versions list in the build process.
-- In order to fix some other issues, there are certain underlying
-  functions which are more exposed through the [[#howto-get-context][gpg.Context()]], but
-  ongoing documentation ought to clarify that or otherwise provide the
-  best means of using the bindings.  Some additions to =gpg.core= and
-  the =Context()=, however, were intended (see below).
-- Continuing work in identifying and confirming the cause of
-  oft-reported [[#snafu-runtime-not-funtime][problems installing the Python bindings on Windows]].
-- GSOC: Google's Surreptitiously Ordered Conscription ... erm ... oh,
-  right; Google's Summer of Code.  Though there were two hopeful
-  candidates this year; only one ended up involved with the GnuPG
-  Project directly, the other concentrated on an unrelated third party
-  project with closer ties to one of the GNU/Linux distributions than
-  to the GnuPG Project.  Thus the Python bindings benefited from GSOC
-  participant Jacob Adams, who added the key_import function; building
-  on prior work by Tobias Mueller.
-- Several new methods functions were added to the gpg.Context(),
-  including: [[#howto-import-key][key_import]], [[#howto-export-key][key_export]], [[#howto-export-public-key][key_export_minimal]] and
-  [[#howto-export-secret-key][key_export_secret]].
-- Importing and exporting examples include versions integrated with
-  Marcel Fest's recently released [[https://github.com/Selfnet/hkp4py][HKP for Python]] module.  Some
-  [[#hkp4py][additional notes on this module]] are included at the end of the HOWTO.
-- Instructions for dealing with semi-walled garden implementations
-  like ProtonMail are also included.  This is intended to make things
-  a little easier when communicating with users of ProtonMail's
-  services and should not be construed as an endorsement of said
-  service.  The GnuPG Project neither favours, nor disfavours
-  ProtonMail and the majority of this deals with interacting with the
-  ProtonMail keyserver.
-- Semi-formalised the location where [[#draft-editions][draft versions]] of this HOWTO may
-  periodically be accessible.  This is both for the reference of
-  others and testing the publishing of the document itself.  Renamed
-  this file at around the same time.
-- The Texinfo documentation build configuration has been replicated
-  from the parent project in order to make to maintain consistency
-  with that project (and actually ship with each release).
-- a reStructuredText (=.rst=) version is also generated for Python
-  developers more used to and comfortable with that format as it is
-  the standard Python documentation format and Python developers may
-  wish to use it with Sphinx.  Please note that there has been no
-  testing of the reStructuredText version with Sphinx at all.  The
-  reST file was generated by the simple expedient of using [[https://pandoc.org/][Pandoc]].
-- Added a new section for [[#advanced-use][advanced or experimental use]].
-- Began the advanced use cases with [[#cython][a section]] on using the module with
-  [[https://cython.org/][Cython]].
-- Added a number of new scripts to the =example/howto/= directory;
-  some of which may be in advance of their planned sections of the
-  HOWTO (and some are just there because it seemed like a good idea at
-  the time).
-- Cleaned up a lot of things under the hood.
+See the [[file:what-was-new#new-stuff-1-12-0][What Was New]] document for what was new in version 1.12.0.
 
 
 * GPGME Concepts
@@ -331,15 +279,16 @@ a significant advantage in some way.
    reach EOL soon.  In production systems and services, Python 3.6
    should be robust enough to be relied on.
 3. If possible add the following Python modules which are not part of
-   the standard library: [[http://docs.python-requests.org/en/latest/index.html][Requests]], [[https://cython.org/][Cython]] and [[https://github.com/Selfnet/hkp4py][hkp4py]].  Chances are
-   quite high that at least the first one and maybe two of those will
-   already be installed.
+   the standard library: [[http://docs.python-requests.org/en/latest/index.html][Requests]], [[https://cython.org/][Cython]], [[https://pendulum.eustace.io/][Pendulum]] and [[https://github.com/Selfnet/hkp4py][hkp4py]].
+
+Chances are quite high that at least the first one and maybe two of
+those will already be installed.
 
-Note that, as with Cython, some of the planned additions to the
-[[#advanced-use][Advanced]] section, will bring with them additional requirements.  Most
-of these will be fairly well known and commonly installed ones,
-however, which are in many cases likely to have already been installed
-on many systems or be familiar to Python programmers.
+Note that, as with Cython, some of advanced use case scenarios will
+bring with them additional requirements.  Most of these will be fairly
+well known and commonly installed ones, however, which are in many
+cases likely to have already been installed on many systems or be
+familiar to Python programmers.
 
 
 ** Installation
@@ -448,7 +397,7 @@ both by and in order to create, the bindings (including both the
 If specifying a selected number of languages to create bindings for,
 try to leave Python last.  Currently the majority of the other
 language bindings are also preceding Python of either version when
-listed alphabetically and so that just happens by default currently.
+listed alphabetically (not counting the Qt bindings).
 
 If Python is set to precede one of the other languages then it is
 possible that the errors described here may interrupt the build
@@ -457,6 +406,12 @@ these cases it may be preferable to configure all preferred language
 bindings separately with alternative =configure= steps for GPGME using
 the =--enable-languages=$LANGUAGE= option.
 
+Alternatively =make= (or =gmake=, depending on your platform) may be
+run with the the =-k= option, which tells make to keep going even if
+errors are encountered.  In that case the failure of one language's
+set of bindings to build should not hamper another language's bindings
+to build.
+
 
 *** Reinstalling Responsibly
     :PROPERTIES:
@@ -490,7 +445,7 @@ place.
     :CUSTOM_ID: snafu-the-full-monty
     :END:
 
-For a veriety of reasons it may be either necessary or just preferable
+For a variety of reasons it may be either necessary or just preferable
 to install the bindings to alternative installed Python versions which
 meet the requirements of these bindings.
 
@@ -507,6 +462,16 @@ by major version numbers and probably minor numbers too).
 On most POSIX systems, including OS X, this will very likely be the
 case in most, if not all, cases.
 
+Note that from GPGME [[https://dev.gnupg.org/rMff6ff616aea6f59b7f2ce1176492850ecdf3851e][1.12.1]] the default installation installs to each
+version of Python it can find first.  That is that it will currently
+install for the first copies of Python versions 2.7, 3.4, 3.5, 3.6,
+3.7 and 3.8 (dev branch) that it finds.  Usually this will be in the
+same prefix as GPGME itself, but is dictated by the =$PATH= when the
+installation is performed.  The above instructions can still be
+performed on other python installations which the installer does not
+find, including alternative prefixes.
+
+
 
 *** Won't Work With Windows
     :PROPERTIES:
@@ -534,12 +499,13 @@ been kept around far longer than it should have been.
 There are two theoretical solutions to this issue:
 
  1. Compile and install the GnuPG stack, including GPGME and the
-    Python bibdings using the same version of Microsoft Visual Studio
+    Python bindings using the same version of Microsoft Visual Studio
     used by the Python Foundation to compile the version of Python
     installed.
 
     If there are multiple versions of Python then this will need to be
-    done with each different version of Visual Studio used.
+    done with each different version of Visual Studio used for those
+    versions of Python.
 
  2. Compile and install Python using the same tools used by choice,
     such as MinGW or Msys2.
@@ -556,6 +522,83 @@ what.
 Investigations into the extent or the limitations of this issue are
 ongoing.
 
+The following table lists the version of Microsoft Visual Studio which
+needs to be used when compiling GPGME and the Python bindings with
+each version of the CPython binary released [[https://www.python.org/downloads/windows/][for Windows]]:
+
+| CPython | Microsoft product name | runtime filename |
+|  2.7.6  |   Visual Studio 2008   |   MSVCR90.DLL    |
+|  3.4.0  |   Visual Studio 2010   |   MSVCR100.DLL   |
+|  3.5.0  |   Visual Studio 2015   |   *see below*    |
+|  3.6.0  |   Visual Studio 2015   |   *see below*    |
+|  3.7.0  |   Visual Studio 2017*  |   *see below*    |
+
+It is important to note that MingW and Msys2 ship with the Visual C
+runtime from Microsoft Visual Studio 2005 and are thus *incompatible*
+with all the versions of CPython which can be used with the GPGME
+Python bindings.
+
+It is also important to note that from CPython 3.5 onwards, the Python
+Foundation has adopted the reworking of the Visual C runtime which was
+performed for Visual Studio 2015 and aimed at resolving many of these
+kinds of issues.  Much greater detail on these issues and the correct
+file(s) to link to are available from Matthew Brett's invaluable page,
+[[https://matthew-brett.github.io/pydagogue/python_msvc.html][Using Microsoft Visual C with Python]].  It is also worth reading the
+Microsoft Developer Network blog post on [[http://blogs.msdn.com/b/vcblog/archive/2015/03/03/introducing-the-universal-crt.aspx][the universal CRT]] and Steve
+Dower's blog posts on Python extensions ([[http://stevedower.id.au/blog/building-for-python-3-5][part 1]] and [[http://stevedower.id.au/blog/building-for-python-3-5-part-two][part 2]]).
+
+The second of those two posts by Steve Dower contains the details of
+specific configuration options required for compiling anything to be
+used with official CPython releases.  In addition to those
+configuration and compiler settings to use, the versions of Visual
+Studio prior to Visual Studio 2015 did not support 64-bit systems by
+default.  So compiling a 64-bit version of these bindings for a 64-bit
+version of CPython 2.7 or 3.4 requires additional work.
+
+In addition to the blog posts, the [[https://wiki.python.org/moin/WindowsCompilers][Windows compilers]] wiki page on the
+CPython wiki is another essential reference on the relevant versions
+of Visual Studio to use and the degree of compatibility with CPython
+releases.
+
+Eventually someone will ask why there isn't an installable binary for
+Windows, which the GPGME of the licenses do not preclude as long as
+the source code is available in conjunction with such a release.
+
+The sheer number of versions of Visual Studio in conjunction with
+differing configuration options depending on the target Windows
+version and whether the architecture is 64-bit or 32-bit makes it
+difficult to provide a correct binary installer for Windows users.  At
+the bare minimum doing so would require the GnuPG project compile ten
+different versions of the bindings with each release; both 32-bit and
+64-bit versions for CPython 2.7 and 3.4, with 64-bit versions for both
+x86-64 (i.e. Intel and AMD) and ARM architectures for CPython 3.5,
+3.6, 3.7 and later releases.  That's the bare *minimum*, it'd probably
+be higher.
+
+Additionally, with only a binary installation used in conjunction with
+the CPython installer from =python.org= the advanced options available
+which utilise [[#cython][Cython]] will not be able to be used at all.  Cython
+depends on being able to compile the C code it generates and that too
+would need to utilise a matching runtime to both the installed version
+of CPython and these bindings in order to work with the bindings.
+
+Considering all of that, what do we recommend?
+
+ 1. Use a recent version of CPython; at least 3.5, but ideally 3.6 or
+    later.
+
+ 2. Use Visual Studio 2015 or the standalone build tools for Visual
+    Studio 2017 (or later).
+
+ 3. Compile both CPython and GPGME with these bindings using the tools
+    selected in step 2.
+
+ 4. Ignore MingW, Msys2 and the official CPython binary installers.
+
+ 5. Be thankful the answer to this question wasn't simply to say
+    something like, “install Linux” or “install FreeBSD” (or even
+    Apple's OS X).
+
 
 *** CFFI is the Best™ and GPGME should use it instead of SWIG
     :PROPERTIES:
@@ -625,10 +668,10 @@ to or which are found by GPGME's configuration stage immediately prior
 to running the make commands.  Which is exactly what the compiling and
 installing process of GPGME does by default.
 
-Once that is done, however, it appears that a copy the compiled module
-may be installed into a virtualenv of the same major and minor version
-matching the build.  Alternatively it is possible to utilise a
-=sites.pth= file in the =site-packages/= directory of a viertualenv
+Once that is done, however, it appears that a copy of the compiled
+module may be installed into a virtualenv of the same major and minor
+version matching the build.  Alternatively it is possible to utilise a
+=sites.pth= file in the =site-packages/= directory of a virtualenv
 installation, which links back to the system installations
 corresponding directory in order to import anything installed system
 wide.  This may or may not be appropriate on a case by case basis.
@@ -662,6 +705,22 @@ the command =python3 -m virtualenv /path/to/install/virtual/thingy=
 instead.
 
 
+*** Post installation
+    :PROPERTIES:
+    :CUSTOM_ID: snafu-docs
+    :END:
+
+Following installation it is recommended to move the
+=post_installer.py= script from the =lang/python/examples/howto/=
+directory to the =lang/python/= directory and run it.  This will fix
+or restore files needed by Sphinx which may be removed during a
+distribution build for release.  It will also generate reST files from
+Org mode files with Pandoc and generate Texinfo files from Org mode
+files with GNU Emacs and Org mode (in batch mode).  Additionally it
+will fix the UTF-8 declaration line in the Texinfo files (Emacs
+expects "UTF-8" to be "utf-8").
+
+
 * Fundamentals
   :PROPERTIES:
   :CUSTOM_ID: howto-fund-a-mental
@@ -926,6 +985,14 @@ relative ease by which such key IDs can be reproduced, as demonstrated
 by the Evil32 Project in 2014 (which was subsequently exploited in
 2016).
 
+Testing for whether a string in any given search is or may be a
+hexadecimal value which may be missing the leading =0x= is a simple
+matter of using a try/except statement which attempts to convert the
+string as hex to an integer and then back to hex; then using that to
+search with.  Raising a ValueError simply results in treating the
+string as a string.  This is the method and logic utilised in the
+=import-keys-hkp.py= script (see below).
+
 
 *** Working with ProtonMail
     :PROPERTIES:
@@ -1060,6 +1127,7 @@ import sys
 c = gpg.Context()
 server = hkp4py.KeyServer("hkps://hkps.pool.sks-keyservers.net")
 results = []
+keys = []
 
 if len(sys.argv) > 2:
     pattern = " ".join(sys.argv[1:])
@@ -1068,22 +1136,56 @@ elif len(sys.argv) == 2:
 else:
     pattern = input("Enter the pattern to search for keys or user IDs: ")
 
-try:
-    keys = server.search(pattern)
-    print("Found {0} key(s).".format(len(keys)))
-except Exception as e:
-    keys = []
+
+if pattern is not None:
+    try:
+        key = server.search(hex(int(pattern, 16)))
+        keyed = True
+    except ValueError as ve:
+        key = server.search(pattern)
+        keyed = False
+
+    if key is not None:
+        keys.append(key[0])
+        if keyed is True:
+            try:
+                fob = server.search(pattern)
+            except:
+                fob = None
+            if fob is not None:
+                keys.append(fob[0])
+        else:
+            pass
+    else:
+        pass
+
     for logrus in pattern.split():
-        if logrus.startswith("0x") is True:
+        try:
+            key = server.search(hex(int(logrus, 16)))
+            hexed = True
+        except ValueError as ve:
             key = server.search(logrus)
+            hexed = False
+
+        if key is not None:
+            keys.append(key[0])
+            if hexed is True:
+                try:
+                    fob = server.search(logrus)
+                except:
+                    fob = None
+                if fob is not None:
+                    keys.append(fob[0])
+            else:
+                pass
         else:
-            key = server.search("0x{0}".format(logrus))
-        keys.append(key[0])
-    print("Found {0} key(s).".format(len(keys)))
+            pass
 
-for key in keys:
-    import_result = c.key_import(key.key_blob)
-    results.append(import_result)
+
+if len(keys) > 0:
+    for key in keys:
+        import_result = c.key_import(key.key_blob)
+        results.append(import_result)
 
 for result in results:
     if result is not None and hasattr(result, "considered") is False:
@@ -1150,7 +1252,7 @@ address can be obtained.
      :CUSTOM_ID: import-hkp4py-pm1
      :END:
 
-The following script is avalable with the rest of the examples under
+The following script is available with the rest of the examples under
 the somewhat less than original name, =pmkey-import-hkp.py=.
 
 #+BEGIN_SRC python -i
@@ -1911,7 +2013,7 @@ with open("secret_plans.txt.asc", "wb") as afile:
     afile.write(ciphertext)
 #+END_SRC
 
-If the =recipients= paramater is empty then the plaintext is encrypted
+If the =recipients= parameter is empty then the plaintext is encrypted
 symmetrically.  If no =passphrase= is supplied as a parameter or via a
 callback registered with the =Context()= then an out-of-band prompt
 for the passphrase via pinentry will be invoked.
@@ -2058,6 +2160,10 @@ content as a byte object, the recipient key IDs and algorithms in
 =result= and the results of verifying any signatures of the data in
 =verify_result=.
 
+If =gpg.Context().decrypt(cfile, verify=False)= is called instead,
+then =verify_result= will be returned as =None= and the rest remains
+as described here.
+
 
 ** Signing text and files
    :PROPERTIES:
@@ -2622,7 +2728,7 @@ Unsurprisingly the result of this is:
 #+END_SRC
 
 
-*** Revokinging User IDs
+*** Revoking User IDs
     :PROPERTIES:
     :CUSTOM_ID: keygen-uids-revoke
     :END:
@@ -2682,6 +2788,82 @@ c.key_sign(key, uids=uid, expires_in=2764800)
 #+END_SRC
 
 
+*** Verifying key certifications
+    :PROPERTIES:
+    :CUSTOM_ID: key-sign-verify
+    :END:
+
+#+BEGIN_SRC python -i
+import gpg
+import time
+
+c = gpg.Context()
+dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+keys = list(c.keylist(pattern=dmuid, mode=gpg.constants.keylist.mode.SIGS))
+key = keys[0]
+
+for user in key.uids:
+    for sig in user.signatures:
+        print("0x{0}".format(sig.keyid), "", time.ctime(sig.timestamp), "",
+              sig.uid)
+#+END_SRC
+
+Which for Danger Mouse displays the following:
+
+#+BEGIN_EXAMPLE
+  0x92E3F6115435C65A  Thu Mar 15 13:17:44 2018  Danger Mouse <dm@secret.example.net>
+  0x321E4E2373590E5D  Mon Nov 26 12:46:05 2018  Ben McGinnes <ben@adversary.org>
+#+END_EXAMPLE
+
+The two key signatures listed are for the self-certification of Danger
+Mouse's key made when the key was created in March, 2018; and the
+second is a signature made by the author and set to expire at the end
+of the year.  Note that the second signature was made with the
+following code (including the preceding code to display the output of
+the certifications or key signatures):
+
+#+BEGIN_SRC python -i
+import gpg
+import math
+import pendulum
+import time
+
+hd = "/home/dm/.gnupg"
+c = gpg.Context()
+d = gpg.Context(home_dir=hd)
+dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+dmuid = "Danger Mouse <dm@secret.example.net>"
+dkeys = list(c.keylist(pattern=dmuid))
+dmkey = dkeys[0]
+
+c.key_import(d.key_export(pattern=None))
+
+tp = pendulum.period(pendulum.now(tz="local"), pendulum.datetime(2019, 1, 1))
+ts = tp.total_seconds()
+total_secs = math.ceil(ts)
+c.key_sign(dmkey, uids=dmuid, expires_in=total_secs)
+
+d.key_import(c.key_export(pattern=dmuid))
+keys = list(c.keylist(pattern=dmuid, mode=gpg.constants.keylist.mode.SIGS))
+key = keys[0]
+
+for user in key.uids:
+    for sig in user.signatures:
+        print("0x{0}".format(sig.keyid), "", time.ctime(sig.timestamp), "",
+              sig.uid)
+#+END_SRC
+
+Note that this final code block includes the use of a module which is
+/not/ part of Python's standard library, the [[https://pendulum.eustace.io/][pendulum module]].  Unlike
+the standard datetime module, pendulum makes working with dates and
+times significantly easier in Python; just as the requests module
+makes working with HTTP and HTTPS easier than the builtin modules do.
+
+Though neither requests nor pendulum are required modules for using
+the GPGME Python bindings, they are both highly recommended more
+generally.
+
+
 * Advanced or Experimental Use Cases
   :PROPERTIES:
   :CUSTOM_ID: advanced-use
@@ -2704,7 +2886,7 @@ Nevertheless, there are some situations where the benefits are
 demonstrable.  One of the better and easier examples being the one of
 the early examples in this HOWTO, the [[#howto-keys-counting][key counting]] code.  Running that
 example as an executable Python script, =keycount.py= (available in
-the =examples/howto/= directory), will take a noticable amount of time
+the =examples/howto/= directory), will take a noticeable amount of time
 to run on most systems where the public keybox or keyring contains a
 few thousand public keys.
 
@@ -2840,33 +3022,29 @@ if sys.platform == "win32":
 else:
     gpgconfcmd = "gpgconf --list-options gpg"
 
-try:
-    lines = subprocess.getoutput(gpgconfcmd).splitlines()
-except:
-    process = subprocess.Popen(gpgconfcmd.split(), stdout=subprocess.PIPE)
-    procom = process.communicate()
-    if sys.version_info[0] == 2:
-        lines = procom[0].splitlines()
-    else:
-        lines = procom[0].decode().splitlines()
+process = subprocess.Popen(gpgconfcmd.split(), stdout=subprocess.PIPE)
+procom = process.communicate()
 
-for i in range(len(lines)):
-    if lines[i].startswith("group") is True:
-        line = lines[i]
-    else:
-        pass
+if sys.version_info[0] == 2:
+    lines = procom[0].splitlines()
+else:
+    lines = procom[0].decode().splitlines()
+
+for line in lines:
+    if line.startswith("group") is True:
+        break
 
 groups = line.split(":")[-1].replace('"', '').split(',')
 
 group_lines = []
 group_lists = []
 
-for i in range(len(groups)):
-    group_lines.append(groups[i].split("="))
-    group_lists.append(groups[i].split("="))
+for group in groups:
+    group_lines.append(group.split("="))
+    group_lists.append(group.split("="))
 
-for i in range(len(group_lists)):
-    group_lists[i][1] = group_lists[i][1].split()
+for glist in group_lists:
+    glist[1] = glist[1].split()
 #+END_SRC
 
 The result of that code is that =group_lines= is a list of lists where
@@ -2876,7 +3054,7 @@ is the key IDs of the group as a string.
 The =group_lists= result is very similar in that it is a list of
 lists.  The first part, =group_lists[i][0]= matches
 =group_lines[i][0]= as the name of the group, but =group_lists[i][1]=
-is the key IDs of the group as a string.
+is the key IDs of the group as a list.
 
 A demonstration of using the =groups.py= module is also available in
 the form of the executable =mutt-groups.py= script.  This second
@@ -2935,6 +3113,96 @@ available in the =lang/python/examples/howto= directory as normal; the
 executable version is the =import-keys-hkp.py= file.
 
 
+** GPGME version checking
+   :PROPERTIES:
+   :CUSTOM_ID: gpgme-version-check
+   :END:
+
+For various reasons it may be necessary to check which version of
+GPGME the bindings have been built against; including whether a
+minimum required version of GPGME is in use.
+
+For the most part the =gpg.version.versionstr= and
+=gpg.version.versionlist= methods have been quite sufficient.  The
+former returns the same string as =gpgme-config --version=, while the
+latter returns the major, minor and patch values in a list.
+
+To check if the installed bindings have actually been built against
+the current installed libgpgme version, this check can be performed:
+
+#+BEGIN_SRC python -i
+import gpg
+import subprocess
+import sys
+
+gpgme_version_call = subprocess.Popen(["gpgme-config", "--version"],
+                                      stdout=subprocess.PIPE,
+                                      stderr=subprocess.PIPE)
+gpgme_version_str = gpgme_version_call.communicate()
+
+if sys.version_info[0] == 2:
+    gpgme_version = gpgme_version_str[0].strip()
+elif sys.version_info[0] >= 3:
+    gpgme_version = gpgme_version_str[0].decode().strip()
+else:
+    gpgme_version = None
+
+if gpgme_version is not None:
+    if gpgme_version == gpg.version.versionstr:
+        print("The GPGME Python bindings match libgpgme.")
+    else:
+        print("The GPGME Python bindings do NOT match libgpgme.")
+else:
+    print("Upgrade Python and reinstall the GPGME Python bindings.")
+#+END_SRC
+
+For many developers, however, the preferred checking means checking
+for a minimum version or point release.  This is now readily available
+via the =gpg.version.versionintlist= method (added in version
+=1.12.1-beta79=).  It is also now possible to easily check whether the
+installed GPGME Python bindings were built from a development or beta
+branch of the GPGME source code.
+
+The following code demonstrates how both of those methods may be used:
+
+#+BEGIN_SRC python -i
+import gpg
+
+try:
+    if gpg.version.is_beta is True:
+        print("The installed GPGME Python bindings were built from beta code.")
+    else:
+        print("The installed GPGME Python bindings are a released version.")
+except Exception as e:
+    print(e)
+
+try:
+    if gpg.version.versionintlist[0] == 1:
+        if gpg.version.versionintlist[1] == 12:
+            if gpg.version.versionintlist[2] == 1:
+                print("This is the minimum version for using versionintlist.")
+            elif gpg.version.versionintlist[2] > 1:
+                print("The versionintlist method is available.")
+            else:
+                pass
+        elif gpg.version.versionintlist[1] > 12:
+            print("The versionintlist method is available.")
+        else:
+            pass
+    elif gpg.version.versionintlist[0] > 1:
+        print("The versionintlist method is available.")
+    else:
+        pass
+except Exception as e:
+    print(e)
+#+END_SRC
+
+The points where =pass= is used in the above example will most likely
+also produce an =Exception= error since those results should only
+occur in versions which do not have the =gpgme.version.is_beta= and
+=gpgme.version.versionintlist= methods available.
+
+
 * Copyright and Licensing
   :PROPERTIES:
   :CUSTOM_ID: copyright-and-license
@@ -2948,8 +3216,6 @@ executable version is the =import-keys-hkp.py= file.
 
 Copyright © The GnuPG Project, 2018.
 
-Copyright (C) The GnuPG Project, 2018.
-
 
 ** Draft Editions of this HOWTO
    :PROPERTIES:
@@ -2959,35 +3225,94 @@ Copyright (C) The GnuPG Project, 2018.
 Draft editions of this HOWTO may be periodically available directly
 from the author at any of the following URLs:
 
-- [[https://files.au.adversary.org/crypto/gpgme-python-howto.html][GPGME Python Bindings HOWTO draft (XHTML AWS S3 SSL)]]
-- [[http://files.au.adversary.org/crypto/gpgme-python-howto.html][GPGME Python Bindings HOWTO draft (XHTML AWS S3 no SSL)]]
-- [[https://files.au.adversary.org/crypto/gpgme-python-howto.texi][GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 SSL)]]
-- [[http://files.au.adversary.org/crypto/gpgme-python-howto.texi][GPGME Python Bindings HOWTO draft (Texinfo file AWS S3 no SSL)]]
-- [[https://files.au.adversary.org/crypto/gpgme-python-howto.info][GPGME Python Bindings HOWTO draft (Info file AWS S3 SSL)]]
-- [[http://files.au.adversary.org/crypto/gpgme-python-howto.info][GPGME Python Bindings HOWTO draft (Info file AWS S3 no SSL)]]
-- [[https://files.au.adversary.org/crypto/gpgme-python-howto.rst][GPGME Python Bindings HOWTO draft (reST file AWS S3 SSL)]]
-- [[http://files.au.adversary.org/crypto/gpgme-python-howto.rst][GPGME Python Bindings HOWTO draft (reST file AWS S3 no SSL)]]
-- [[https://files.au.adversary.org/crypto/gpgme-python-howto.xml][GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 SSL)]]
-- [[http://files.au.adversary.org/crypto/gpgme-python-howto.xml][GPGME Python Bindings HOWTO draft (Docbook 4.2 AWS S3 no SSL)]]
-
-All of these draft versions except for one have been generated from
-this document via Emacs [[https://orgmode.org/][Org mode]] and [[https://www.gnu.org/software/texinfo/][GNU Texinfo]].  Though it is likely
-that the specific [[https://files.au.adversary.org/crypto/gpgme-python-howto.org][file]] [[http://files.au.adversary.org/crypto/gpgme-python-howto.org][version]] used will be on the same server with
-the generated output formats.
-
-The one exception is the reStructuredText version, which was converted
-using the latest version of Pandoc from the Org mode source file using
-the following command:
+- [[https://files.au.adversary.org/crypto/gpgme-python-howto.html][GPGME Python Bindings HOWTO draft (HTML single file, AWS S3 SSL)]]
+- [[http://files.au.adversary.org/crypto/gpgme-python-howto.html][GPGME Python Bindings HOWTO draft (HTML single file, AWS S3 no SSL)]]
+- [[https://files.au.adversary.org/crypto/gpgme-python-howto-split/index.html][GPGME Python Bindings HOWTO draft (HTML multiple files, AWS S3 SSL)]]
+- [[http://files.au.adversary.org/crypto/gpgme-python-howto/index.html][GPGME Python Bindings HOWTO draft (HTML multiple files, AWS S3 no SSL)]]
+
+These draft versions have been generated from this document via GNU
+Emacs [[https://orgmode.org/][Org mode]] to =.texi= and [[https://www.gnu.org/software/texinfo/][GNU Texinfo]] to HTML.  Though it is
+likely that the specific [[https://files.au.adversary.org/crypto/gpgme-python-howto][file]] [[http://files.au.adversary.org/crypto/gpgme-python-howto.org][version]] used will be on the same server
+with the generated output formats.  Occasionally I may include the Org
+mode generated XHTML versions:
+
+- [[https://files.au.adversary.org/crypto/gpgme-python-howto.xhtml][GPGME Python Bindings HOWTO draft (HTML single file, AWS S3 SSL)]]
+- [[http://files.au.adversary.org/crypto/gpgme-python-howto.xhtml][GPGME Python Bindings HOWTO draft (HTML single file, AWS S3 no SSL)]]
+
+That XHTML version, however, is exported in a way which inherits a
+colour scheme from [[https://github.com/holomorph/emacs-zenburn][the author's Emacs theme]] (which is a higher contrast
+version of [[http://kippura.org/zenburnpage/][Zenburn]] ported by [[https://github.com/holomorph][Holomorph]]).  So it's fine for people who
+prefer dark themed web pages, but not so great for everyone else.
+
+The GNU Texinfo and reStructured Text versions ship with the software,
+while the GNU Emacs Info version is generated from the Texinfo
+version using GNU Texinfo or GNU Makeinfo.  The Texinfo format is
+generated from the original Org mode source file in Org mode itself
+either within GNU Emacs or via the command line by invoking Emacs in
+batch mode:
+
+#+BEGIN_SRC shell
+  emacs gpgme-python-howto.org --batch -f org-texinfo-export-to-texinfo --kill
+  emacs gpgme-python-howto --batch -f org-texinfo-export-to-texinfo --kill
+#+END_SRC
+
+The reStructuredText format is also generated from the Org mode source
+file, except it is generated using [[https://pandoc.org][Pandoc]] with either of the following
+commands (depending on the filename):
 
 #+BEGIN_SRC shell
-  pandoc -f org -t rst -o gpgme-python-howto.rst gpgme-python-howto.org
+  pandoc -f org -t rst+smart -o gpgme-python-howto.rst gpgme-python-howto.org
+  pandoc -f org -t rst+smart -o gpgme-python-howto.rst gpgme-python-howto
 #+END_SRC
 
+Note that the Org mode source files are identified as such via a mode
+line at the top of each file and have had their =.org= file extensions
+dropped in order to make scripted generation of output formats easier
+and not require renaming files post-conversion.
+
+Due to a bug in Org mode's texinfo conversion method, the recommended
+steps for generating the Texinfo files for all the files in the
+=lang/python/doc/src/= directory are as follows:
+
+#+BEGIN_SRC shell
+  for x in * ; do
+      emacs $x --batch -f org-texinfo-export-to-texinfo --kill
+      cat $x.texi | sed -e 's/@documentencoding UTF-8/@documentencoding utf-8/g' > ../texinfo/$x.texi
+      pandoc -f org -t rst+smart -o ../rst/$x.rst $x
+  done ;
+  rm -fv *.texi
+  cd ../texinfo
+  mkdir info
+  mkdir html
+  for x in *.texi ; do
+      makeinfo -v $x
+      makeinfo --html --no-split $x
+  done ;
+  mv *.info info/
+  mv *.html html/
+#+END_SRC
+
+This code snippet includes the generation of the reStructuredText
+files and would be expected to be run from the =doc/src/= directory
+containing the Org mode source files.  It also assumes that the
+commands are being run on POSIX compliant systems with basic tools
+like sed, the Bourne shell and GNU Emacs[fn:6] available.  The code
+snippet also includes the steps for generating the Emacs Info files
+and HTML files from the Texinfo files.  Using reStructuredText files
+with Sphinx is best left for the documentation of that project.
+
 In addition to these there is a significantly less frequently updated
-version as a HTML [[https://files.au.adversary.org/crypto/gpgme-python-howto/webhelp/index.html][WebHelp site]] (AWS S3 SSL); generated from DITA XML
+version as a HTML [[https://files.au.adversary.org/crypto/gpgme-python/dita/webhelp/index.html][WebHelp site]] (AWS S3 SSL); generated from DITA XML
 source files, which can be found in [[https://dev.gnupg.org/source/gpgme/browse/ben%252Fhowto-dita/][an alternative branch]] of the GPGME
 git repository.
 
+Various generated output formats may occasionally be found in
+subdirectories of the [[https://s3.amazonaws.com/files.au.adversary.org/crypto/gpgme-python][gpgme-python]] directory.  In particular within
+the [[https://s3.amazonaws.com/files.au.adversary.org/crypto/gpgme-python/dita][DITA]], [[https://s3.amazonaws.com/files.au.adversary.org/crypto/gpgme-python/rst][reStructuredText]] and [[https://s3.amazonaws.com/files.au.adversary.org/crypto/gpgme-python/texinfo][Texinfo]] subdirectories.  The =rst=
+directory contains output files generated with Sphinx and may include a
+considerable number of its possible output formats, but there are no
+guarantees as to how recent these are or even if they are present.
+
 These draft editions are not official documents and the version of
 documentation in the master branch or which ships with released
 versions is the only official documentation.  Nevertheless, these
@@ -3014,7 +3339,7 @@ PURPOSE.
 
 * Footnotes
 
-[fn:1] =short-history.org= and/or =short-history.html=.
+[fn:1] =short-history= and/or =short-history.html=.
 
 [fn:2] With no issues reported specific to Python 3.7, the release of
 Python 3.7.1 at around the same time as GPGME 1.12.0 and the testing
@@ -3041,3 +3366,8 @@ restricted servers which only advertise either HTTP or HTTPS end
 points and not HKP or HKPS end points must still be identified as as
 HKP or HKPS within the Python Code.  The =hkp4py= module will rewrite
 these appropriately when the connection is made to the server.
+
+[fn:6] Okay, Emacs might not necessarily qualify as a basic tool, but
+it is common enough that having it installed on a system isn't too
+great an expectation, nor is it difficult to add to most POSIX
+systems, even if the users of those systems do not personally use it.