Made exporting GPG key to keyserver work on Windows.
[claws.git] / src / plugins / pgpcore / sgpgme.c
index 371a7f0432d9cb3514fb606aad5370a52b4d0db4..90c759a36a1568704e88e486b692622da1cf2e6d 100644 (file)
@@ -14,7 +14,6 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
  */
 
 #ifdef HAVE_CONFIG_H
@@ -36,6 +35,9 @@
 #include <sys/types.h>
 #ifndef G_OS_WIN32
 #  include <sys/wait.h>
+#else
+#  include <pthread.h>
+#  include <windows.h>
 #endif
 #if (defined(__DragonFly__) || defined(SOLARIS) || defined (__NetBSD__) || defined (__FreeBSD__) || defined (__OpenBSD__))
 #  include <sys/signal.h>
@@ -56,6 +58,7 @@
 #include "prefs_gpg.h"
 #include "account.h"
 #include "select-keys.h"
+#include "claws.h"
 
 static void sgpgme_disable_all(void)
 {
@@ -169,6 +172,23 @@ static const gchar *get_owner_trust_str(unsigned long owner_trust)
        }
 }
 
+gchar *get_gpg_executable_name()
+{
+       gpgme_engine_info_t e;
+
+       if (!gpgme_get_engine_info(&e)) {
+               while (e != NULL) {
+                       if (e->protocol == GPGME_PROTOCOL_OpenPGP
+                                       && e->file_name != NULL) {
+                               debug_print("Found gpg executable: '%s'\n", e->file_name);
+                               return e->file_name;
+                       }
+               }
+       }
+
+       return NULL;
+}
+
 static gchar *extract_name(const char *uid)
 {
        if (uid == NULL)
@@ -218,17 +238,8 @@ gchar *sgpgme_sigstat_info_short(gpgme_ctx_t ctx, gpgme_verify_result_t status)
        } else if (gpg_err_code(err) != GPG_ERR_NO_ERROR && gpg_err_code(err) != GPG_ERR_EOF) {
                return g_strdup_printf(_("The signature can't be checked - %s"), 
                        gpgme_strerror(err));
-       } else if (gpg_err_code(err) != GPG_ERR_NO_ERROR && gpg_err_code(err) == GPG_ERR_EOF) {
-               /*
-                * When gpg is upgraded to gpg-v21 then installer tries to migrate the old
-                * gpg keyrings found in ~/.gnupg to the new version. If the keyrings contain
-                * very old keys using ciphers no more supported in gpg-v21 this transition
-                * can fail and the left-over ~/.gnupg/pubring.gpg will cause claws to crash
-                * when the above condition is meet.
-                */
-               return g_strdup_printf(_("The signature can't be checked - %s."),
-                        gpgme_strerror(err));
-       }
+  }
+
        if (key)
                uname = extract_name(key->uids->uid);
        else
