2006-07-06 Marcus Brinkmann <marcus@g10code.de>
authorMarcus Brinkmann <mb@g10code.com>
Thu, 6 Jul 2006 10:37:52 +0000 (10:37 +0000)
committerMarcus Brinkmann <mb@g10code.com>
Thu, 6 Jul 2006 10:37:52 +0000 (10:37 +0000)
* lang, lang/cl: New subdirectories.
* lang/Makefile.am, lang/README: New files.
* configure.ac (AC_CONFIG_FILES): Add lang/Makefile,
lang/cl/Makefile and lang/cl/gpgme.asd.
* Makefile.am (SUBDIRS): Add lang.

lang/cl/
2006-07-06  Marcus Brinkmann  <marcus@g10code.de>

* Initial release.

13 files changed:
ChangeLog
Makefile.am
README
TODO
configure.ac
lang/Makefile.am [new file with mode: 0644]
lang/README [new file with mode: 0644]
lang/cl/ChangeLog [new file with mode: 0644]
lang/cl/Makefile.am [new file with mode: 0644]
lang/cl/README [new file with mode: 0644]
lang/cl/gpgme-package.lisp [new file with mode: 0644]
lang/cl/gpgme.asd.in [new file with mode: 0644]
lang/cl/gpgme.lisp [new file with mode: 0644]

index 1bd3c37aa008b7f8867f068b3804c8b642bab446..79eba54c1df92e99ad484e1720eb641477e85f4a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2006-07-06  Marcus Brinkmann  <marcus@g10code.de>
+
+       * lang, lang/cl: New subdirectories.
+       * lang/Makefile.am, lang/README: New files.
+       * configure.ac (AC_CONFIG_FILES): Add lang/Makefile,
+       lang/cl/Makefile and lang/cl/gpgme.asd.
+       * Makefile.am (SUBDIRS): Add lang.
+
 2006-03-02  Marcus Brinkmann  <marcus@g10code.de>
 
        * configure.ac (LIBGPGME_LT_REVISION): Bump for release.
index 905c3cea22ad23bc60fd81c2949b5af35048c4a0..c614f447700f71f1e58642e38eb828caf1879775 100644 (file)
@@ -43,7 +43,7 @@ else
 tests = 
 endif
 
-SUBDIRS = ${assuan} gpgme ${tests} doc ${complus}
+SUBDIRS = ${assuan} gpgme ${tests} doc ${complus} lang
 
 # Fix the version of the spec file and create a file named VERSION 
 # to be used for patch's Prereq: feature.
diff --git a/README b/README
index bee7482371699181ba434c96d5ab6a864252b71e..7594bda7704d553e5d945e5da3ca61c7c4de5089 100644 (file)
--- a/README
+++ b/README
@@ -1,7 +1,7 @@
                   GPGME - GnuPG Made Easy
                 ---------------------------
 
-               Copyright 2004 g10 Code GmbH
+               Copyright 2004, 2006 g10 Code GmbH
 
 This file is free software; as a special exception the author gives
 unlimited permission to copy and/or distribute it, with or without
@@ -120,3 +120,4 @@ in the tests/gpg/ directory may also prove useful.
 
 Please subscribe to the gnupg-devel@gnupg.org mailing list if you want
 to do serious work.
