view mcabber/mcabber/pgp.c @ 1909:9c14153e2580

Do not display unhandled IQ result messages to the log window We display the message only in the debug log file, because these messages are usually ignored anyway (ideally we would create a handler explicitly when sending the initial IQ request). Thanks to VarLog for the report!
author Mikael Berthe <mikael@lilotux.net>
date Sun, 18 Apr 2010 14:14:05 +0200
parents e6d355e50d7a
children e3b66c8ead4f
line wrap: on
line source

/*
 * pgp.c        -- PGP utility functions
 *
 * Copyright (C) 2006-2009 Mikael Berthe <mikael@lilotux.net>
 * Some parts inspired by centericq (impgp.cc)
 *
 * This program 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.
 *
 * This program 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
 * 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
 */

#include <config.h>

#ifdef HAVE_GPGME

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <sys/mman.h>
#include <glib.h>

#include "pgp.h"
#include "logprint.h"

#define MIN_GPGME_VERSION "1.0.0"

static struct gpg_struct
{
  int enabled;
  char *private_key;
  char *passphrase;
} gpg;


//  gpg_init(priv_key, passphrase)
// Initialize the GPG sub-systems.  This function must be invoked early.
// Note: priv_key & passphrase are optional, they can be set later.
// This function returns 0 if gpgme is available and initialized;
// if not it returns the gpgme error code.
int gpg_init(const char *priv_key, const char *passphrase)
{
  gpgme_error_t err;

  // Check for version and OpenPGP protocol support.
  if (!gpgme_check_version(MIN_GPGME_VERSION)) {
    scr_LogPrint(LPRINT_LOGNORM,
                 "GPGME initialization error: Bad library version");
    return -1;
  }

  err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
  if (err) {
    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
                 "GPGME initialization error: %s", gpgme_strerror(err));
    return err;
  }

  // Set the locale information.
  gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
  gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));

  // Store private data.
  gpg_set_private_key(priv_key);
  gpg_set_passphrase(passphrase);

  gpg.enabled = 1;
  return 0;
}

//  gpg_terminate()
// Destroy data and free memory.
void gpg_terminate(void)
{
  gpg.enabled = 0;
  gpg_set_passphrase(NULL);
  gpg_set_private_key(NULL);
}

//  gpg_set_passphrase(passphrase)
// Set the current passphrase (use NULL to erase it).
void gpg_set_passphrase(const char *passphrase)
{
  // Remove current passphrase
  if (gpg.passphrase) {
    ssize_t len = strlen(gpg.passphrase);
    memset(gpg.passphrase, 0, len);
    munlock(gpg.passphrase, len);
    g_free(gpg.passphrase);
  }
  if (passphrase) {
    gpg.passphrase = g_strdup(passphrase);
    mlock(gpg.passphrase, strlen(gpg.passphrase));
  } else {
    gpg.passphrase = NULL;
  }
}

//  gpg_set_private_key(keyid)
// Set the current private key id (use NULL to unset it).
void gpg_set_private_key(const char *priv_keyid)
{
  g_free(gpg.private_key);
  if (priv_keyid)
    gpg.private_key = g_strdup(priv_keyid);
  else
    gpg.private_key = NULL;
}

//  strip_header_footer(data)
// Remove PGP header & footer from data.
// Return a new string, or NULL.
// The string must be freed by the caller with g_free() when no longer needed.
static char *strip_header_footer(const char *data)
{
  char *p, *q;

  if (!data)
    return NULL;

  // p: beginning of real data
  // q: end of real data

  // Strip header (to the first empty line)
  p = strstr(data, "\n\n");
  if (!p)
    return g_strdup(data);

  // Strip footer
  // We want to remove the last lines, until the line beginning with a '-'
  p += 2;
  for (q = p ; *q; q++) ;
  // (q is at the end of data now)
  for (q--; q > p && (*q != '\n' || *(q+1) != '-'); q--) ;

  if (q <= p)
    return NULL; // Shouldn't happen...

  return g_strndup(p, q-p);
}

// GCC ignores casts to void, thus we need to hack around that
static inline void ignore(void*x) {}

//  passphrase_cb()
// GPGME passphrase callback function.
static gpgme_error_t passphrase_cb(void *hook, const char *uid_hint,
                       const char *passphrase_info, int prev_was_bad, int fd)
{
  ssize_t len;

  // Abort if we do not have the password.
  if (!gpg.passphrase) {
    ignore((void*)write(fd, "\n", 1)); // We have an error anyway, thus it does
                                       // not matter if we fail again.
    return gpg_error(GPG_ERR_CANCELED);
  }

  // Write the passphrase to the file descriptor.
  len = strlen(gpg.passphrase);
  if (write(fd, gpg.passphrase, len) != len)
    return gpg_error(GPG_ERR_CANCELED);
  if (write(fd, "\n", 1) != 1)
    return gpg_error(GPG_ERR_CANCELED);

  return 0; // Success
}