@@ -236,7 +247,7 @@ gchar *sgpgme_sigstat_info_short(gpgme_ctx_t ctx, gpgme_verify_result_t status)
 
        switch (gpg_err_code(sig->status)) {
        case GPG_ERR_NO_ERROR:
-               switch (key->uids?key->uids->validity:GPGME_VALIDITY_UNKNOWN) {
+               switch ((key && key->uids) ? key->uids->validity : GPGME_VALIDITY_UNKNOWN) {
                case GPGME_VALIDITY_ULTIMATE:
                        result = g_strdup_printf(_("Good signature from \"%s\" [ultimate]"), uname);
                        break;
@@ -250,7 +261,13 @@ gchar *sgpgme_sigstat_info_short(gpgme_ctx_t ctx, gpgme_verify_result_t status)
                case GPGME_VALIDITY_UNDEFINED:
                case GPGME_VALIDITY_NEVER:
                default:
-                       result = g_strdup_printf(_("Good signature from \"%s\""), uname);
+                       if (key) {
+                               result = g_strdup_printf(_("Good signature from \"%s\""), uname);
+                       } else {
+                               gchar *id = g_strdup(sig->fpr + strlen(sig->fpr)-8);
+                               result = g_strdup_printf(_("Key 0x%s not available to verify this signature"), id);
+                               g_free(id);
+                       }
                        break;
                }
                break;
@@ -335,7 +352,7 @@ gchar *sgpgme_sigstat_info_full(gpgme_ctx_t ctx, gpgme_verify_result_t status)
                case GPG_ERR_NO_ERROR:
                        g_string_append_printf(siginfo,
                                _("Good signature from uid \"%s\" (Validity: %s)\n"),
-                               uid, get_validity_str(key->uids?key->uids->validity:GPGME_VALIDITY_UNKNOWN));
+                               uid, get_validity_str((key && key->uids) ? key->uids->validity:GPGME_VALIDITY_UNKNOWN));
                        break;
                case GPG_ERR_KEY_EXPIRED:
                        g_string_append_printf(siginfo,
@@ -345,7 +362,7 @@ gchar *sgpgme_sigstat_info_full(gpgme_ctx_t ctx, gpgme_verify_result_t status)
                case GPG_ERR_SIG_EXPIRED:
                        g_string_append_printf(siginfo,
                                _("Expired signature from uid \"%s\" (Validity: %s)\n"),
-                               uid, get_validity_str(key->uids?key->uids->validity:GPGME_VALIDITY_UNKNOWN));
+                               uid, get_validity_str((key && key->uids) ? key->uids->validity:GPGME_VALIDITY_UNKNOWN));
                        break;
                case GPG_ERR_CERT_REVOKED:
                        g_string_append_printf(siginfo,
@@ -362,17 +379,20 @@ gchar *sgpgme_sigstat_info_full(gpgme_ctx_t ctx, gpgme_verify_result_t status)
                }
                if (sig->status != GPG_ERR_BAD_SIGNATURE) {
                        gint j = 1;
-                       key->uids = key->uids ? key->uids->next : NULL;
-                       while (key->uids != NULL) {
-                               g_string_append_printf(siginfo,
-                                       _("                    uid \"%s\" (Validity: %s)\n"),
-                                       key->uids->uid,
-                                       key->uids->revoked==TRUE?_("Revoked"):get_validity_str(key->uids->validity));
-                               j++;
-                               key->uids = key->uids->next;
+                       if (key) {
+                               key->uids = key->uids ? key->uids->next : NULL;
+                               while (key->uids != NULL) {
+                                       g_string_append_printf(siginfo,
+                                               g_strconcat("                    ",
+                                                           _("uid \"%s\" (Validity: %s)\n"), NULL),
+                                               key->uids->uid,
+                                               key->uids->revoked==TRUE?_("Revoked"):get_validity_str(key->uids->validity));
+                                       j++;
+                                       key->uids = key->uids->next;
+                               }
                        }
                        g_string_append_printf(siginfo,_("Owner Trust: %s\n"),
-                                              get_owner_trust_str(key->owner_trust));
+                                              key ? get_owner_trust_str(key->owner_trust) : _("No key!"));
                        g_string_append(siginfo,
                                _("Primary key fingerprint:"));
                        const char* primary_fpr = NULL;
@@ -454,7 +474,7 @@ gpgme_data_t sgpgme_decrypt_verify(gpgme_data_t cipher, gpgme_verify_result_t *s
        
        if (gpgme_get_protocol(ctx) == GPGME_PROTOCOL_OpenPGP) {
                prefs_gpg_enable_agent(prefs_gpg_get_config()->use_gpg_agent);
-               if (!getenv("GPG_AGENT_INFO") || !prefs_gpg_get_config()->use_gpg_agent) {
+               if (!g_getenv("GPG_AGENT_INFO") || !prefs_gpg_get_config()->use_gpg_agent) {
                        info.c = ctx;
                        gpgme_set_passphrase_cb (ctx, gpgmegtk_passphrase_cb, &info);
                }
@@ -644,12 +664,16 @@ void sgpgme_init()
 {
        gchar *ctype_locale = NULL, *messages_locale = NULL;
        gchar *ctype_utf8_locale = NULL, *messages_utf8_locale = NULL;
+       gpgme_error_t err = 0;
 
        gpgme_engine_info_t engineInfo;
 
-       if (strcmp(prefs_gpg_get_config()->gpg_path, "") != 0 &&
-           access(prefs_gpg_get_config()->gpg_path, X_OK) != -1)
-               gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP,prefs_gpg_get_config()->gpg_path, NULL);
+       if (strcmp(prefs_gpg_get_config()->gpg_path, "") != 0
+           && access(prefs_gpg_get_config()->gpg_path, X_OK) != -1) {
+               err = gpgme_set_engine_info(GPGME_PROTOCOL_OpenPGP, prefs_gpg_get_config()->gpg_path, NULL);
+               if (err != GPG_ERR_NO_ERROR)
+                       g_warning("failed to set crypto engine configuration: %s", gpgme_strerror(err));
+       }
 
        if (gpgme_check_version("1.0.0")) {
 #ifdef LC_CTYPE
@@ -761,6 +785,48 @@ void sgpgme_done()
         gpgmegtk_free_passphrase();
 }
 
+#ifdef G_OS_WIN32
+struct _ExportCtx {
+       gboolean done;
+       gchar *cmd;
+       DWORD exitcode;
+};
+
+static void *_export_threaded(void *arg)
+{
+       struct _ExportCtx *ctx = (struct _ExportCtx *)arg;
+       gboolean result;
+
+       PROCESS_INFORMATION pi = {0};
+       STARTUPINFO si = {0};
+
+       result = CreateProcess(NULL, ctx->cmd, NULL, NULL, FALSE,
+                       NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,
+                       NULL, NULL, &si, &pi);
+
+       if (!result) {
+               debug_print("Couldn't execute '%s'\n", ctx->cmd);
+       } else {
+               WaitForSingleObject(pi.hProcess, 10000);
+               result = GetExitCodeProcess(pi.hProcess, &ctx->exitcode);
+               if (ctx->exitcode == STILL_ACTIVE) {
+                       debug_print("Process still running, terminating it.\n");
+                       TerminateProcess(pi.hProcess, 255);
+               }
+
+               CloseHandle(pi.hProcess);
+               CloseHandle(pi.hThread);
+
+               if (!result) {
+                       debug_print("Process executed, but we couldn't get its exit code (huh?)\n");
+               }
+       }
+
+       ctx->done = TRUE;
+       return NULL;
+}
+#endif
+
 void sgpgme_create_secret_key(PrefsAccount *account, gboolean ask_create)
 {
        AlertValue val = G_ALERTDEFAULT;
@@ -774,6 +840,7 @@ void sgpgme_create_secret_key(PrefsAccount *account, gboolean ask_create)
        gpgme_ctx_t ctx;
        GtkWidget *window = NULL;
        gpgme_genkey_result_t key;
+       gboolean exported = FALSE;
 
        if (account == NULL)
                account = account_get_default();
@@ -889,8 +956,12 @@ again:
                                  GTK_STOCK_NO, "+" GTK_STOCK_YES, NULL);
                g_free(buf);
                if (val == G_ALERTALTERNATE) {
+                       gchar *gpgbin = get_gpg_executable_name();
+                       gchar *cmd = g_strdup_printf("\"%s\" --batch --no-tty --send-keys %s",
+                               (gpgbin ? gpgbin : "gpg"), key->fpr);
+                       debug_print("Executing command: %s\n", cmd);
+
 #ifndef G_OS_WIN32
-                       gchar *cmd = g_strdup_printf("gpg --no-tty --send-keys %s", key->fpr);
                        int res = 0;
                        pid_t pid = 0;
                        pid = fork();
@@ -923,15 +994,44 @@ again:
                                        }
                                } while(1);
                        }
-                       if (res == 0) {
+
+                       if (res == 0)
+                               exported = TRUE;
+#else
+                       /* We need to call gpg in a separate thread, so that waiting for
+                        * it to finish does not block the UI. */
+                       pthread_t pt;
+                       struct _ExportCtx *ectx = malloc(sizeof(struct _ExportCtx));
+
+                       ectx->done = FALSE;
+                       ectx->exitcode = STILL_ACTIVE;
+                       ectx->cmd = cmd;
+
+                       if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
+                                               _export_threaded, (void *)ectx) != 0) {
+                               debug_print("Couldn't create thread, continuing unthreaded.\n");
+                               _export_threaded(ctx);
+                       } else {
+                               debug_print("Thread created, waiting for it to finish...\n");
+                               while (!ectx->done)
+                                       claws_do_idle();
+                       }
+
+                       debug_print("Thread finished.\n");
+                       pthread_join(pt, NULL);
+
+                       if (ectx->exitcode == 0)
+                               exported = TRUE;
+
+                       g_free(ectx);
+#endif
+                       g_free(cmd);
+
+                       if (exported) {
                                alertpanel_notice(_("Key exported."));
                        } else {
                                alertpanel_error(_("Couldn't export key."));
                        }
-                       g_free(cmd);
-#else
-                       alertpanel_error(_("Key export isn't implemented in Windows."));
-#endif
                }
        }
        prefs_gpg_get_config()->gpg_ask_create_key = FALSE;