+
diff --git a/TODO b/TODO
index 477d868bef3cd191a08a47f04deb6505e1484673..c2255ca500754da08de3e6465449e184c2fdbd0c 100644 (file)
--- a/TODO
+++ b/TODO
@@ -86,8 +86,20 @@ Hey Emacs, this is -*- outline -*- mode!
    (it's an internal error, as select_protocol checks already).
 ** When server mode is implemented properly, more care has to be taken to
    release all resources on error (for example to free assuan_cmd).
+* GPG breakage:
+** gpg 1.4.2 lacks error reporting if sign/encrypt with revoked key.
+** gpg 1.4.2 does crappy error reporting (namely none at all) when
+   smart card is missing for sign operation:
+    [GNUPG:] CARDCTRL 4
+    gpg: selecting openpgp failed: ec=6.110
+    gpg: signing failed: general error
+    [GNUPG:] BEGIN_ENCRYPTION 2 10
+    gpg: test: sign+encrypt failed: general error
+** Without agent and with wrong passphrase, gpg 1.4.2 enters into an
+   infinite loop.
 
 * Operations
+** Include cert values -2, -1, 0 and 1 should be defined as macros.
 ** If an operation failed, make sure that the result functions don't return
    corrupt partial information. !!!
    NOTE: The EOF status handler is not called in this case !!!
index 95e9a561021151b943a3d46197fe2e029f1da5d3..bcbf4847b448b944b574042cb2352144219b0c4c 100644 (file)
@@ -537,6 +537,7 @@ AC_CONFIG_FILES(Makefile assuan/Makefile gpgme/Makefile
                doc/Makefile complus/Makefile
                 gpgme/versioninfo.rc)
 AC_CONFIG_FILES(gpgme/gpgme-config, chmod +x gpgme/gpgme-config)
+AC_CONFIG_FILES([lang/Makefile lang/cl/Makefile lang/cl/gpgme.asd])
 AC_OUTPUT
 
 echo "
diff --git a/lang/Makefile.am b/lang/Makefile.am
new file mode 100644 (file)
index 0000000..3b07992
--- /dev/null
@@ -0,0 +1,22 @@
+# Makefile.am for gpgme/lang.
+# Copyright (C) 2003, 2006 g10 Code GmbH
+# 
+# This file is part of GPGME.
+# 
+# GPGME is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+# 
+# GPGME is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
+# Public License for more details.
+# 
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+SUBDIRS = cl
+
+EXTRA_DIST = README
diff --git a/lang/README b/lang/README
new file mode 100644 (file)
index 0000000..da54c78
--- /dev/null
@@ -0,0 +1,12 @@
+Language Support for GPGME
+--------------------------
+
+This directory contains support for other languages than C.
+
+Please note that language support components may be under a different
+license than GPGME itself.  You can find more information in each
+sub-directory.
+
+Directory      Language
+
+cl             Common Lisp
diff --git a/lang/cl/ChangeLog b/lang/cl/ChangeLog
new file mode 100644 (file)
index 0000000..6862273
--- /dev/null
@@ -0,0 +1,4 @@
+2006-07-06  Marcus Brinkmann  <marcus@g10code.de>
+
+       * Initial release.
+
diff --git a/lang/cl/Makefile.am b/lang/cl/Makefile.am
new file mode 100644 (file)
index 0000000..e0064ba
--- /dev/null
@@ -0,0 +1,29 @@
+# Makefile.am for GPGME-CL.
+# Copyright (C) 2003, 2006 g10 Code GmbH
+# 
+# This file is part of GPGME-CL.
+# 
+# GPGME-CL is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+# 
+# GPGME-CL is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA
+
+clfiles = gpgme.asd gpgme-package.lisp gpgme.lisp
+
+# FIXME: Should be configurable.
+clfilesdir = $(datadir)/common-lisp/source/gpgme
+dist_clfiles_DATA = $(clfiles)
+
+EXTRA_DIST = README
+
+
diff --git a/lang/cl/README b/lang/cl/README
new file mode 100644 (file)
index 0000000..b4a3c81
--- /dev/null
@@ -0,0 +1,40 @@
+Common Lisp Support for GPGME
+-----------------------------
+
+Requirements:
+
+ASDF           Packaging Support
+CFFI           Foreign Function Interface
+gpg-error      GPG Error Codes
+
+Use with:
+
+> (asdf:operate 'asdf:load-op ':gpgme)
+
+
+Examples
+--------
+
+(with-open-file (stream "/tmp/myout" :direction :output
+                       :if-exists :supersede :element-type '(unsigned-byte 8))
+  (with-context (ctx)
+    (setf (armor-p ctx) t)
+    (op-export ctx "DEADBEEF" out)))
+
+(with-context (ctx)
+  (with-output-to-string (out)
+    (setf (armor-p ctx) t)
+    (op-export ctx "McTester" out)))
+
+(gpgme:with-context (ctx :armor t)
+  (with-output-to-string (out)
+    (gpgme:op-export ctx "McTester" out)))
+
+
+TODO
+----
+
+* When GPGME defines macros for include cert values -2, -1, 0 and 1,
+  define lisp macros for them as well.
+
+* 
diff --git a/lang/cl/gpgme-package.lisp b/lang/cl/gpgme-package.lisp
new file mode 100644 (file)
index 0000000..239d57f
--- /dev/null
@@ -0,0 +1,49 @@
+;;;; gpgme-package.lisp
+
+;;; Copyright (C) 2006 g10 Code GmbH
+;;;
+;;; This file is part of GPGME-CL.
+;;;
+;;; GPGME-CL is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published
+;;; by the Free Software Foundation; either version 2 of the License,
+;;; or (at your option) any later version.
+;;;
+;;; GPGME-CL is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;;; Lesser General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GPGME; if not, write to the Free Software Foundation,
+;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+;;; Conventions:
+;;;
+;;; gpg-error is used for error handling.
+;;;
+;;; Standard I/O streams are used for input and output.
+
+(defpackage #:gpgme
+  (:use #:common-lisp #:cffi #:gpg-error)
+
+  (:export #:check-version
+          #:*version*
+          #:context
+          #:protocol
+          #:armorp
+          #:textmodep
+          #:+include-certs-default+
+          #:include-certs
+          #:keylist-mode
+          #:signers
+          #:sig-notations
+          #:with-context
+          #:key-data
+          #:get-key
+          #:op-encrypt
+          #:op-decrypt
+          #:op-sign
+          #:op-verify
+          #:op-import
+          #:op-export))
diff --git a/lang/cl/gpgme.asd.in b/lang/cl/gpgme.asd.in
new file mode 100644 (file)
index 0000000..86e8d51
--- /dev/null
@@ -0,0 +1,35 @@
+;;; -*- Mode: lisp -*-
+
+;;; Copyright (C) 2006 g10 Code GmbH
+;;;
+;;; This file is part of GPGME.
+;;;
+;;; GPGME is free software; you can redistribute it and/or
+;;; modify it under the terms of the GNU Lesser General Public License
+;;; as published by the Free Software Foundation; either version 2.1 of
+;;; the License, or (at your option) any later version.
+;;;
+;;; GPGME is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;;; Lesser General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU Lesser General Public
+;;; License along with GPGME; if not, write to the Free
+;;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+;;; 02111-1307, USA.
+
+(defpackage #:gpgme-system
+  (:use #:common-lisp #:asdf))
+
+(in-package #:gpgme-system)
+
+(defsystem gpgme
+    :description "GnuPG Made Easy."
+    :author "g10 Code GmbH"
+    :version "@VERSION@"
+    :licence "GPL"
+    :depends-on ("cffi" "gpg-error")
+    :components ((:file "gpgme-package")
+                (:file "gpgme"
+                       :depends-on ("gpgme-package"))))
diff --git a/lang/cl/gpgme.lisp b/lang/cl/gpgme.lisp
new file mode 100644 (file)
index 0000000..cb536fa
--- /dev/null
@@ -0,0 +1,2077 @@
+;;;; gpgme.lisp
+
+;;; Copyright (C) 2006 g10 Code GmbH
+;;;
+;;; This file is part of GPGME-CL.
+;;;
+;;; GPGME-CL is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 2 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; GPGME-CL is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;;; Lesser General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GPGME; if not, write to the Free Software Foundation,
+;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+;;; TODO
+
+;;; Set up the library.
+
+(in-package :gpgme)
+
+;;; Debugging.
+
+(defvar *debug* nil "If debugging output should be given or not.")
+
+;;; Load the foreign library.
+
+(define-foreign-library libgpgme
+  (:unix "libgpgme.so")
+  (t (:default "libgpgme")))
+
+(use-foreign-library libgpgme)
+
+;;; System dependencies.
+
+; FIXME: Use cffi-grovel?  cffi-unix?
+
+(defctype size-t :unsigned-int
+  :documentation "The system size_t type.")
+
+(defctype ssize-t :int
+  :documentation "The system ssize_t type.")
+
+; FIXME: Ouch.  Grovel?  Helper function?
+(defconstant +seek-set+ 0)
+(defconstant +seek-cur+ 1)
+(defconstant +seek-end+ 2)
+(defctype off-t :long-long
+  :documentation "The system off_t type.")
+
+(defcfun ("strerror" c-strerror) :string
+  (err :int))
+
+; Access to ERRNO.
+; FIXME: Ouch.  Should be grovel + helper function.
+
+(define-condition system-error (error)
+  ((errno :initarg :errno :reader system-error-errno))
+  (:report (lambda (c stream)
+            (format stream "System error: ~A: ~A"
+                    (system-error-errno c)
+                    (c-strerror (system-error-errno c)))))
+  (:documentation "Signalled when an errno is encountered."))
+
+(defconstant +ebadf+ 1)
+
+; Ouch.
+(defun get-errno ()
+  +ebadf+)
+
+;;; More about errno below.
+
+; Needed to write passphrases.
+(defcfun ("write" c-write) ssize-t
+  (fd :int)
+  (buffer :string) ; Actually :pointer, but we only need string.
+  (size size-t))
+
+(defun system-write (fd buffer size)
+  (let ((res (c-write fd buffer size)))
+    (when (< res 0) (error 'system-error :errno (get-errno)))
+    res))
+
+;;; More about errno here.
+
+(defun set-errno (errno)
+  (cond
+                                       ; Works on GNU/Linux.
+    ((eql errno +ebadf+) (system-write -1 (null-pointer) 0))
+    (t (error 'invalid-errno :errno errno))))
+
+;;;
+;;; C Interface Definitions
+;;;
+
+;;; Data Type Interface
+
+;;; Some new data types used for easier translation.
+
+;;; The number of include certs.  Translates to NIL for default.
+(defctype cert-int-t :int)
+
+;;; A string that may be NIL to indicate a null pointer.
+(defctype string-or-nil-t :string)
+
+;;; Some opaque data types used by GPGME.
+
+(defctype gpgme-ctx-t :pointer
+  :documentation "The GPGME context type.")
+
+(defctype gpgme-data-t :pointer
+  :documentation "The GPGME data object type.")
+
+;;; Wrappers for the libgpg-error library.
+
+(defctype gpgme-error-t gpg-error::gpg-error-t
+  :documentation "The GPGME error type.")
+
+(defctype gpgme-error-no-signal-t gpg-error::gpg-error-t
+  :documentation "The GPGME error type (this version does not
+  signal conditions in translation.")
+
+(defctype gpgme-err-code-t gpg-error::gpg-err-code-t
+  :documentation "The GPGME error code type.")
+
+(defctype gpgme-err-source-t gpg-error::gpg-err-source-t
+  :documentation "The GPGME error source type.")
+
+(defun gpgme-err-make (source code)
+  "Construct an error value from an error code and source."
+  (gpg-err-make source code))
+
+(defun gpgme-error (code)
+  "Construct an error value from an error code."
+  (gpgme-err-make :gpg-err-source-gpgme code))
+
+(defun gpgme-err-code (err)
+  "Retrieve an error code from the error value ERR."
+  (gpg-err-code err))
+
+(defun gpgme-err-source (err)
+  "Retrieve an error source from the error value ERR."
+  (gpg-err-source err))
+
+(defun gpgme-strerror (err)
+  "Return a string containig a description of the error code."
+  (gpg-strerror err))
+
+(defun gpgme-strsource (err)
+  "Return a string containig a description of the error source."
+  (gpg-strsource err))
+
+(defun gpgme-err-code-from-errno (err)
+  "Retrieve the error code for the system error.  If the system error
+   is not mapped, :gpg-err-unknown-errno is returned."
+  (gpg-err-code-from-errno err))
+
+(defun gpgme-err-code-to-errno (code)
+  "Retrieve the system error for the error code.  If this is not a
+   system error, 0 is returned."
+  (gpg-err-code-to-errno code))
+
+(defun gpgme-err-make-from-errno (source err)
+  (gpg-err-make-from-errno source err))
+
+(defun gpgme-error-from-errno (err)
+  (gpg-error-from-errno err))
+
+;;;
+
+(defcenum gpgme-data-encoding-t
+  "The possible encoding mode of gpgme-data-t objects."
+  (:none 0)
+  (:binary 1)
+  (:base64 2)
+  (:armor 3))
+
+;;;
+
+(defcenum gpgme-pubkey-algo-t
+  "Public key algorithms from libgcrypt."
+  (:rsa 1)
+  (:rsa-e 2)
+  (:rsa-s 3)
+  (:elg-e 16)
+  (:dsa 17)
+  (:elg 20))
+
+(defcenum gpgme-hash-algo-t
+  "Hash algorithms from libgcrypt."
+  (:none 0)
+  (:md5 1)
+  (:sha1 2)
+  (:rmd160 3)
+  (:md2 5)
+  (:tiger 6)
+  (:haval 7)
+  (:sha256 8)
+  (:sha384 9)
+  (:sha512 10)
+  (:md4 301)
+  (:crc32 302)
+  (:crc32-rfc1510 303)
+  (:crc24-rfc2440 304))
+
+;;;
+
+(defcenum gpgme-sig-mode-t
+  "The available signature modes."
+  (:none 0)
+  (:detach 1)
+  (:clear 2))
+
+;;;
+
+(defcenum gpgme-validity-t
+  "The available validities for a trust item or key."
+  (:unknown 0)
+  (:undefined 1)
+  (:never 2)
+  (:marginal 3)
+  (:full 4)
+  (:ultimate 5))
+
+;;;
+
+(defcenum gpgme-protocol-t
+  "The available protocols."
+  (:openpgp 0)
+  (:cms 1))
+
+;;;
+
+(defbitfield (gpgme-keylist-mode-t :unsigned-int)
+  "The available keylist mode flags."
+  (:local 1)
+  (:extern 2)
+  (:sigs 4)
+  (:validate 256))
+
+;;;
+
+(defbitfield (gpgme-sig-notation-flags-t :unsigned-int)
+  "The available signature notation flags."
+  (:human-readable 1)
+  (:critical 2))
+
+(defctype gpgme-sig-notation-t :pointer
+  :documentation "Signature notation pointer type.")
+
+;; FIXME: Doesn't this depend on endianess?
+(defbitfield (gpgme-sig-notation-bitfield :unsigned-int)
+  (:human-readable 1)
+  (:critical 2))
+
+(defcstruct gpgme-sig-notation
+  "Signature notations."
+  (next gpgme-sig-notation-t)
+  (name :pointer)
+  (value :pointer)
+  (name-len :int)
+  (value-len :int)
+  (flags gpgme-sig-notation-flags-t)
+  (bitfield gpgme-sig-notation-bitfield))
+
+;;;
+
+;; FIXME: Add status codes.
+(defcenum gpgme-status-code-t
+  "The possible status codes for the edit operation."
+  (:eof 0)
+  (:enter 1))
+
+;;;
+
+(defctype gpgme-engine-info-t :pointer
+  :documentation "The engine information structure pointer type.")
+
+(defcstruct gpgme-engine-info
+  "Engine information."
+  (next gpgme-engine-info-t)
+  (protocol gpgme-protocol-t)
+  (file-name :string)
+  (version :string)
+  (req-version :string)
+  (home-dir :string))
+
+;;;
+
+(defctype gpgme-subkey-t :pointer
+  :documentation "A subkey from a key.")
+
+;; FIXME: Doesn't this depend on endianess?
+(defbitfield (gpgme-subkey-bitfield :unsigned-int)
+  "The subkey bitfield."
+  (:revoked 1)
+  (:expired 2)
+  (:disabled 4)
+  (:invalid 8)
+  (:can-encrypt 16)
+  (:can-sign 32)
+  (:can-certify 64)
+  (:secret 128)
+  (:can-authenticate 256)
+  (:is-qualified 512))
+
+(defcstruct gpgme-subkey
+  "Subkey from a key."
+  (next gpgme-subkey-t)
+  (bitfield gpgme-subkey-bitfield)
+  (pubkey-algo gpgme-pubkey-algo-t)
+  (length :unsigned-int)
+  (keyid :string)
+  (-keyid :char :count 17)
+  (fpr :string)
+  (timestamp :long)
+  (expires :long))
+
+
+(defctype gpgme-key-sig-t :pointer
+  :documentation "A signature on a user ID.")
+
+;; FIXME: Doesn't this depend on endianess?
+(defbitfield (gpgme-key-sig-bitfield :unsigned-int)
+  "The key signature bitfield."
+  (:revoked 1)
+  (:expired 2)
+  (:invalid 4)
+  (:exportable 16))
+
+(defcstruct gpgme-key-sig
+  "A signature on a user ID."
+  (next gpgme-key-sig-t)
+  (bitfield gpgme-key-sig-bitfield)
+  (pubkey-algo gpgme-pubkey-algo-t)
+  (keyid :string)
+  (-keyid :char :count 17)
+  (timestamp :long)
+  (expires :long)
+  (status gpgme-error-no-signal-t)
+  (-class :unsigned-int)
+  (uid :string)
+  (name :string)
+  (email :string)
+  (comment :string)
+  (sig-class :unsigned-int))
+
+
+(defctype gpgme-user-id-t :pointer
+  :documentation "A user ID from a key.")
+
+;; FIXME: Doesn't this depend on endianess?
+(defbitfield (gpgme-user-id-bitfield :unsigned-int)
+  "The user ID bitfield."
+  (:revoked 1)
+  (:invalid 2))
+
+(defcstruct gpgme-user-id
+  "A user ID from a key."
+  (next gpgme-user-id-t)
+  (bitfield gpgme-user-id-bitfield)
+  (validity gpgme-validity-t)
+  (uid :string)
+  (name :string)
+  (email :string)
+  (comment :string)
+  (signatures gpgme-key-sig-t)
+  (-last-keysig gpgme-key-sig-t))
+
+
+(defctype gpgme-key-t :pointer
+  :documentation "A key from the keyring.")
+
+;; FIXME: Doesn't this depend on endianess?
+(defbitfield (gpgme-key-bitfield :unsigned-int)
+  "The key bitfield."
+  (:revoked 1)
+  (:expired 2)
+  (:disabled 4)
+  (:invalid 8)
+  (:can-encrypt 16)
+  (:can-sign 32)
+  (:can-certify 64)
+  (:secret 128)
+  (:can-authenticate 256)
+  (:is-qualified 512))
+
+(defcstruct gpgme-key
+  "A signature on a user ID."
+  (-refs :unsigned-int)
+  (bitfield gpgme-key-bitfield)
+  (protocol gpgme-protocol-t)
+  (issuer-serial :string)
+  (issuer-name :string)
+  (chain-id :string)
+  (owner-trust gpgme-validity-t)
+  (subkeys gpgme-subkey-t)
+  (uids gpgme-user-id-t)
+  (-last-subkey gpgme-subkey-t)
+  (-last-uid gpgme-user-id-t)
+  (keylist-mode gpgme-keylist-mode-t))
+
+;;;
+
+;;; There is no support in CFFI to define callback C types and have
+;;; automatic type checking with the callback definition.
+
+(defctype gpgme-passphrase-cb-t :pointer)
+
+(defctype gpgme-progress-cb-t :pointer)
+
+(defctype gpgme-edit-cb-t :pointer)
+
+
+;;;
+;;; Function Interface
+;;;
+
+;;; Context management functions.
+
+(defcfun ("gpgme_new" c-gpgme-new) gpgme-error-t
+  (ctx :pointer))
+
+(defcfun ("gpgme_release" c-gpgme-release) :void
+  (ctx gpgme-ctx-t))
+
+(defcfun ("gpgme_set_protocol" c-gpgme-set-protocol) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (proto gpgme-protocol-t))
+
+(defcfun ("gpgme_get_protocol" c-gpgme-get-protocol) gpgme-protocol-t
+  (ctx gpgme-ctx-t))
+
+(defcfun ("gpgme_get_protocol_name" c-gpgme-get-protocol-name) :string
+  (proto gpgme-protocol-t))
+
+(defcfun ("gpgme_set_armor" c-gpgme-set-armor) :void
+  (ctx gpgme-ctx-t)
+  (yes :boolean))
+
+(defcfun ("gpgme_get_armor" c-gpgme-get-armor) :boolean
+  (ctx gpgme-ctx-t))
+
+(defcfun ("gpgme_set_textmode" c-gpgme-set-textmode) :void
+  (ctx gpgme-ctx-t)
+  (yes :boolean))
+
+(defcfun ("gpgme_get_textmode" c-gpgme-get-textmode) :boolean
+  (ctx gpgme-ctx-t))
+
+(defconstant +include-certs-default+ -256)
+
+(defcfun ("gpgme_set_include_certs" c-gpgme-set-include-certs) :void
+  (ctx gpgme-ctx-t)
+  (nr-of-certs cert-int-t))
+
+(defcfun ("gpgme_get_include_certs" c-gpgme-get-include-certs) cert-int-t
+  (ctx gpgme-ctx-t))
+
+(defcfun ("gpgme_set_keylist_mode" c-gpgme-set-keylist-mode) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (mode gpgme-keylist-mode-t))
+
+(defcfun ("gpgme_get_keylist_mode" c-gpgme-get-keylist-mode)
+    gpgme-keylist-mode-t
+  (ctx gpgme-ctx-t))
+
+(defcfun ("gpgme_set_passphrase_cb" c-gpgme-set-passphrase-cb) :void
+  (ctx gpgme-ctx-t)
+  (cb gpgme-passphrase-cb-t)
+  (hook-value :pointer))
+
+(defcfun ("gpgme_get_passphrase_cb" c-gpgme-get-passphrase-cb) :void
+  (ctx gpgme-ctx-t)
+  (cb-p :pointer)
+  (hook-value-p :pointer))
+
+(defcfun ("gpgme_set_progress_cb" c-gpgme-set-progress-cb) :void
+  (ctx gpgme-ctx-t)
+  (cb gpgme-progress-cb-t)
+  (hook-value :pointer))
+
+(defcfun ("gpgme_get_progress_cb" c-gpgme-get-progress-cb) :void
+  (ctx gpgme-ctx-t)
+  (cb-p :pointer)
+  (hook-value-p :pointer))
+
+(defcfun ("gpgme_set_locale" c-gpgme-set-locale) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (category :int)
+  (value string-or-nil-t))
+
+(defcfun ("gpgme_ctx_get_engine_info" c-gpgme-ctx-get-engine-info)
+    gpgme-engine-info-t
+  (ctx gpgme-ctx-t))
+
+(defcfun ("gpgme_ctx_set_engine_info" c-gpgme-ctx-set-engine-info)
+    gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (proto gpgme-protocol-t)
+  (file-name string-or-nil-t)
+  (home-dir string-or-nil-t))
+
+;;;
+
+(defcfun ("gpgme_pubkey_algo_name" c-gpgme-pubkey-algo-name) :string
+  (algo gpgme-pubkey-algo-t))
+
+(defcfun ("gpgme_hash_algo_name" c-gpgme-hash-algo-name) :string
+  (algo gpgme-hash-algo-t))
+
+;;;
+
+(defcfun ("gpgme_signers_clear" c-gpgme-signers-clear) :void
+  (ctx gpgme-ctx-t))
+
+(defcfun ("gpgme_signers_add" c-gpgme-signers-add) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (key gpgme-key-t))
+
+(defcfun ("gpgme_signers_enum" c-gpgme-signers-enum) gpgme-key-t
+  (ctx gpgme-ctx-t)
+  (seq :int))
+
+;;;
+
+(defcfun ("gpgme_sig_notation_clear" c-gpgme-sig-notation-clear) :void
+  (ctx gpgme-ctx-t))
+
+(defcfun ("gpgme_sig_notation_add" c-gpgme-sig-notation-add) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (name :string)
+  (value string-or-nil-t)
+  (flags gpgme-sig-notation-flags-t))
+
+(defcfun ("gpgme_sig_notation_get" c-gpgme-sig-notation-get)
+    gpgme-sig-notation-t
+  (ctx gpgme-ctx-t))
+
+;;; Run Control.
+
+;;; There is no support in CFFI to define callback C types and have
+;;; automatic type checking with the callback definition.
+
+(defctype gpgme-io-cb-t :pointer)
+
+(defctype gpgme-register-io-cb-t :pointer)
+
+(defctype gpgme-remove-io-cb-t :pointer)
+
+(defcenum gpgme-event-io-t
+  "The possible events on I/O event callbacks."
+  (:start 0)
+  (:done 1)
+  (:next-key 2)
+  (:next-trustitem 3))
+
+(defctype gpgme-event-io-cb-t :pointer)
+
+(defcstruct gpgme-io-cbs
+  "I/O callbacks."
+  (add gpgme-register-io-cb-t)
+  (add-priv :pointer)
+  (remove gpgme-remove-io-cb-t)
+  (event gpgme-event-io-cb-t)
+  (event-priv :pointer))
+
+(defctype gpgme-io-cbs-t :pointer)
+
+(defcfun ("gpgme_set_io_cbs" c-gpgme-set-io-cbs) :void
+  (ctx gpgme-ctx-t)
+  (io-cbs gpgme-io-cbs-t))
+
+(defcfun ("gpgme_get_io_cbs" c-gpgme-get-io-cbs) :void
+  (ctx gpgme-ctx-t)
+  (io-cbs gpgme-io-cbs-t))
+
+(defcfun ("gpgme_wait" c-gpgme-wait) gpgme-ctx-t
+  (ctx gpgme-ctx-t)
+  (status-p :pointer)
+  (hang :int))
+
+;;; Functions to handle data objects.
+
+;;; There is no support in CFFI to define callback C types and have
+;;; automatic type checking with the callback definition.
+
+(defctype gpgme-data-read-cb-t :pointer)
+(defctype gpgme-data-write-cb-t :pointer)
+(defctype gpgme-data-seek-cb-t :pointer)
+(defctype gpgme-data-release-cb-t :pointer)
+
+(defcstruct gpgme-data-cbs
+  "Data callbacks."
+  (read gpgme-data-read-cb-t)
+  (write gpgme-data-write-cb-t)
+  (seek gpgme-data-seek-cb-t)
+  (release gpgme-data-release-cb-t))
+
+(defctype gpgme-data-cbs-t :pointer
+  :documentation "Data callbacks pointer.")
+
+(defcfun ("gpgme_data_read" c-gpgme-data-read) ssize-t
+  (dh gpgme-data-t)
+  (buffer :pointer)
+  (size size-t))
+
+(defcfun ("gpgme_data_write" c-gpgme-data-write) ssize-t
+  (dh gpgme-data-t)
+  (buffer :pointer)
+  (size size-t))
+
+(defcfun ("gpgme_data_seek" c-gpgme-data-seek) off-t
+  (dh gpgme-data-t)
+  (offset off-t)
+  (whence :int))
+
+(defcfun ("gpgme_data_new" c-gpgme-data-new) gpgme-error-t
+  (dh-p :pointer))
+
+(defcfun ("gpgme_data_release" c-gpgme-data-release) :void
+  (dh gpgme-data-t))
+
+(defcfun ("gpgme_data_new_from_mem" c-gpgme-data-new-from-mem) gpgme-error-t
+  (dh-p :pointer)
+  (buffer :pointer)
+  (size size-t)
+  (copy :int))
+
+(defcfun ("gpgme_data_release_and_get_mem" c-gpgme-data-release-and-get-mem)
+    :pointer
+  (dh gpgme-data-t)
+  (len-p :pointer))
+
+(defcfun ("gpgme_data_new_from_cbs" c-gpgme-data-new-from-cbs) gpgme-error-t
+  (dh-p :pointer)
+  (cbs gpgme-data-cbs-t)
+  (handle :pointer))
+
+(defcfun ("gpgme_data_new_from_fd" c-gpgme-data-new-from-fd) gpgme-error-t
+  (dh-p :pointer)
+  (fd :int))
+
+(defcfun ("gpgme_data_new_from_stream" c-gpgme-data-new-from-stream)
+    gpgme-error-t
+  (dh-p :pointer)
+  (stream :pointer))
+
+(defcfun ("gpgme_data_get_encoding" c-gpgme-data-get-encoding)
+    gpgme-data-encoding-t
+  (dh gpgme-data-t))
+
+(defcfun ("gpgme_data_set_encoding" c-gpgme-data-set-encoding)
+    gpgme-error-t
+  (dh gpgme-data-t)
+  (enc gpgme-data-encoding-t))
+
+(defcfun ("gpgme_data_get_file_name" c-gpgme-data-get-file-name) :string
+  (dh gpgme-data-t))
+
+(defcfun ("gpgme_data_set_file_name" c-gpgme-data-set-file-name) gpgme-error-t
+  (dh gpgme-data-t)
+  (file-name string-or-nil-t))
+
+(defcfun ("gpgme_data_new_from_file" c-gpgme-data-new-from-file) gpgme-error-t
+  (dh-p :pointer)
+  (fname :string)
+  (copy :int))
+
+(defcfun ("gpgme_data_new_from_filepart" c-gpgme-data-new-from-filepart)
+    gpgme-error-t
+  (dh-p :pointer)
+  (fname :string)
+  (fp :pointer)
+  (offset off-t)
+  (length size-t))
+
+;;; Key and trust functions.
+
+(defcfun ("gpgme_get_key" c-gpgme-get-key) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (fpr :string)
+  (key-p :pointer)
+  (secret :boolean))
+
+(defcfun ("gpgme_key_ref" c-gpgme-key-ref) :void
+  (key gpgme-key-t))
+
+(defcfun ("gpgme_key_unref" c-gpgme-key-unref) :void
+  (key gpgme-key-t))
+
+;;; Crypto operations.
+
+(defcfun ("gpgme_cancel" c-gpgme-cancel) gpgme-error-t
+  (ctx gpgme-ctx-t))
+
+;;;
+
+(defctype gpgme-invalid-key-t :pointer
+  :documentation "An invalid key structure.")
+
+(defcstruct gpgme-invalid-key
+  "An invalid key structure."
+  (next gpgme-invalid-key-t)
+  (fpr :string)
+  (reason gpgme-error-no-signal-t))
+
+;;; Encryption.
+
+(defcstruct gpgme-op-encrypt-result
+  "Encryption result structure."
+  (invalid-recipients gpgme-invalid-key-t))
+
+(defctype gpgme-op-encrypt-result-t :pointer
+  :documentation "An encryption result structure.")
+
+(defcfun ("gpgme_op_encrypt_result" c-gpgme-op-encrypt-result)
+    gpgme-op-encrypt-result-t
+  (ctx gpgme-ctx-t))
+
+(defbitfield gpgme-encrypt-flags-t
+  (:always-trust 1))
+
+(defcfun ("gpgme_op_encrypt_start" c-gpgme-op-encrypt-start) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (recp :pointer) ; Key array.
+  (flags gpgme-encrypt-flags-t)
+  (plain gpgme-data-t)
+  (cipher gpgme-data-t))
+
+(defcfun ("gpgme_op_encrypt" c-gpgme-op-encrypt) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (recp :pointer) ; Key array.
+  (flags gpgme-encrypt-flags-t)
+  (plain gpgme-data-t)
+  (cipher gpgme-data-t))
+
+(defcfun ("gpgme_op_encrypt_sign_start" c-gpgme-op-encrypt-sign-start)
+    gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (recp :pointer) ; Key array.
+  (flags gpgme-encrypt-flags-t)
+  (plain gpgme-data-t)
+  (cipher gpgme-data-t))
+
+(defcfun ("gpgme_op_encrypt_sign" c-gpgme-op-encrypt-sign) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (recp :pointer) ; Key array.
+  (flags gpgme-encrypt-flags-t)
+  (plain gpgme-data-t)
+  (cipher gpgme-data-t))
+
+;;; Decryption.
+
+(defctype gpgme-recipient-t :pointer
+  :documentation "A recipient structure.")
+
+(defcstruct gpgme-recipient
+  "Recipient structure."
+  (next gpgme-recipient-t)
+  (keyid :string)
+  (-keyid :char :count 17)
+  (pubkey-algo gpgme-pubkey-algo-t)
+  (status gpgme-error-no-signal-t))
+
+(defbitfield gpgme-op-decrypt-result-bitfield
+  "Decryption result structure bitfield."
+  (:wrong-key-usage 1))
+
+(defcstruct gpgme-op-decrypt-result
+  "Decryption result structure."
+  (unsupported-algorithm :string)
+  (bitfield gpgme-op-decrypt-result-bitfield)
+  (recipients gpgme-recipient-t)
+  (file-name :string))
+
+(defctype gpgme-op-decrypt-result-t :pointer
+  :documentation "A decryption result structure.")
+
+(defcfun ("gpgme_op_decrypt_result" c-gpgme-op-decrypt-result)
+    gpgme-op-decrypt-result-t
+  (ctx gpgme-ctx-t))
+
+(defcfun ("gpgme_op_decrypt_start" c-gpgme-op-decrypt-start) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (cipher gpgme-data-t)
+  (plain gpgme-data-t))
+
+(defcfun ("gpgme_op_decrypt" c-gpgme-op-decrypt) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (cipher gpgme-data-t)
+  (plain gpgme-data-t))
+
+(defcfun ("gpgme_op_decrypt_verify_start" c-gpgme-op-decrypt-verify-start)
+    gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (cipher gpgme-data-t)
+  (plain gpgme-data-t))
+
+(defcfun ("gpgme_op_decrypt_verify" c-gpgme-op-decrypt-verify) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (cipher gpgme-data-t)
+  (plain gpgme-data-t))
+
+;;; Signing.
+
+(defctype gpgme-new-signature-t :pointer
+  :documentation "A new signature structure.")
+
+(defcstruct gpgme-new-signature
+  "New signature structure."
+  (next gpgme-new-signature-t)
+  (type gpgme-sig-mode-t)
+  (pubkey-algo gpgme-pubkey-algo-t)
+  (hash-algo gpgme-hash-algo-t)
+  (-obsolete-class :unsigned-long)
+  (timestamp :long)
+  (fpr :string)
+  (-obsolete-class-2 :unsigned-int)
+  (sig-class :unsigned-int))
+
+(defcstruct gpgme-op-sign-result
+  "Signing result structure."
+  (invalid-signers gpgme-invalid-key-t)
+  (signatures gpgme-new-signature-t))
+
+(defctype gpgme-op-sign-result-t :pointer
+  :documentation "A signing result structure.")
+
+(defcfun ("gpgme_op_sign_result" c-gpgme-op-sign-result)
+    gpgme-op-sign-result-t
+  (ctx gpgme-ctx-t))
+
+(defcfun ("gpgme_op_sign_start" c-gpgme-op-sign-start) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (plain gpgme-data-t)
+  (sig gpgme-data-t)
+  (mode gpgme-sig-mode-t))
+
+(defcfun ("gpgme_op_sign" c-gpgme-op-sign) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (plain gpgme-data-t)
+  (sig gpgme-data-t)
+  (mode gpgme-sig-mode-t))
+
+;;; Verify.
+
+(defbitfield (gpgme-sigsum-t :unsigned-int)
+  "Flags used for the summary field in a gpgme-signature-t."
+  (:valid #x0001)
+  (:green #x0002)
+  (:red #x0004)
+  (:key-revoked #x0010)
+  (:key-expired #x0020)
+  (:sig-expired #x0040)
+  (:key-missing #x0080)
+  (:crl-missing #x0100)
+  (:crl-too-old #x0200)
+  (:bad-policy #x0400)
+  (:sys-error #x0800))
+
+(defctype gpgme-signature-t :pointer
+  :documentation "A signature structure.")
+
+;; FIXME: Doesn't this depend on endianess?
+(defbitfield (gpgme-signature-bitfield :unsigned-int)
+  "The signature bitfield."
+  (:wrong-key-usage 1))
+
+(defcstruct gpgme-signature
+  "Signature structure."
+  (next gpgme-signature-t)
+  (summary gpgme-sigsum-t)
+  (fpr :string)
+  (status gpgme-error-no-signal-t)
+  (notations gpgme-sig-notation-t)
+  (timestamp :unsigned-long)
+  (exp-timestamp :unsigned-long)
+  (bitfield gpgme-signature-bitfield)
+  (validity gpgme-validity-t)
+  (validity-reason gpgme-error-no-signal-t)
+  (pubkey-algo gpgme-pubkey-algo-t)
+  (hash-algo gpgme-hash-algo-t))
+
+(defcstruct gpgme-op-verify-result
+  "Verify result structure."
+  (signatures gpgme-signature-t)
+  (file-name :string))
+
+(defctype gpgme-op-verify-result-t :pointer
+  :documentation "A verify result structure.")
+
+(defcfun ("gpgme_op_verify_result" c-gpgme-op-verify-result)
+    gpgme-op-verify-result-t
+  (ctx gpgme-ctx-t))
+
+(defcfun ("gpgme_op_verify_start" c-gpgme-op-verify-start) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (sig gpgme-data-t)
+  (signed-text gpgme-data-t)
+  (plaintext gpgme-data-t))
+
+(defcfun ("gpgme_op_verify" c-gpgme-op-verify) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (sig gpgme-data-t)
+  (signed-text gpgme-data-t)
+  (plaintext gpgme-data-t))
+
+;;; Import.
+
+(defbitfield (gpgme-import-flags-t :unsigned-int)
+  "Flags used for the import status field."
+  (:new #x0001)
+  (:uid #x0002)
+  (:sig #x0004)
+  (:subkey #x0008)
+  (:secret #x0010))
+
+(defctype gpgme-import-status-t :pointer
+  :documentation "An import status structure.")
+
+(defcstruct gpgme-import-status
+  "New import status structure."
+  (next gpgme-import-status-t)
+  (fpr :string)
+  (result gpgme-error-no-signal-t)
+  (status :unsigned-int))
+
+(defcstruct gpgme-op-import-result
+  "Import result structure."
+  (considered :int)
+  (no-user-id :int)
+  (imported :int)
+  (imported-rsa :int)
+  (unchanged :int)
+  (new-user-ids :int)
+  (new-sub-keys :int)
+  (new-signatures :int)
+  (new-revocations :int)
+  (secret-read :int)
+  (secret-imported :int)
+  (secret-unchanged :int)
+  (skipped-new-keys :int)
+  (not-imported :int)
+  (imports gpgme-import-status-t))
+
+(defctype gpgme-op-import-result-t :pointer
+  :documentation "An import status result structure.")
+
+(defcfun ("gpgme_op_import_result" c-gpgme-op-import-result)
+    gpgme-op-import-result-t
+  (ctx gpgme-ctx-t))
+
+(defcfun ("gpgme_op_import_start" c-gpgme-op-import-start) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (keydata gpgme-data-t))
+
+(defcfun ("gpgme_op_import" c-gpgme-op-import) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (keydata gpgme-data-t))
+
+;;; Export.
+
+(defcfun ("gpgme_op_export_start" c-gpgme-op-export-start) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (pattern :string)
+  (reserved :unsigned-int)
+  (keydata gpgme-data-t))
+
+(defcfun ("gpgme_op_export" c-gpgme-op-export) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (pattern :string)
+  (reserved :unsigned-int)
+  (keydata gpgme-data-t))
+
+;;; FIXME: Extended export interfaces require array handling.
+
+;;; Key generation.
+
+(defbitfield (gpgme-genkey-flags-t :unsigned-int)
+  "Flags used for the key generation result bitfield."
+  (:primary #x0001)
+  (:sub #x0002))
+
+(defcstruct gpgme-op-genkey-result
+  "Key generation result structure."
+  (bitfield gpgme-genkey-flags-t)
+  (fpr :string))
+
+(defctype gpgme-op-genkey-result-t :pointer
+  :documentation "A key generation result structure.")
+
+(defcfun ("gpgme_op_genkey_result" c-gpgme-op-genkey-result)
+    gpgme-op-genkey-result-t
+  (ctx gpgme-ctx-t))
+
+(defcfun ("gpgme_op_genkey_start" c-gpgme-op-genkey-start) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (parms :string)
+  (pubkey gpgme-data-t)
+  (seckey gpgme-data-t))
+
+(defcfun ("gpgme_op_genkey" c-gpgme-op-genkey) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (parms :string)
+  (pubkey gpgme-data-t)
+  (seckey gpgme-data-t))
+
+;;; Key deletion.
+
+(defcfun ("gpgme_op_delete_start" c-gpgme-op-delete-start) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (key gpgme-key-t)
+  (allow-secret :int))
+
+(defcfun ("gpgme_op_delete" c-gpgme-op-delete) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (key gpgme-key-t)
+  (allow-secret :int))
+
+;;; FIXME: Add edit interfaces.
+
+;;; Keylist interface.
+
+(defbitfield (gpgme-keylist-flags-t :unsigned-int)
+  "Flags used for the key listing result bitfield."
+  (:truncated #x0001))
+
+(defcstruct gpgme-op-keylist-result
+  "Key listing result structure."
+  (bitfield gpgme-keylist-flags-t))
+
+(defctype gpgme-op-keylist-result-t :pointer
+  :documentation "A key listing result structure.")
+
+(defcfun ("gpgme_op_keylist_result" c-gpgme-op-keylist-result)
+    gpgme-op-keylist-result-t
+  (ctx gpgme-ctx-t))
+
+(defcfun ("gpgme_op_keylist_start" c-gpgme-op-keylist-start) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (pattern :string)
+  (secret_only :boolean))
+
+;;; FIXME: Extended keylisting requires array handling.
+
+(defcfun ("gpgme_op_keylist_next" c-gpgme-op-keylist-next) gpgme-error-t
+  (ctx gpgme-ctx-t)
+  (r-key :pointer))
+
+(defcfun ("gpgme_op_keylist_end" c-gpgme-op-keylist-end) gpgme-error-t
+  (ctx gpgme-ctx-t))
+
+;;; Various functions.
+
+(defcfun ("gpgme_check_version" c-gpgme-check-version) :string
+  (req-version string-or-nil-t))
+
+(defcfun ("gpgme_get_engine_info" c-gpgme-get-engine-info) gpgme-error-t
+  (engine-info-p :pointer))
+
+(defcfun ("gpgme_set_engine_info" c-gpgme-set-engine-info) gpgme-error-t
+  (proto gpgme-protocol-t)
+  (file-name string-or-nil-t)
+  (home-dir string-or-nil-t))
+
+(defcfun ("gpgme_engine_check_version" c-gpgme-engine-check-verson)
+    gpgme-error-t
+  (proto gpgme-protocol-t))
+
+;;;
+;;;  L I S P   I N T E R F A C E
+;;;
+
+;;;
+;;; Lisp type translators.
+;;;
+
+;;; Both directions.
+
+;;; cert-int-t is a helper type that takes care of representing the
+;;; default number of certs as NIL.
+
+(defmethod translate-from-foreign (value (type (eql 'cert-int-t)))
+  (cond
+    ((eql value +include-certs-default+) nil)
+    (t value)))
+
+(defmethod translate-to-foreign (value (type (eql 'cert-int-t)))
+  (cond
+    (value value)
+    (t +include-certs-default+)))
+
+;;; string-or-nil-t translates a null pointer to NIL and vice versa.
+;;; Translation from foreign null pointer already works as expected.
+;;; FIXME: May the "to foreign" conversion problem be a bug in CFFI?
+
+(defmethod translate-to-foreign (value (type (eql 'string-or-nil-t)))
+  (cond
+    (value value)
+    (t (null-pointer))))
+
+;;; Output only.
+
+;;; These type translators only convert from foreign type, because we
+;;; never use these types in the other direction.
+
+;;; Convert gpgme-engine-info-t linked lists into a list of property
+;;; lists.  Note that this converter will automatically be invoked
+;;; recursively.
+;;;
+;;; FIXME: Should we use a hash table (or struct, or clos) instead of
+;;; property list, as recommended by the Lisp FAQ?
+
+(defmethod translate-from-foreign (value (type (eql 'gpgme-engine-info-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((next protocol file-name version req-version home-dir)
+           value gpgme-engine-info)
+        (append (list protocol (list
+                            :file-name file-name
+                            :version version
+                            :req-version req-version
+                            :home-dir home-dir))
+                next)))))
+
+(defmethod translate-from-foreign (value (type (eql 'gpgme-invalid-key-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((next fpr reason)
+           value gpgme-invalid-key)
+        (append (list (list :fpr fpr
+                            :reason reason))
+                next)))))
+
+(defmethod translate-from-foreign (value
+                                  (type (eql 'gpgme-op-encrypt-result-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((invalid-recipients)
+           value gpgme-op-encrypt-result)
+        (list :encrypt
+              (list :invalid-recipients invalid-recipients))))))
+
+(defmethod translate-from-foreign (value (type (eql 'gpgme-recipient-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((next keyid pubkey-algo status)
+           value gpgme-recipient)
+        (append (list (list :keyid keyid
+                            :pubkey-algo pubkey-algo
+                            :status status))
+                next)))))
+
+(defmethod translate-from-foreign (value
+                                  (type (eql 'gpgme-op-decrypt-result-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((unsupported-algorithm bitfield recipients file-name)
+           value gpgme-op-decrypt-result)
+        (list :decrypt (list :unsupported-algorithm unsupported-algorithm
+                             :bitfield bitfield
+                             :recipients recipients
+                             :file-name file-name))))))
+
+(defmethod translate-from-foreign (value (type (eql 'gpgme-new-signature-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((next type pubkey-algo hash-algo timestamp fpr sig-class)
+           value gpgme-new-signature)
+        (append (list (list :type type
+                            :pubkey-algo pubkey-algo
+                            :hash-algo hash-algo
+                            :timestamp timestamp
+                            :fpr fpr
+                            :sig-class sig-class))
+                next)))))
+
+(defmethod translate-from-foreign (value
+                                  (type (eql 'gpgme-op-sign-result-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((invalid-signers signatures)
+           value gpgme-op-sign-result)
+        (list :sign (list :invalid-signers invalid-signers
+                          :signatures signatures))))))
+
+(defmethod translate-from-foreign (value (type (eql 'gpgme-signature-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((next summary fpr status notations timestamp
+                 exp-timestamp bitfield validity validity-reason
+                 pubkey-algo hash-algo)
+           value gpgme-signature)
+        (append (list (list :summary summary
+                            :fpr fpr
+                            :status status
+                            :notations notations
+                            :timestamp timestamp
+                            :exp-timestamp exp-timestamp
+                            :bitfield bitfield
+                            :validity validity
+                            :validity-reason validity-reason
+                            :pubkey-algo pubkey-algo))
+                next)))))
+
+(defmethod translate-from-foreign (value
+                                  (type (eql 'gpgme-op-verify-result-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((signatures file-name)
+           value gpgme-op-verify-result)
+        (list :verify (list :signatures signatures
+                            :file-name file-name))))))
+
+(defmethod translate-from-foreign (value (type (eql 'gpgme-import-status-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((next fpr result status)
+           value gpgme-import-status)
+        (append (list (list :fpr fpr
+                            :result result
+                            :status status))
+                next)))))
+
+(defmethod translate-from-foreign (value
+                                  (type (eql 'gpgme-op-import-result-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((considered no-user-id imported imported-rsa unchanged
+                       new-user-ids new-sub-keys new-signatures
+                       new-revocations secret-read secret-imported
+                       secret-unchanged skipped-new-keys not-imported
+                       imports)
+           value gpgme-op-import-result)
+        (list :verify (list :considered considered
+                            :no-user-id no-user-id
+                            :imported imported
+                            :imported-rsa imported-rsa
+                            :unchanged unchanged
+                            :new-user-ids new-user-ids
+                            :new-sub-keys new-sub-keys
+                            :new-signatures new-signatures
+                            :new-revocations new-revocations
+                            :secret-read secret-read
+                            :secret-imported secret-imported
+                            :secret-unchanged secret-unchanged
+                            :skipped-new-keys skipped-new-keys
+                            :not-imported not-imported
+                            :imports imports))))))
+
+;;; Error handling.
+
+;;; Use gpgme-error-no-signal-t to suppress automatic error handling
+;;; at translation time.
+;;;
+;;; FIXME: Part of this probably should be in gpg-error!
+
+(define-condition gpgme-error (error)
+  ((value :initarg :gpgme-error :reader gpgme-error-value))
+  (:report (lambda (c stream)
+            (format stream "GPGME returned error: ~A (~A)"
+                    (gpgme-strerror (gpgme-error-value c))
+                    (gpgme-strsource (gpgme-error-value c)))))
+  (:documentation "Signalled when a GPGME function returns an error."))
+
+(defmethod translate-from-foreign (value (name (eql 'gpgme-error-t)))
+  "Raise a GPGME-ERROR if VALUE is non-zero."
+  (when (not (eql (gpgme-err-code value) :gpg-err-no-error))
+    (error 'gpgme-error :gpgme-error value))
+  (gpg-err-canonicalize value))
+
+(defmethod translate-to-foreign (value (name (eql 'gpgme-error-t)))
+  "Canonicalize the error value."
+  (if (eql (gpgme-err-code value) :gpg-err-no-error)
+      0
+      (gpg-err-as-value value)))
+
+(defmethod translate-from-foreign (value (name (eql 'gpgme-error-no-signal-t)))
+  "Canonicalize the error value."
+  (gpg-err-canonicalize value))
+
+
+;;; *INTERNAL* Lispy Function Interface that is still close to the C
+;;; interface.
+
+;;; Passphrase callback management.
+
+;;; Maybe: Instead, use subclassing, and provide a customizable
+;;; default implementation for ease-of-use.
+
+(defvar *passphrase-handles* (make-hash-table)
+  "Hash table with GPGME context address as key and the corresponding
+   passphrase callback object as value.")
+
+(defcallback passphrase-cb gpgme-error-t ((handle :pointer)
+                                         (uid-hint :string)
+                                         (passphrase-info :string)
+                                         (prev-was-bad :boolean)
+                                         (fd :int))
+  (handler-case
+      (let* ((passphrase-cb
+             (gethash (pointer-address handle) *passphrase-handles*))
+            (passphrase
+             (cond
+               ((functionp passphrase-cb)
+                (concatenate 'string
+                             (funcall passphrase-cb uid-hint passphrase-info
+                                      prev-was-bad)
+                             '(#\Newline)))
+               (t (concatenate 'string passphrase-cb '(#\Newline)))))
+            (passphrase-len (length passphrase))
+            ;; FIXME: Could be more robust.
+            (res (system-write fd passphrase passphrase-len)))
+       (cond
+         ((< res passphrase-len) ; FIXME: Blech.  A weak attempt to be robust.
+          (gpgme-error :gpg-err-inval))
+         (t (gpgme-error :gpg-err-no-error))))
+    (gpgme-error (err) (gpgme-error-value err))
+    (system-error (err) (gpgme-error-from-errno (system-error-errno err)))
+    ;; FIXME: The original error gets lost here.  
+    (condition (err) (progn
+                      (when *debug*
+                        (format t "DEBUG: passphrase-cb: Unexpressable: ~A~%"
+                                err))
+                      (gpgme-error :gpg-err-general)))))
+
+;;; CTX is a C-pointer to the context.
+(defun gpgme-set-passphrase-cb (ctx cb)
+  "Set the passphrase callback for CTX."
+  (let ((handle (pointer-address ctx)))
+    (cond
+      (cb (setf (gethash handle *passphrase-handles*) cb)
+         (c-gpgme-set-passphrase-cb ctx (callback passphrase-cb) ctx))
+      (t (c-gpgme-set-passphrase-cb ctx (null-pointer) (null-pointer))
+        (remhash handle *passphrase-handles*)))))
+
+;;; Progress callback management.
+
+;;; Maybe: Instead, use subclassing, and provide a customizable
+;;; default implementation for ease-of-use.
+
+(defvar *progress-handles* (make-hash-table)
+  "Hash table with GPGME context address as key and the corresponding
+   progress callback object as value.")
+
+(defcallback progress-cb :void ((handle :pointer)
+                               (what :string)
+                               (type :int)
+                               (current :int)
+                               (total :int))
+  (handler-case
+      (let* ((progress-cb
+             (gethash (pointer-address handle) *progress-handles*)))
+       (funcall progress-cb what type current total))
+    ;; FIXME: The original error gets lost here.  
+    (condition (err) (when *debug*
+                      (format t "DEBUG: progress-cb: Unexpressable: ~A~%"
+                              err)))))
+
+;;; CTX is a C-pointer to the context.
+(defun gpgme-set-progress-cb (ctx cb)
+  "Set the progress callback for CTX."
+  (let ((handle (pointer-address ctx)))
+    (cond
+      (cb (setf (gethash handle *progress-handles*) cb)
+         (c-gpgme-set-progress-cb ctx (callback progress-cb) ctx))
+      (t (c-gpgme-set-progress-cb ctx (null-pointer) (null-pointer))
+        (remhash handle *progress-handles*)))))
+
+;;; Context management.
+
+(defun gpgme-new (&key (protocol :openpgp) armor textmode include-certs
+                 keylist-mode passphrase progress file-name home-dir)
+  "Allocate a new GPGME context."
+  (with-foreign-object (ctx-p 'gpgme-ctx-t)
+    (c-gpgme-new ctx-p)
+    (let ((ctx (mem-ref ctx-p 'gpgme-ctx-t)))
+      ;;; Set locale?
+      (gpgme-set-protocol ctx protocol)
+      (gpgme-set-armor ctx armor)
+      (gpgme-set-textmode ctx textmode)
+      (when include-certs (gpgme-set-include-certs ctx include-certs))
+      (when keylist-mode (gpgme-set-keylist-mode ctx keylist-mode))
+      (gpgme-set-passphrase-cb ctx passphrase)
+      (gpgme-set-progress-cb ctx progress)
+      (gpgme-set-engine-info ctx protocol
+                            :file-name file-name :home-dir home-dir)
+      (when *debug* (format t "DEBUG: gpgme-new: ~A~%" ctx))
+      ctx)))
+
+(defun gpgme-release (ctx)
+  "Release a GPGME context."
+  (when *debug* (format t "DEBUG: gpgme-release: ~A~%" ctx))
+  (c-gpgme-release ctx))
+
+(defun gpgme-set-protocol (ctx proto)
+  "Set the protocol to be used by CTX to PROTO."
+  (c-gpgme-set-protocol ctx proto))
+
+(defun gpgme-get-protocol (ctx)
+  "Get the protocol used with CTX."
+  (c-gpgme-get-protocol ctx))
+
+;;; FIXME: How to do pretty printing?
+;;;
+;;; gpgme-get-protocol-name
+
+(defun gpgme-set-armor (ctx armor)
+  "If ARMOR is true, enable armor mode in CTX, disable it otherwise."
+ (c-gpgme-set-armor ctx armor))
+
+(defun gpgme-armor-p (ctx)
+  "Return true if armor mode is set for CTX."
+  (c-gpgme-get-armor ctx))
+
+(defun gpgme-set-textmode (ctx textmode)
+  "If TEXTMODE is true, enable text mode mode in CTX, disable it otherwise."
+ (c-gpgme-set-textmode ctx textmode))
+
+(defun gpgme-textmode-p (ctx)
+  "Return true if text mode mode is set for CTX."
+  (c-gpgme-get-textmode ctx))
+
+(defun gpgme-set-include-certs (ctx &optional certs)
+  "Include up to CERTS certificates in an S/MIME message."
+  (c-gpgme-set-include-certs ctx certs))
+
+(defun gpgme-get-include-certs (ctx)
+  "Return the number of certs to include in an S/MIME message,
+   or NIL if the default is used."
+  (c-gpgme-get-include-certs ctx))
+
+(defun gpgme-get-keylist-mode (ctx)
+  "Get the keylist mode in CTX."
+  (c-gpgme-get-keylist-mode ctx))
+
+(defun gpgme-set-keylist-mode (ctx mode)
+  "Set the keylist mode in CTX."
+  (c-gpgme-set-keylist-mode ctx mode))
+
+
+;;; FIXME: How to handle locale?  cffi-grovel?
+
+(defun gpgme-get-engine-info (&optional ctx)
+  "Retrieve the engine info for CTX, or the default if CTX is omitted."
+  (cond
+    (ctx (c-gpgme-ctx-get-engine-info ctx))
+    (t (with-foreign-object (info-p 'gpgme-engine-info-t)
+        (c-gpgme-get-engine-info info-p)
+        (mem-ref info-p 'gpgme-engine-info-t)))))
+
+(defun gpgme-set-engine-info (ctx proto &key file-name home-dir)
+  "Set the engine info for CTX, or the default if CTX is NIL."
+  (cond
+    (ctx (c-gpgme-ctx-set-engine-info ctx proto file-name home-dir))
+    (t (c-gpgme-set-engine-info proto file-name home-dir))))
+
+;;; FIXME: How to do pretty printing?
+;;;
+;;; gpgme_pubkey_algo_name, gpgme_hash_algo_name
+
+(defun gpgme-set-signers (ctx keys)
+  "Set the signers for the context CTX."
+  (c-gpgme-signers-clear ctx)
+  (dolist (key keys) (c-gpgme-signers-add ctx key)))
+
+;;;
+
+(defun gpgme-set-sig-notation (ctx notations)
+  "Set the sig notation for the context CTX."
+  (c-gpgme-sig-notation-clear ctx)
+  (dolist (notation notations)
+    (c-gpgme-sig-notation-add
+     ctx (first notation) (second notation) (third notation))))
+
+(defun gpgme-get-sig-notation (ctx)
+  "Get the signature notation data for the context CTX."
+  (c-gpgme-sig-notation-get ctx))
+
+;;; FIXME: Add I/O callback interface, for integration with clg.
+
+;;; FIXME: Add gpgme_wait?
+
+;;; Streams
+;;; -------
+;;;
+;;; GPGME uses standard streams.  You can define your own streams, or
+;;; use the existing file or string streams.
+;;;
+;;; A stream-spec is either a stream, or a list with a stream as its
+;;; first argument followed by keyword parameters: encoding,
+;;; file-name.
+;;;
+;;; FIXME: Eventually, we should provide a class that can be mixed
+;;; into stream classes and which provides accessors for encoding and
+;;; file-names.  This interface should be provided in addition to the
+;;; above sleazy interface, because the sleazy interface is easier to
+;;; use (less typing), and is quite sufficient in a number of cases.
+;;;
+;;; For best results, streams with element type (unsigned-byte 8)
+;;; should be used.  Character streams may work if armor mode is used.
+
+;;; Do we need to provide access to GPGME data objects through streams
+;;; as well?  It seems to me that specific optimizations, like
+;;; directly writing to file descriptors, is better done by extending
+;;; the sleazy syntax (stream-spec) instead of customized streams.
+;;; Customized streams do buffering, and this may mess up things.  Mmh.
+
+(defvar *data-handles* (make-hash-table)
+  "Hash table with GPGME data user callback handle address as key
+   and the corresponding stream as value.")
+
+;;; The release callback removes the stream from the *data-handles*
+;;; hash and releases the CBS structure that is used as the key in
+;;; that hash.  It is implicitely invoked (through GPGME) by
+;;; gpgme-data-release.
+(defcallback data-release-cb :void ((handle :pointer))
+  (unwind-protect (remhash (pointer-address handle) *data-handles*)
+    (when (not (null-pointer-p handle)) (foreign-free handle))))
+
+(defcallback data-read-cb ssize-t ((handle :pointer) (buffer :pointer)
+                                  (size size-t))
+  (when *debug* (format t "DEBUG: gpgme-data-read-cb: want ~A~%" size))
+  (let ((stream (gethash (pointer-address handle) *data-handles*)))
+    (cond
+      (stream
+       (let* ((stream-type (stream-element-type stream))
+             (seq (make-array size :element-type stream-type))
+             (read (read-sequence seq stream)))
+        (loop for i from 0 to (- read 1)
+              do (setf (mem-aref buffer :unsigned-char i)
+                       ;;; FIXME: This is a half-assed attempt at
+                       ;;; supporting character streams.
+                       (cond
+                         ((eql stream-type 'character)
+                          (char-code (elt seq i)))
+                         (t (coerce (elt seq i) stream-type)))))
+        (when *debug* (format t "DEBUG: gpgme-data-read-cb: read ~A~%" read))
+        read))
+      (t (set-errno +ebadf+)
+        -1))))
+
+(defcallback data-write-cb ssize-t ((handle :pointer) (buffer :pointer)
+                                  (size size-t))
+  (when *debug* (format t "DEBUG: gpgme-data-write-cb: want ~A~%" size))
+  (let ((stream (gethash (pointer-address handle) *data-handles*)))
+    (cond
+      (stream
+       (let* ((stream-type (stream-element-type stream))
+             (seq (make-array size :element-type stream-type)))
+        (loop for i from 0 to (- size 1)
+              do (setf (elt seq i)
+                       ;;; FIXME: This is a half-assed attempt at
+                       ;;; supporting character streams.
+                       (cond
+                         ((eql stream-type 'character)
+                          (code-char (mem-aref buffer :unsigned-char i)))
+                         (t (coerce (mem-aref buffer :unsigned-char i)
+                                    stream-type)))))
+        (write-sequence seq stream)
+        ;;; FIXME: What about write errors?
+        size))
+      (t (set-errno +ebadf+)
+        -1))))
+
+;;; This little helper macro allows us to swallow the cbs structure by
+;;; simply setting it to a null pointer, but still protect against
+;;; conditions.
+(defmacro with-cbs-swallowed ((cbs) &body body)
+  `(let ((,cbs (foreign-alloc 'gpgme-data-cbs)))
+    (unwind-protect (progn ,@body)
+      (when (not (null-pointer-p ,cbs)) (foreign-free ,cbs)))))
+
+;;; FIXME: Wrap the object and attach to it a finalizer.  Requires new
+;;; CFFI.  Should we use an OO interface, ie make-instance?  For now,
+;;; we do not provide direct access to data objects.
+(defun gpgme-data-new (stream &key encoding file-name)
+  "Allocate a new GPGME data object for STREAM."
+  (with-foreign-object (dh-p 'gpgme-data-t)
+    ;;; We allocate one CBS structure for each stream we wrap in a
+    ;;; data object.  Although we could also share all these
+    ;;; structures, as they contain the very same callbacks, we need a
+    ;;; unique C pointer as handle anyway to look up the stream in the
+    ;;; callback.  This is a convenient one to use.
+    (with-cbs-swallowed (cbs)
+      (setf
+       (foreign-slot-value cbs 'gpgme-data-cbs 'read) (callback data-read-cb)
+       (foreign-slot-value cbs 'gpgme-data-cbs 'write) (callback data-write-cb)
+       (foreign-slot-value cbs 'gpgme-data-cbs 'seek) (null-pointer)
+       (foreign-slot-value cbs 'gpgme-data-cbs 'release) (callback
+                                                         data-release-cb))
+      (c-gpgme-data-new-from-cbs dh-p cbs cbs)
+      (let ((dh (mem-ref dh-p 'gpgme-data-t)))
+       (when encoding (gpgme-data-set-encoding dh encoding))
+       (when file-name (gpgme-data-set-file-name dh file-name))
+       ;;; Install the stream into the hash table and swallow the cbs
+        ;;; structure while protecting against any errors.
+       (unwind-protect
+            (progn
+              (setf (gethash (pointer-address cbs) *data-handles*) stream)
+              (setf cbs (null-pointer)))
+         (when (not (null-pointer-p cbs)) (c-gpgme-data-release dh)))
+       (when *debug* (format t "DEBUG: gpgme-data-new: ~A~%" dh))
+       dh))))
+
+;;; This function releases a GPGME data object.  It implicitely
+;;; invokes the data-release-cb function to clean up associated junk.
+(defun gpgme-data-release (dh)
+  "Release a GPGME data object."
+  (when *debug* (format t "DEBUG: gpgme-data-release: ~A~%" dh))
+  (c-gpgme-data-release dh))
+
+(defmacro with-gpgme-data ((dh streamspec) &body body)
+  `(let ((,dh (if (listp ,streamspec)
+                 (apply 'gpgme-data-new ,streamspec)
+                 (gpgme-data-new ,streamspec))))
+    (unwind-protect (progn ,@body)
+      (when (not (null-pointer-p ,dh)) (gpgme-data-release ,dh)))))
+
+(defun gpgme-data-get-encoding (dh)
+  "Get the encoding associated with the data object DH."
+  (c-gpgme-data-get-encoding dh))
+
+(defun gpgme-data-set-encoding (dh encoding)
+  "Set the encoding associated with the data object DH to ENCODING."
+  (c-gpgme-data-set-encoding dh encoding))
+
+(defun gpgme-data-get-file-name (dh)
+  "Get the file name associated with the data object DH."
+  (c-gpgme-data-get-file-name dh))
+
+(defun gpgme-data-set-file-name (dh file-name)
+  "Set the file name associated with the data object DH to FILE-NAME."
+  (c-gpgme-data-set-file-name dh file-name))
+
+;;; FIXME: Add key accessor interfaces.
+
+(defun gpgme-get-key (ctx fpr &optional secret)
+  "Get the key with the fingerprint FPR from the context CTX."
+  (with-foreign-object (key-p 'gpgme-key-t)
+    (c-gpgme-get-key ctx fpr key-p secret)
+    (mem-ref key-p 'gpgme-key-t)))
+
+(defun gpgme-key-ref (key)
+  "Acquire an additional reference to the key KEY."
+  (when *debug* (format t "DEBUG: gpgme-key-ref: ~A~%" key))
+  (c-gpgme-key-ref key))
+
+(defun gpgme-key-unref (key)
+  "Release a reference to the key KEY."
+  (when *debug* (format t "DEBUG: gpgme-key-unref: ~A~%" key))
+  (c-gpgme-key-unref key))
+
+;;; FIXME: We REALLY need pretty printing for keys and all the other
+;;; big structs.
+
+;;; Various interfaces.
+
+(defun gpgme-check-version (&optional req-version)
+  (c-gpgme-check-version req-version))
+
+;;;
+;;; The *EXPORTED* CLOS interface.
+;;;
+
+;;; The context type.
+
+;;; We wrap the C context pointer into a class object to be able to
+;;; stick a finalizer on it.
+
+(defclass context ()
+  (c-ctx  ; The C context object pointer.
+   signers ; The list of signers.
+   sig-notation) ; The list of signers.
+  (:documentation "The GPGME context type."))
+
+(defmethod initialize-instance :after ((ctx context) &rest rest
+                                      &key &allow-other-keys)
+  (let ((c-ctx (apply #'gpgme-new rest))
+       (cleanup t))
+    (unwind-protect
+        (progn (setf (slot-value ctx 'c-ctx) c-ctx)
+               (finalize ctx (lambda () (gpgme-release c-ctx)))
+               (setf cleanup nil))
+      (if cleanup (gpgme-release c-ctx)))))
+
+(defmethod translate-to-foreign (value (type (eql 'gpgme-ctx-t)))
+  ;; Allow a pointer to be passed directly for the finalizer to work.
+  (if (pointerp value) value (slot-value value 'c-ctx)))
+
+(defmacro context (&rest rest)
+  "Create a new GPGME context."
+  `(make-instance 'context ,@rest))
+
+;;; The context type: Accessor functions.
+
+;;; The context type: Accessor functions: Protocol.
+
+(defgeneric protocol (ctx)
+  (:documentation "Get the protocol of CONTEXT."))
+
+(defmethod protocol ((ctx context))
+  (gpgme-get-protocol ctx))
+
+(defgeneric (setf protocol) (protocol ctx)
+  (:documentation "Set the protocol of CONTEXT to PROTOCOL."))
+
+;;; FIXME: Adjust translator to reject invalid protocols.  Currently,
+;;; specifing an invalid protocol throws a "NIL is not 32 signed int"
+;;; error.  This is suboptimal.
+(defmethod (setf protocol) (protocol (ctx context))
+  (gpgme-set-protocol ctx protocol))
+
+;;; The context type: Accessor functions: Armor.
+;;; FIXME: Is it good style to make foop setf-able?  Or should it be
+;;; foo/foop for set/get?
+
+(defgeneric armorp (ctx)
+  (:documentation "Get the armor flag of CONTEXT."))
+
+(defmethod armorp ((ctx context))
+  (gpgme-armor-p ctx))
+
+(defgeneric (setf armorp) (armor ctx)
+  (:documentation "Set the armor flag of CONTEXT to ARMOR."))
+
+(defmethod (setf armorp) (armor (ctx context))
+  (gpgme-set-armor ctx armor))
+
+;;; The context type: Accessor functions: Textmode.
+;;; FIXME: Is it good style to make foop setf-able?  Or should it be
+;;; foo/foop for set/get?
+
+(defgeneric textmodep (ctx)
+  (:documentation "Get the text mode flag of CONTEXT."))
+
+(defmethod textmodep ((ctx context))
+  (gpgme-textmode-p ctx))
+
+(defgeneric (setf textmodep) (textmode ctx)
+  (:documentation "Set the text mode flag of CONTEXT to TEXTMODE."))
+
+(defmethod (setf textmodep) (textmode (ctx context))
+  (gpgme-set-textmode ctx textmode))
+
+;;; The context type: Accessor functions: Include Certs.
+
+(defgeneric include-certs (ctx)
+  (:documentation "Get the number of included certificates in an
+                   S/MIME message, or NIL if the default is used."))
+
+(defmethod include-certs ((ctx context))
+  (gpgme-get-include-certs ctx))
+
+(defgeneric (setf include-certs) (certs ctx)
+  (:documentation "Return the number of certificates to include in an
+                   S/MIME message, or NIL if the default is used."))
+
+(defmethod (setf include-certs) (certs (ctx context))
+  (gpgme-set-include-certs ctx certs))
+
+;;; The context type: Accessor functions: Engine info.
+
+(defgeneric engine-info (ctx)
+  (:documentation "Retrieve the engine info for CTX."))
+
+(defmethod engine-info ((ctx context))
+  (gpgme-get-engine-info ctx))
+
+(defgeneric (setf engine-info) (info ctx)
+  (:documentation "Set the engine info for CTX."))
+
+(defmethod (setf engine-info) (info (ctx context))
+  (dolist (proto '(:openpgp :cms))
+    (let ((pinfo (getf info proto)))
+      (when pinfo
+       (gpgme-set-engine-info ctx proto :file-name (getf pinfo :file-name)
+                              :home-dir (getf pinfo :home-dir))))))
+
+;;; The context type: Accessor functions: Keylist mode.
+
+(defgeneric keylist-mode (ctx)
+  (:documentation "Get the keylist mode of CTX."))
+
+(defmethod keylist-mode ((ctx context))
+  (gpgme-get-keylist-mode ctx))
+
+(defgeneric (setf keylist-mode) (mode ctx)
+  (:documentation "Set the keylist mode of CTX to MODE."))
+
+(defmethod (setf keylist-mode) (mode (ctx context))
+  (gpgme-set-keylist-mode ctx mode))
+
+;;; The context type: Accessor functions: Signers.
+
+(defgeneric signers (ctx)
+  (:documentation "Get the signers of CTX."))
+
+(defmethod signers ((ctx context))
+  (slot-value ctx 'signers))
+
+(defgeneric (setf signers) (signers ctx)
+  (:documentation "Set the signers of CTX to SIGNERS."))
+
+(defmethod (setf keylist-mode) (signers (ctx context))
+  (gpgme-set-signers ctx signers)
+  (setf (slot-value ctx 'signers) signers))
+
+;;; The context type: Accessor functions: Sig notations.
+
+(defgeneric sig-notations (ctx)
+  (:documentation "Get the signature notations of CTX."))
+
+(defmethod sig-notations ((ctx context))
+  (slot-value ctx 'signers))
+
+(defgeneric (setf sig-notations) (notations ctx)
+  (:documentation "Set the signatire notations of CTX to NOTATIONS."))
+
+(defmethod (setf sig-notations) (notations (ctx context))
+  (gpgme-set-signers ctx notations)
+  (setf (slot-value ctx 'notations) notations))
+
+;;; The context type: Support macros.
+
+(defmacro with-context ((ctx &rest rest) &body body)
+  `(let ((,ctx (make-instance 'context ,@rest)))
+    ,@body))
+
+;;; The key type.
+
+(defclass key ()
+  (c-key)  ; The C key object pointer.
+  (:documentation "The GPGME key type."))
+
+;;; In the initializer, we swallow the c-key argument.
+(defmethod initialize-instance :after ((key key) &key c-key
+                                      &allow-other-keys)
+  (setf (slot-value key 'c-key) c-key)
+  (finalize key (lambda () (gpgme-key-unref c-key))))
+
+(defmethod translate-from-foreign (value (type (eql 'gpgme-key-t)))
+  (when *debug* (format t "DEBUG: import key: ~A~%" value))
+  (make-instance 'key :c-key value))
+
+(defmethod translate-to-foreign (value (type (eql 'gpgme-key-t)))
+  ;; Allow a pointer to be passed directly for the finalizer to work.
+  (if (pointerp value) value (slot-value value 'c-key)))
+
+(defmethod print-object ((key key) stream)
+  (print-unreadable-object (key stream :type t :identity t)
+    (format stream "~s" (fpr key))))
+
+;;; The key type: Accessor functions.
+
+;;; FIXME: The bitfield and flags contain redundant information at
+;;; this point.  FIXME: Deal nicer with zero-length name (policy url)
+;;; and zero length value (omit?) and human-readable (convert to string).
+;;; FIXME: Turn binary data into sequence or vector or what it should be.
+;;; FIXME: Turn the whole thing into a hash?
+(defmethod translate-from-foreign (value (type (eql 'gpgme-sig-notation-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((next name value name-len value-len flags bitfield)
+           value gpgme-sig-notation)
+        (append (list (list
+                       :name name
+                       :value value
+                       :name-len name-len
+                       :value-len value-len
+                       :flags flags
+                       :bitfield bitfield))
+                next)))))
+
+;;; FIXME: Deal nicer with timestamps.  bitfield field name?
+(defmethod translate-from-foreign (value (type (eql 'gpgme-subkey-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((next bitfield pubkey-algo length keyid fpr timestamp expires)
+           value gpgme-subkey)
+        (append (list (list
+                       :bitfield bitfield
+                       :pubkey-algo pubkey-algo
+                       :length length
+                       :keyid keyid
+                       :fpr fpr
+                       :timestamp timestamp
+                       :expires expires))
+                next)))))
+
+(defmethod translate-from-foreign (value (type (eql 'gpgme-key-sig-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((next bitfield pubkey-algo keyid timestamp expires status
+                 uid name email comment sig-class)
+           value gpgme-key-sig)
+        (append (list (list
+                       :bitfield bitfield
+                       :pubkey-algo pubkey-algo
+                       :keyid keyid
+                       :timestamp timestamp
+                       :expires expires
+                       :status status
+                       :uid uid
+                       :name name
+                       :email email
+                       :comment comment
+                       :sig-class sig-class))
+                next)))))
+
+(defmethod translate-from-foreign (value (type (eql 'gpgme-user-id-t)))
+  (cond
+    ((null-pointer-p value) nil)
+    (t (with-foreign-slots
+          ((next bitfield validity uid name email comment signatures)
+           value gpgme-user-id)
+        (append (list (list
+                       :bitfield bitfield
+                       :validity validity
+                       :uid uid
+                       :name name
+                       :email email
+                       :comment comment
+                       :signatures signatures))
+                next)))))
+
+(defun key-data (key)
+  (with-slots (c-key) key
+    (with-foreign-slots
+       ((bitfield protocol issuer-serial issuer-name chain-id
+                  owner-trust subkeys uids keylist-mode)
+        c-key gpgme-key)
+      (list
+       :bitfield bitfield
+       :protocol protocol
+       :issuer-serial issuer-serial
+       :issuer-name issuer-name
+       :chain-id chain-id
+       :owner-trust owner-trust
+       :subkeys subkeys
+       :uids uids
+       :keylist-mode keylist-mode))
+    ))
+
+
+(defgeneric fpr (key)
+  (:documentation "Get the primary fingerprint of the key."))
+
+(defmethod fpr ((key key))
+  (getf (car (getf (key-data key) :subkeys)) :fpr))
+
+
+;;; The context type: Crypto-Operations.
+
+(defgeneric get-key (ctx fpr &optional secret)
+  (:documentation "Get the (secret) key FPR from CTX."))
+
+(defmethod get-key ((ctx context) fpr &optional secret)
+  (gpgme-get-key ctx fpr secret))
+
+;;; Encrypt.
+
+(defgeneric op-encrypt (ctx recp plain cipher &key always-trust sign)
+  (:documentation "Encrypt."))
+
+(defmethod op-encrypt ((ctx context) recp plain cipher
+                      &key always-trust sign)
+  (with-foreign-object (c-recp :pointer (+ 1 (length recp)))
+    (dotimes (i (length recp))
+      (setf (mem-aref c-recp 'gpgme-key-t i) (elt recp i)))
+    (setf (mem-aref c-recp :pointer (length recp)) (null-pointer))
+    (with-gpgme-data (in plain)
+      (with-gpgme-data (out cipher)
+       (let ((flags))
+         (if always-trust (push :always-trust flags))
+         (cond
+           (sign
+            (c-gpgme-op-encrypt-sign ctx c-recp flags in out)
+            (append (c-gpgme-op-encrypt-result ctx)
+                    (c-gpgme-op-sign-result ctx)))
+           (t
+            (c-gpgme-op-encrypt ctx c-recp flags in out)
+            (c-gpgme-op-encrypt-result ctx))))))))
+
+;;; Decrypt.
+
+(defgeneric op-decrypt (ctx cipher plain &key verify)
+  (:documentation "Decrypt."))
+
+(defmethod op-decrypt ((ctx context) cipher plain &key verify)
+  (with-gpgme-data (in cipher)
+    (with-gpgme-data (out plain)
+      (cond
+       (verify
+        (c-gpgme-op-decrypt-verify ctx in out)
+        (append (c-gpgme-op-decrypt-result ctx)
+                (c-gpgme-op-verify-result ctx)))
+       (t
+        (c-gpgme-op-decrypt ctx in out)
+        (c-gpgme-op-decrypt-result ctx))))))
+
+;;; Signing.
+
+(defgeneric op-sign (ctx plain sig &optional mode)
+  (:documentation "Sign."))
+
+(defmethod op-sign ((ctx context) plain sig &optional (mode :none))
+  (with-gpgme-data (in plain)
+    (with-gpgme-data (out sig)
+      (c-gpgme-op-sign ctx in out mode)
+      (c-gpgme-op-sign-result ctx))))
+
+;;; Verify.
+
+(defgeneric op-verify (ctx sig text &key detached)
+  (:documentation "Verify."))
+
+(defmethod op-verify ((ctx context) sig text &key detached)
+  (with-gpgme-data (in sig)
+    (with-gpgme-data (on text)
+      (c-gpgme-op-verify ctx in (if detached on nil)
+                        (if detached nil on))
+      (c-gpgme-op-verify-result ctx))))
+
+;;; Import.
+
+(defgeneric op-import (ctx keydata)
+  (:documentation "Import."))
+
+(defmethod op-import ((ctx context) keydata)
+  (with-gpgme-data (in keydata)
+    (c-gpgme-op-import ctx in)
+    (c-gpgme-op-import-result ctx)))
+
+;;; Export.
+
+(defgeneric op-export (ctx pattern keydata)
+  (:documentation "Export public key data matching PATTERN to the
+                   stream KEYDATA."))
+
+(defmethod op-export ((ctx context) pattern keydata)
+  (with-gpgme-data (dh keydata)
+    (c-gpgme-op-export ctx pattern 0 dh)))
+
+;;; Key generation.
+
+
+;;;
+;;; Initialization
+;;;
+
+(defun check-version (&optional req-version)
+  "Check that the GPGME version requirement is satisfied."
+  (gpgme-check-version req-version))
+
+(defparameter *version* (check-version)
+  "The version number of GPGME used.")