//  gpg_verify(gpg_data, text, *sigsum)
// Verify that gpg_data is a correct signature for text.
// Return the key id (or fingerprint), and set *sigsum to
// the gpgme signature summary value.
// The returned string must be freed with g_free() after use.
char *gpg_verify(const char *gpg_data, const char *text,
                 gpgme_sigsum_t *sigsum)
{
  gpgme_ctx_t ctx;
  gpgme_data_t data_sign, data_text;
  char *data;
  char *verified_key = NULL;
  gpgme_key_t key;
  gpgme_error_t err;
  const char prefix[] = "-----BEGIN PGP SIGNATURE-----\n\n";
  const char suffix[] = "\n-----END PGP SIGNATURE-----\n";

  // Reset the summary.
  *sigsum = 0;

  if (!gpg.enabled)
    return NULL;

  err = gpgme_new(&ctx);
  if (err) {
    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
                 "GPGME error: %s", gpgme_strerror(err));
    return NULL;
  }

  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);

  // Surround the given data with the prefix & suffix
  data = g_new(char, sizeof(prefix) + sizeof(suffix) + strlen(gpg_data));
  strcpy(data, prefix);
  strcat(data, gpg_data);
  strcat(data, suffix);

  err = gpgme_data_new_from_mem(&data_sign, data, strlen(data), 0);
  if (!err) {
    err = gpgme_data_new_from_mem(&data_text, text, strlen(text), 0);
    if (!err) {
      err = gpgme_op_verify(ctx, data_sign, data_text, 0);
      if (!err) {
        gpgme_verify_result_t vr = gpgme_op_verify_result(ctx);
        if (vr && vr->signatures) {
          char *r = vr->signatures->fpr;
          // Found the fingerprint.  Let's try to get the key id.
          if (!gpgme_get_key(ctx, r, &key, 0) && key) {
            r = key->subkeys->keyid;
            gpgme_key_release(key);
          }
          // r is a static variable, let's copy it.
          verified_key = g_strdup(r);
          *sigsum = vr->signatures->summary;
          // For some reason summary could be 0 when status is 0 too,
          // which means the signature is valid...
          if (!*sigsum && !vr->signatures->status)
            *sigsum = GPGME_SIGSUM_GREEN;
        }
      }
      gpgme_data_release(data_text);
    }
    gpgme_data_release(data_sign);
  }
  if (err)
    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
                 "GPGME verification error: %s", gpgme_strerror(err));
  gpgme_release(ctx);
  g_free(data);
  return verified_key;
}

//  gpg_sign(gpg_data)
// Return a signature of gpg_data (or NULL).
// The returned string must be freed with g_free() after use.
char *gpg_sign(const char *gpg_data)
{
  gpgme_ctx_t ctx;
  gpgme_data_t in, out;
  char *p;
  char *signed_data = NULL;
  size_t nread;
  gpgme_key_t key;
  gpgme_error_t err;

  if (!gpg.enabled || !gpg.private_key)
    return NULL;

  err = gpgme_new(&ctx);
  if (err) {
    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
                 "GPGME error: %s", gpgme_strerror(err));
    return NULL;
  }

  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
  gpgme_set_textmode(ctx, 0);
  gpgme_set_armor(ctx, 1);

  p = getenv("GPG_AGENT_INFO");
  if (!(p && strchr(p, ':')))
    gpgme_set_passphrase_cb(ctx, passphrase_cb, 0);

  err = gpgme_get_key(ctx, gpg.private_key, &key, 1);
  if (err || !key) {
    scr_LogPrint(LPRINT_LOGNORM, "GPGME error: private key not found");
    gpgme_release(ctx);
    return NULL;
  }

  gpgme_signers_clear(ctx);
  gpgme_signers_add(ctx, key);
  gpgme_key_release(key);
  err = gpgme_data_new_from_mem(&in, gpg_data, strlen(gpg_data), 0);
  if (!err) {
    err = gpgme_data_new(&out);
    if (!err) {
      err = gpgme_op_sign(ctx, in, out, GPGME_SIG_MODE_DETACH);
      if (!err) {
        signed_data = gpgme_data_release_and_get_mem(out, &nread);
        if (signed_data) {
          // We need to add a trailing NULL
          char *dd = g_strndup(signed_data, nread);
          free(signed_data);
          signed_data = strip_header_footer(dd);
          g_free(dd);
        }
      } else {
        gpgme_data_release(out);
      }
    }
    gpgme_data_release(in);
  }
  if (err && err != GPG_ERR_CANCELED)
    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
                 "GPGME signature error: %s", gpgme_strerror(err));
  gpgme_release(ctx);
  return signed_data;
}

//  gpg_decrypt(gpg_data)
// Return decrypted gpg_data (or NULL).
// The returned string must be freed with g_free() after use.
char *gpg_decrypt(const char *gpg_data)
{
  gpgme_ctx_t ctx;
  gpgme_data_t in, out;
  char *p, *data;
  char *decrypted_data = NULL;
  size_t nread;
  gpgme_error_t err;
  const char prefix[] = "-----BEGIN PGP MESSAGE-----\n\n";
  const char suffix[] = "\n-----END PGP MESSAGE-----\n";

  if (!gpg.enabled)
    return NULL;

  err = gpgme_new(&ctx);
  if (err) {
    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
                 "GPGME error: %s", gpgme_strerror(err));
    return NULL;
  }

  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);

  p = getenv("GPG_AGENT_INFO");
  if (!(p && strchr(p, ':')))
    gpgme_set_passphrase_cb(ctx, passphrase_cb, 0);

  // Surround the given data with the prefix & suffix
  data = g_new(char, sizeof(prefix) + sizeof(suffix) + strlen(gpg_data));
  strcpy(data, prefix);
  strcat(data, gpg_data);
  strcat(data, suffix);

  err = gpgme_data_new_from_mem(&in, data, strlen(data), 0);
  if (!err) {
    err = gpgme_data_new(&out);
    if (!err) {
      err = gpgme_op_decrypt(ctx, in, out);
      if (!err) {
        decrypted_data = gpgme_data_release_and_get_mem(out, &nread);
        if (decrypted_data) {
          // We need to add a trailing NULL
          char *dd = g_strndup(decrypted_data, nread);
          free(decrypted_data);
          decrypted_data = dd;
        }
      } else {
        gpgme_data_release(out);
      }
    }
    gpgme_data_release(in);
  }
  if (err && err != GPG_ERR_CANCELED)
    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
                 "GPGME decryption error: %s", gpgme_strerror(err));
  gpgme_release(ctx);
  g_free(data);
  return decrypted_data;
}

//  gpg_encrypt(gpg_data, keyid)
// Return encrypted gpg_data with the key keyid (or NULL).
// The returned string must be freed with g_free() after use.
char *gpg_encrypt(const char *gpg_data, const char *keyid)
{
  gpgme_ctx_t ctx;
  gpgme_data_t in, out;
  char *encrypted_data = NULL, *edata;
  size_t nread;
  gpgme_key_t key;
  gpgme_error_t err;

  if (!gpg.enabled)
    return NULL;

  err = gpgme_new(&ctx);
  if (err) {
    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
                 "GPGME error: %s", gpgme_strerror(err));
    return NULL;
  }

  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
  gpgme_set_textmode(ctx, 0);
  gpgme_set_armor(ctx, 1);

  err = gpgme_get_key(ctx, keyid, &key, 0);
  if (!err && key) {
    gpgme_key_t keys[] = { key, 0 };
    err = gpgme_data_new_from_mem(&in, gpg_data, strlen(gpg_data), 0);
    if (!err) {
      err = gpgme_data_new(&out);
      if (!err) {
        err = gpgme_op_encrypt(ctx, keys, GPGME_ENCRYPT_ALWAYS_TRUST, in, out);
        if (!err)
          encrypted_data = gpgme_data_release_and_get_mem(out, &nread);
        else
          gpgme_data_release(out);
      }
      gpgme_data_release(in);
    }
    gpgme_key_release(key);
  } else {
    scr_LogPrint(LPRINT_LOGNORM, "GPGME encryption error: key not found");
    err = 0;
  }
  if (err && err != GPG_ERR_CANCELED)
    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
                 "GPGME encryption error: %s", gpgme_strerror(err));
  gpgme_release(ctx);
  edata = strip_header_footer(encrypted_data);
  if (encrypted_data)
    free(encrypted_data);
  return edata;
}

//  gpg_test_passphrase()
// Test the current gpg.passphrase with gpg.private_key.
// If the test doesn't succeed, the passphrase is cleared and a non-null
// value is returned.
int gpg_test_passphrase(void)
{
  char *s;

  if (!gpg.private_key)
    return -1; // No private key...

  s = gpg_sign("test");
  if (s) {
    free(s);
    return 0; // Ok, test successful
  }
  // The passphrase is wrong (if provided)
  gpg_set_passphrase(NULL);
  return -1;
}

int gpg_enabled(void)
{
  return gpg.enabled;
}

#else  /* not HAVE_GPGME */

int gpg_enabled(void)
{
  return 0;
}

#endif /* HAVE_GPGME */

/* vim: set et cindent cinoptions=>2\:2(0 ts=2 sw=2:  For Vim users... */