/*
    kpgp.cpp

    Copyright (C) 2001,2002 the KPGP authors
    See file AUTHORS.kpgp for details

    This file is part of KPGP, the KDE PGP/GnuPG support library.

    KPGP 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.

    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 <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <assert.h>
#include <stdarg.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <signal.h>

#include <qlabel.h>

#include <kdebug.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <ksimpleconfig.h>
#include <kstaticdeleter.h>

#include "kpgpbase.h"
#include "kpgpui.h"
#include "kpgp.h"

namespace Kpgp {

Module *Module::kpgpObject = 0L;
static KStaticDeleter<Module> kpgpod;

Module::Module()
  : publicKeys(),
    passphrase(0), passphrase_buffer_len(0), havePassPhrase(false)
{
  if (!kpgpObject) {
    kdDebug(5100) << "creating new pgp object" << endl;
  }
  kpgpObject=kpgpod.setObject(this);
  pgp = 0;

  config = new KSimpleConfig("kpgprc");

  init();
}

Module::~Module()
{
  if (kpgpObject == this) kpgpObject = kpgpod.setObject(0);
  clear(TRUE);
  delete config;
  delete pgp;
}

// ----------------- public methods -------------------------

void
Module::init()
{
  wipePassPhrase();

  // read kpgp config file entries
  readConfig();

  // read the email address -> OpenPGP key associations
  readAddressKeyDict();

  // do we have a pgp executable
  checkForPGP();

  // create the Base object later when it is
  // needed to avoid the costly check done for
  // the autodetection of PGP 2/6
  //assignPGPBase();
  delete pgp;
  pgp=0;

  needPublicKeys = true;
}


void
Module::readConfig()
{
  storePass = config->readBoolEntry("storePass", false);
  showEncryptionResult = config->readBoolEntry("showEncryptionResult", true);
  mShowKeyApprovalDlg = config->readBoolEntry( "showKeysForApproval", true );
  pgpType = (Module::PGPType) config->readNumEntry("pgpType", tOff);
  flagEncryptToSelf = config->readBoolEntry("encryptToSelf", true);
}

void
Module::writeConfig(bool sync)
{
  config->writeEntry("storePass", storePass);
  config->writeEntry("showEncryptionResult", showEncryptionResult);
  config->writeEntry( "showKeysForApproval", mShowKeyApprovalDlg );
  config->writeEntry("pgpType", (int) pgpType);
  config->writeEntry("encryptToSelf", flagEncryptToSelf);

  if(sync)
    config->sync();

  /// ### Why is the pgp object deleted? This is only necessary if the
  ///     PGP type was changed in the config dialog.
  delete pgp;
  pgp = 0;
}


void
Module::setUser(const QCString& keyID)
{
  if (pgpUser != keyID) {
    pgpUser = keyID;
    wipePassPhrase();
  }
}

const QCString
Module::user(void) const
{
  return pgpUser;
}


void
Module::setEncryptToSelf(bool flag)
{
  flagEncryptToSelf = flag;
}

bool
Module::encryptToSelf(void) const
{
  return flagEncryptToSelf;
}


void
Module::setStorePassPhrase(bool flag)
{
  storePass = flag;
}

bool
Module::storePassPhrase(void) const
{
  return storePass;
}

int
Module::prepare( bool needPassPhrase, Block* block )
{
  if (0 == pgp) assignPGPBase();

  if(!havePgp)
  {
    errMsg = i18n("Couldn't find PGP executable.\n"
		       "Please check your PATH is set correctly.");
    return 0;
  }

  if( block && ( block->status() & NO_SEC_KEY ) )
    return 0;

  if(needPassPhrase && !havePassPhrase) {
    QString ID;
    if( block )
      ID = block->requiredUserId();
    PassphraseDialog passdlg(0, i18n("OpenPGP Security Check"), true, ID);
    int n = 0;
    while (this->isBusy()) { n++; this->idle(); }
    int passdlgResult = passdlg.exec();
    for ( int j = 0 ; j < n ; j++ ) this->setBusy();
    if (passdlgResult == QDialog::Accepted) {
      if (!setPassPhrase(passdlg.passphrase())) {
        if (strlen(passdlg.passphrase()) >= 1024)
           errMsg = i18n("Passphrase is too long, it must contain less than 1024 characters.");
        else
           errMsg = i18n("Out of memory.");
	return 0;
      }
    } else {
      wipePassPhrase();
      return -1;
    }
  }
  return 1;
}

void
Module::wipePassPhrase(bool freeMem)
{
  if ( passphrase ) {
    if ( passphrase_buffer_len )
      memset( passphrase, 0x00, passphrase_buffer_len );
    else {
      kdDebug(5100) << "wipePassPhrase: passphrase && !passphrase_buffer_len ???" << endl;
      passphrase = 0;
    }
  }
  if ( freeMem && passphrase ) {
    free( passphrase );
    passphrase = 0;
    passphrase_buffer_len = 0;
  }
  havePassPhrase = false;
}

bool
Module::verify( Block& block )
{
  int retval;

  if (0 == pgp) assignPGPBase();

  // everything ready
  if( !prepare( false, &block ) )
    return false;
  // ok now try to verify the message.
  retval = pgp->verify( block );

  if(retval & ERROR)
  {
    errMsg = pgp->lastErrorMessage();
    return false;
  }
  return true;
}

bool
Module::decrypt( Block& block )
{
  int retval;

  if (0 == pgp) assignPGPBase();

  do {
    // everything ready
    if( prepare( true, &block ) != 1 )
      return FALSE;
    // ok now try to decrypt the message.
    retval = pgp->decrypt( block, passphrase );
    // loop on bad passphrase
    if( retval & BADPHRASE ) {
      wipePassPhrase();
      int n = 0;
      while (this->isBusy()) { n++; this->idle(); }
      int ret = KMessageBox::warningContinueCancel(0,
	     i18n("You just entered an invalid passphrase.\n"
		  "Do you want to try again, or "
		  "cancel and view the message undecrypted?"),
	     i18n("PGP Warning"), i18n("&Retry"));
      for (int j = 0; j < n; j++) this->setBusy();
      if ( ret == KMessageBox::Cancel ) break;
    } else
      break;
  } while ( true );

  // erase the passphrase if we do not want to keep it
  cleanupPass();

  if(retval & ERROR)
  {
    errMsg = pgp->lastErrorMessage();
    return false;
  }
  return true;
}

bool
Module::clearsign( Block& block,
                   const QCString& keyId, const QCString& charset )
{
  return encrypt( block, QStringList(), keyId, true, charset );
}

bool
Module::encrypt( Block& block,
                 const QStringList& receivers, const QCString& keyId,
                 bool sign, const QCString& charset )
{
  QStrList recipientKeyIDs; // list of recipients' key IDs
  bool showKeysForApproval = false;
  int status = 0;
  errMsg = "";

  if (0 == pgp) assignPGPBase();

  setUser( keyId );

  if(!receivers.empty())
  {
    for (QStringList::ConstIterator it = receivers.begin();
         it != receivers.end(); ++it)
    {
      EncryptPref encrPref = readEncryptionPreference( *it );
      if( ( encrPref == UnknownEncryptPref ) || ( encrPref == NeverEncrypt ) )
        showKeysForApproval = true;

      Key *aPgpKey = getPublicKey((*it));
      if( aPgpKey )
      {
        recipientKeyIDs.append( aPgpKey->primaryKeyID() );
      }
      else
      {
        recipientKeyIDs.append( "" );// append dummy keyID so that receivers[i]
                                     // corresponds to recipientKeys[i] (this
                                     // is needed for the key approval dialog)
        showKeysForApproval = true;
      }
    }

    if( showKeysForApproval || mShowKeyApprovalDlg )
    { // show the receivers <-> key relation
      KeyApprovalDialog dlg( receivers, recipientKeyIDs );

      int n = 0;
      while (this->isBusy()) { n++; this->idle(); }
      int ret = dlg.exec();
      for (int j = 0; j < n; j++) this->setBusy();
      if( ret == QDialog::Rejected )
        return false;

      recipientKeyIDs = dlg.keys();
    }

    // remove empty key IDs from the list of the recipients' key IDs
    while( recipientKeyIDs.remove( "" ) );

    // show a warning if the user didn't select an encryption key for
    // some of the recipients
    if( recipientKeyIDs.isEmpty() )
    {
      QString str = ( receivers.count() == 1 )
                    ? i18n("You didn't select an encryption key for the "
                           "recipient of this message. Therefore the message "
                           "will not be encrypted.")
                    : i18n("You didn't select an encryption key for any of the "
                           "recipients of this message. Therefore the message "
                           "will not be encrypted.");
      int n = 0;
      while (this->isBusy()) { n++; this->idle(); }
      int ret = KMessageBox::warningContinueCancel( 0, str,
                                                    i18n("PGP Warning"),
                                                    i18n("Send &unencrypted") );
      for (int j = 0; j < n; j++) this->setBusy();
      if( ret == KMessageBox::Cancel )
        return false;
    }
    else if( recipientKeyIDs.count() < receivers.count() )
    {
      QString str = ( receivers.count() - recipientKeyIDs.count() == 1 )
                    ? i18n("You didn't select an encryption key for one of "
                           "the recipients. This person will not be able to "
                           "decrypt the message if you encrypt it.")
                    : i18n("You didn't select encryption keys for some of "
                           "the recipients. These persons will not be able to "
                           "decrypt the message if you encrypt it." );
      int n = 0;
      while (this->isBusy()) { n++; this->idle(); }
      int ret = KMessageBox::warningYesNoCancel( 0, str,
                                                 i18n("PGP Warning"),
                                                 i18n("Send &encrypted"),
                                                 i18n("Send &unencrypted") );
      for (int j = 0; j < n; j++) this->setBusy();
      if( ret == KMessageBox::Cancel )
        return false;
      if( ret == KMessageBox::No )
      { // the user selected "Send unencrypted"
        recipientKeyIDs.clear();
      }
    }
  }

  status = doEncSign( block, recipientKeyIDs, sign );

  if( status & CANCEL )
    return false;

  // check for bad passphrase
  while( status & BADPHRASE )
  {
    wipePassPhrase();
    QString str = i18n("You entered an invalid passphrase.\n"
                       "Do you want to try again, continue and leave the "
                       "message unsigned, or cancel sending the message?");
    int n = 0;
    while (this->isBusy()) { n++; this->idle(); }
    int ret = KMessageBox::warningYesNoCancel( 0, str,
                                               i18n("PGP Warning"),
                                               i18n("&Retry"),
                                               i18n("Send &unsigned") );
    for (int j = 0; j < n; j++) this->setBusy();
    if( ret == KMessageBox::Cancel )
      return false;
    if( ret == KMessageBox::No )
    { // the user selected "Send unsigned"
      if( recipientKeyIDs.isEmpty() )
      {
        block.reset();
        return true;
      }
      else
        sign = false;
    }
    // ok let's try once again...
    status = doEncSign( block, recipientKeyIDs, sign );
  }

  // did signing fail?
  if( status & ERR_SIGNING )
  {
    QString str = i18n("%1 = 'signing failed' error message", 
                       "%1\nDo you want to send the message unsigned, "
                       "or cancel sending the message?")
                  .arg( pgp->lastErrorMessage() );
    int n = 0;
    while (this->isBusy()) { n++; this->idle(); }
    int ret = KMessageBox::warningContinueCancel( 0, str,
                                                  i18n("PGP Warning"),
                                                  i18n("Send &unsigned") );
    for (int j = 0; j < n; j++) this->setBusy();
    if( ret == KMessageBox::Cancel )
      return false;
    sign = false;
    status = doEncSign( block, recipientKeyIDs, sign );
  }

  // check for bad keys
  if( status & BADKEYS )
  {
    QString str = i18n("%1 = 'bad keys' error message", 
                       "%1\nDo you want to encrypt anyway, leave the "
                       "message as is, or cancel sending the message?")
                  .arg( pgp->lastErrorMessage() );
    int n = 0;
    while (this->isBusy()) { n++; this->idle(); }
    int ret = KMessageBox::warningYesNoCancel( 0, str,
                                               i18n("PGP Warning"),
                                               i18n("Send &encrypted"),
                                               i18n("Send &unencrypted") );
    for (int j = 0; j < n; j++) this->setBusy();
    if( ret == KMessageBox::Cancel )
      return false;
    if( ret == KMessageBox::No )
    { // the user selected "Send unencrypted"
      if( sign )
        doEncSign( block, QStrList(), sign );
      else
        block.reset();
      return true;
    }
  }
  if( status & MISSINGKEY )
  {
    QString str = i18n("%1 = 'missing keys' error message", 
                       "%1\nDo you want to leave the message as is, "
                       "or cancel sending the message?")
                  .arg( pgp->lastErrorMessage() );
    int n = 0;
    while (this->isBusy()) { n++; this->idle(); }
    int ret = KMessageBox::warningContinueCancel( 0, str,
                                                  i18n("PGP Warning"),
                                                  i18n("&Send as is") );
    for (int j = 0; j < n; j++) this->setBusy();
    if( ret == KMessageBox::Cancel )
      return false;
    block.reset();
    return true;
  }

  if( status & ERROR )
  {
    // show error dialog
    errMsg = i18n( "The following error occurred:\n%1" )
             .arg( pgp->lastErrorMessage() );
    QString details = i18n( "This is the error message of %1:\n%2" )
                    .arg( ( pgpType == tGPG ) ? "GnuPG" : "PGP" )
                    .arg( block.error().data() );
    int n = 0;
    while (this->isBusy()) { n++; this->idle(); }
    KMessageBox::detailedSorry( 0, errMsg, details );
    for (int j = 0; j < n; j++) this->setBusy();

    return false;
  }

  if( showCipherText() )
  {
    // show cipher text dialog
    CipherTextDialog *cipherTextDlg = new CipherTextDialog( block.text(), charset );

    int n = 0;
    while (this->isBusy()) { n++; this->idle(); }
    bool result = (cipherTextDlg->exec() == QDialog::Accepted);
    for (int j = 0; j < n; j++) this->setBusy();

    delete cipherTextDlg;
    return result;
  }
  return true;
}

int
Module::doEncSign( Block& block,
                   const QStrList& recipientKeyIDs, bool sign )
{
  int retval = 0;

  if (0 == pgp) assignPGPBase();

  // to avoid error messages in case pgp is not installed
  if(!havePgp)
    return OK;

  if(sign)
  {
    int result = prepare( true, &block );
    switch( result )
    {
    case -1:
      return CANCEL;
    case 0:
      return ERROR;
    }
    retval = pgp->encsign( block, recipientKeyIDs, passphrase );
  }
  else
  {
    if(!prepare( false, &block )) return ERROR;
    retval = pgp->encrypt( block, recipientKeyIDs );
  }
  // erase the passphrase if we do not want to keep it
  cleanupPass();

  return retval;
}

int
Module::encryptionPossible( const QStringList& recipients )
{
  if( 0 == pgp ) assignPGPBase();
  
  if( !usePGP() )
    return 0;

  if( recipients.empty() )
    return 0;

  int noKey = 0, never = 0, unknown = 0, always = 0, aip = 0, ask = 0,
      askwp = 0;
  for( QStringList::ConstIterator it = recipients.begin();
       it != recipients.end(); ++it)
  {
    Key *key = havePublicKey( (*it) );
    if( key )
    {
      EncryptPref encrPref = readEncryptionPreference( *it );
      switch( encrPref )
      {
        case NeverEncrypt:
          never++;
          break;
        case UnknownEncryptPref:
          unknown++;
          break;
        case AlwaysEncrypt:
          always++;
          break;
        case AlwaysEncryptIfPossible:
          aip++;
          break;
        case AlwaysAskForEncryption:
          ask++;
          break;
        case AskWheneverPossible:
          askwp++;
          break;
      }
    }      
    else
      noKey++;
  }

  if( ( always+aip > 0 ) && ( never+unknown+ask+askwp+noKey == 0 ) )
    return 1; // encryption possible and desired

  if( ( unknown+ask+askwp > 0 ) && ( never+noKey == 0 ) )
    return 2; // encryption possible, but user should be asked

  if( ( never+noKey > 0 ) && ( always+ask == 0 ) )
    return 0; // encryption isn't possible or desired

  return -1; // we can't decide it automatically
}

bool
Module::signKey(const QCString& keyID)
{
  if (0 == pgp) assignPGPBase();

  if( prepare( true ) != 1 )
    return FALSE;
  if(pgp->signKey(keyID, passphrase) & ERROR)
  {
    errMsg = pgp->lastErrorMessage();
    return false;
  }
  return true;
}


const KeyList*
Module::keys(void)
{
  if (0 == pgp) assignPGPBase();

  if (!prepare()) return NULL;

  readPublicKeys();

  return &publicKeys;
}

Key*
Module::havePublicKey(const QString& person)
{
  if (0 == pgp) assignPGPBase();

  if( !usePGP() ) return 0;
  
  readPublicKeys();

  QString address = canonicalAddress( person ).lower();

  // First look for this person's address in the address->key dictionary
  if ( addressKeyDict.contains( address ) ) {
    QCString keyId = addressKeyDict[address];
    kdDebug(5100) << "Using key 0x" << keyId << " for " << person << endl;

    // Check if we still have the key with this key id and if the key is
    // a trusted and valid encryption key
    keyTrust( keyId ); // this is called to make sure that the trust info
                       // for this key is read
    Key *key = publicKey( keyId );
    if( ( key != 0 ) && ( key->isValidEncryptionKey() ) &&
        ( key->keyTrust() >= KPGP_VALIDITY_MARGINAL ) )
      return key;
    else
      removeKeyForAddress( address );
  }

  // Now search public keys for matching keys
  KeyListIterator it( publicKeys );

  // search a key which matches the complete address
  for ( it.toFirst(); (*it); ++it )
    // search case insensitively in the list of userIDs of this key
    if((*it)->matchesUserID(person, false))
    {
      keyTrust( (*it)->primaryKeyID() ); // this is called to make sure that
                                         // the trust info for this key is read
      if( ( (*it)->isValidEncryptionKey() ) &&
          ( (*it)->keyTrust() >= KPGP_VALIDITY_MARGINAL ) )
        return (*it);
    }

  // if no key matches the complete address look for a key which matches
  // the canonical mail address
  for ( it.toFirst(); (*it); ++it )
    // search case insensitively in the list of userIDs of this key
    if((*it)->matchesUserID(address, false))
    {
      keyTrust( (*it)->primaryKeyID() ); // this is called to make sure that
                                         // the trust info for this key is read
      if( ( (*it)->isValidEncryptionKey() ) &&
          ( (*it)->keyTrust() >= KPGP_VALIDITY_MARGINAL ) )
        return (*it);
    }

  return 0;
}

Key*
Module::getPublicKey(const QString& person)
{
  if (0 == pgp) assignPGPBase();

  // just to avoid some error messages
  if( !usePGP() ) return 0;

  readPublicKeys();

  QString address = canonicalAddress( person ).lower();

  // First look for this person's address in the address->key dictionary
  if ( addressKeyDict.contains( address ) )
  {
    QCString keyId = addressKeyDict[address];
    kdDebug(5100) << "Trying key 0x" << keyId << " for " << person << endl;

    // Check if we still have the key with this key id and if the key is
    // a trusted and valid encryption key
    keyTrust( keyId ); // this is called to make sure that the trust info
                       // for this key is read
    Key *key = publicKey( keyId );
    if( ( key != 0 ) && ( key->isValidEncryptionKey() ) &&
        ( key->keyTrust() >= KPGP_VALIDITY_MARGINAL ) )
      return key;
    else
      removeKeyForAddress( address );
  }

  // Now search all public keys for matching keys
  KeyListIterator it(publicKeys);
  KeyList matchingKeys;

  //for (int tries = 0; tries < 2; ++tries)
  {
    // search all keys which match the complete address
    for (it.toFirst(); (*it); ++it)
      // search case insensitively in the list of userIDs of this key
      if((*it)->matchesUserID(person, false))
      {
        keyTrust( (*it)->primaryKeyID() ); // this is called to make sure that
                                           // the trust info for this key is read
        if( ( (*it)->isValidEncryptionKey() ) &&
            ( (*it)->keyTrust() >= KPGP_VALIDITY_MARGINAL ) )
          matchingKeys.append(*it);
      }

    // if no keys match the complete address look for keys which match
    // the canonical mail address
    if (matchingKeys.isEmpty())
    {
      for (it.toFirst(); (*it); ++it)
        // search case insensitively in the list of userIDs of this key
        if((*it)->matchesUserID(address, false))
        {
          keyTrust( (*it)->primaryKeyID() ); // this is called to make sure that
                                             // the trust info for this key is read
          if( ( (*it)->isValidEncryptionKey() ) &&
              ( (*it)->keyTrust() >= KPGP_VALIDITY_MARGINAL ) )
            matchingKeys.append(*it);
        }
    }

    //if (!matchingKeys.isEmpty())
    //  break;

    // reread the database, because no matching key was found...
    // FIXME: Add a "Re-read keys" option to the key selection dialog
    //if ( tries == 0 )
    //  readPublicKeys( true );
  }

  // no match until now, let the user choose the key
  if (matchingKeys.isEmpty())
  {
    // FIXME: let user set the key/ get from keyserver
    bool rememberChoice;
    QCString keyId = selectKey( rememberChoice, publicKeys,
                                i18n("Encryption Key Selection"),
                                i18n("No valid and trusted OpenPGP key was "
                                     "found for \"%1\".\n\n"
                                     "Select the key which should "
                                     "be used for this recipient."
                                     ).arg(person),
                                QCString(),
                                true );
    if (!keyId.isEmpty())
    {
      if (rememberChoice)
        storeKeyForAddress ( person, keyId );
      return publicKey( keyId );
    }
  }
  // only one key matches
  else if (matchingKeys.count() == 1)
  {
    return matchingKeys.at(0);
  }
  // more than one key matches; let the user choose the key
  else
  {
    bool rememberChoice;
    QCString keyId = selectKey( rememberChoice, matchingKeys,
                                i18n("Encryption Key Selection"),
                                i18n("More than one key match \"%1\".\n\n"
                                     "Select the key which should "
                                     "be used for this recipient."
                                     ).arg(person),
                                matchingKeys.at(0)->primaryKeyID(),
                                true );
    if (!keyId.isEmpty())
    {
      if (rememberChoice)
        storeKeyForAddress ( person, keyId );
      return publicKey( keyId );
    }
  }

  return 0;
}

Key*
Module::publicKey(const QCString& keyID)
{
  readPublicKeys();

  for (KeyListIterator it(publicKeys); (*it); ++it)
    if(keyID == (*it)->primaryKeyID())
      return (*it);

  return 0;
}

Key*
Module::publicKey( const QString& userID )
{
  readPublicKeys();

  for( KeyListIterator it(publicKeys); (*it); ++it )
    if( (*it)->matchesUserID( userID ) )
      return (*it);

  return 0;
}

Validity
Module::keyTrust( const QCString& keyID )
{
  Key *key = publicKey( keyID );

  if( key == 0 )
    return KPGP_VALIDITY_UNKNOWN;
  
  if( key->keyTrust() == KPGP_VALIDITY_UNKNOWN )
  {
    key = rereadKey( keyID, true );
    if( key == 0 )
      return KPGP_VALIDITY_UNKNOWN;
  }

  return key->keyTrust();
}

Validity
Module::keyTrust( const QString& userID )
{
  Key *key = publicKey( userID );

  if( key == 0 )
    return KPGP_VALIDITY_UNKNOWN;
  
  if( key->keyTrust() == KPGP_VALIDITY_UNKNOWN )
  {
    key = rereadKey( key->primaryKeyID(), true );
    if( key == 0 )
      return KPGP_VALIDITY_UNKNOWN;
  }

  return key->keyTrust();
}

bool
Module::isTrusted( const QCString& keyID )
{
  return ( keyTrust( keyID ) >= KPGP_VALIDITY_MARGINAL );
}

Key*
Module::rereadKey( const QCString& keyID, const bool readTrust /* = true */ )
{
  if( 0 == pgp ) assignPGPBase();

  Key* key;

  // search the old key data in the key list
  for( key = publicKeys.first(); key; key = publicKeys.next() )
    if( keyID == key->primaryKeyID() )
      break;

  key = pgp->readPublicKey( keyID, readTrust, key );

  return key;
}

QCString
Module::getAsciiPublicKey(const QCString& keyID)
{
  if (0 == pgp) assignPGPBase();

  return pgp->getAsciiPublicKey(keyID);
}


bool Module::setPassPhrase(const char * aPass)
{
  // null out old buffer before we touch the new string.  So in case
  // aPass isn't properly null-terminated, we don't leak secret data.
  wipePassPhrase();

  if (aPass)
  {
    size_t newlen = strlen( aPass );
    if ( newlen >= 1024 ) {
      // rediculously long passphrase.
      // Maybe someone wants to trick us in malloc()'ing
      // huge buffers...
      return false;
    }
    if ( passphrase_buffer_len < newlen + 1 ) {
      // too little space in current buffer:
      // allocate a larger one.
      if ( passphrase )
	free( passphrase );
      passphrase_buffer_len = (newlen + 1 + 15) & ~0xF; // make it a multiple of 16.
      passphrase = (char*)malloc( passphrase_buffer_len );
      if (!passphrase) {
	passphrase_buffer_len = 0;
	return false;
      }
    }
    memcpy( passphrase, aPass, newlen + 1 );
    havePassPhrase = true;
  }
  return true;
}

bool
Module::changePassPhrase()
{
  //FIXME...
  KMessageBox::information(0,i18n("This feature is\nstill missing"));
  return FALSE;
}

void
Module::clear(const bool erasePassPhrase)
{
  if(erasePassPhrase)
    wipePassPhrase(true);
}

const QString
Module::lastErrorMsg(void) const
{
  return errMsg;
}

bool
Module::havePGP(void) const
{
  return havePgp;
}

void
Module::setShowCipherText(const bool flag)
{
  showEncryptionResult = flag;
}

bool
Module::showCipherText(void) const
{
  return showEncryptionResult;
}

QCString
Module::selectSecretKey( const QString& title,
                         const QString& text,
                         const QCString& keyId )
{
  if (0 == pgp) assignPGPBase();

  if ( usePGP() ) {
    KeyList secretKeys = pgp->secretKeys();
    secretKeys.setAutoDelete(true);
    return selectKey( secretKeys, title, text, keyId );
  }
  else {
    KMessageBox::sorry( 0, i18n("You either don't have GnuPG/PGP installed "
                                "or you chose not to use GnuPG/PGP.") );
    return QCString();
  }
}

QCString
Module::selectPublicKey( const QString& title,
                         const QString& text /* = QString::null */,
                         const QCString& oldKeyId /* = QCString() */,
                         const QString& address /* = QString::null */,
                         const bool encryption /* = false */,
                         const bool signing /* = false */ )
{
  if (0 == pgp) assignPGPBase();

  if ( usePGP() )
  {
    readPublicKeys();

    QCString keyId;

    if( address.isEmpty() )
      keyId = selectKey( *keys(), title, text, oldKeyId, encryption, signing );
    else
    {
      bool rememberChoice;
      keyId = selectKey( rememberChoice, *keys(), title, text, oldKeyId,
                         encryption, signing );
      if( !keyId.isEmpty() && rememberChoice )
        storeKeyForAddress( address, keyId );
    }

    return keyId;
  }
  else {
    KMessageBox::sorry( 0, i18n("You either don't have GnuPG/PGP installed "
                                "or you chose not to use GnuPG/PGP.") );
    return QCString();
  }
}


// -- static member functions ----------------------------------------------

Module *
Module::getKpgp()
{
  if (!kpgpObject)
  {
      kdError(5100) << "there is no instance of kpgp available" << endl;
  }
  return kpgpObject;
}


KSimpleConfig *
Module::getConfig()
{
  return getKpgp()->config;
}


bool
Module::prepareMessageForDecryption( const QCString& msg,
                                     QPtrList<Block>& pgpBlocks,
                                     QStrList& nonPgpBlocks )
{
  BlockType pgpBlock = NoPgpBlock;
  int start = -1;   // start of the current PGP block
  int lastEnd = -1; // end of the last PGP block

  pgpBlocks.setAutoDelete( true );
  pgpBlocks.clear();
  nonPgpBlocks.setAutoDelete( true );
  nonPgpBlocks.clear();

  if( msg.isEmpty() )
  {
    nonPgpBlocks.append( "" );
    return false;
  }

  if( !strncmp( msg.data(), "-----BEGIN PGP ", 15 ) )
    start = 0;
  else
  {
    start = msg.find( "\n-----BEGIN PGP" ) + 1;
    if( start == 0 )
    {
      nonPgpBlocks.append( msg );
      return false; // message doesn't contain an OpenPGP block
    }
  }

  while( start != -1 )
  {
    int nextEnd, nextStart;

    // is the PGP block a clearsigned block?
    if( !strncmp( msg.data() + start + 15, "SIGNED", 6 ) )
      pgpBlock = ClearsignedBlock;
    else
      pgpBlock = UnknownBlock;

    nextEnd = msg.find( "\n-----END PGP", start + 15 );
    if( nextEnd == -1 )
    {
      nonPgpBlocks.append( msg.mid( lastEnd+1 ) );
      break;
    }
    nextStart = msg.find( "\n-----BEGIN PGP", start + 15 );

    if( ( nextStart == -1 ) || ( nextEnd < nextStart ) ||
        ( pgpBlock == ClearsignedBlock ) )
    { // most likely we found a PGP block (but we don't check if it's valid)
      // store the preceding non-PGP block
      nonPgpBlocks.append( msg.mid( lastEnd+1, start-lastEnd-1 ) );
      lastEnd = msg.find( "\n", nextEnd + 14 );
      if( lastEnd == -1 )
      {
        pgpBlocks.append( new Block( msg.mid( start ) ) );
        nonPgpBlocks.append( "" );
        break;
      }
      else
      {
        pgpBlocks.append( new Block( msg.mid( start, lastEnd+1-start ) ) );
        if( ( nextStart != -1 ) && ( nextEnd > nextStart ) )
          nextStart = msg.find( "\n-----BEGIN PGP", lastEnd+1 );
      }
    }

    start = nextStart;
    if( start == -1 )
      nonPgpBlocks.append( msg.mid( lastEnd+1 ) );
    else
      start++; // move start behind the '\n'
  }

  return ( !pgpBlocks.isEmpty() );
}


// --------------------- private functions -------------------

// check if pgp 2.6.x or 5.0 is installed
// kpgp will prefer to user pgp 5.0
bool
Module::checkForPGP(void)
{
  // get path
  QCString path;
  QStrList pSearchPaths;
  int index = 0;
  int lastindex = -1;

  havePgp=FALSE;

  path = getenv("PATH");
  while((index = path.find(":",lastindex+1)) != -1)
  {
    pSearchPaths.append(path.mid(lastindex+1,index-lastindex-1));
    lastindex = index;
  }
  if(lastindex != (int)path.length() - 1)
    pSearchPaths.append( path.mid(lastindex+1,path.length()-lastindex) );

  QStrListIterator it(pSearchPaths);

  haveGpg=FALSE;
  // lets try gpg
  
  for ( it.toFirst() ; it.current() ; ++it )
  {
    path = (*it);
    path += "/gpg";
    if ( !access( path, X_OK ) )
    {
      kdDebug(5100) << "Kpgp: gpg found" << endl;
      havePgp=TRUE;
      haveGpg=TRUE;
      break;
    }
  }

  // search for pgp5.0
  havePGP5=FALSE;
  for ( it.toFirst() ; it.current() ; ++it )
  {
    path = (*it);
    path += "/pgpe";
    if ( !access( path, X_OK ) )
    {
      kdDebug(5100) << "Kpgp: pgp 5 found" << endl;
      havePgp=TRUE;
      havePGP5=TRUE;
      break;
    }
  }

  // lets try pgp2.6.x
  if (!havePgp) {
    for ( it.toFirst() ; it.current() ; ++it )
    {
      path = it.current();
      path += "/pgp";
      if ( !access( path, X_OK ) )
      {
	kdDebug(5100) << "Kpgp: pgp 2 or 6 found" << endl;
	havePgp=TRUE;
	break;
      }
    }
  }

  if (!havePgp)
  {
    kdDebug(5100) << "Kpgp: no pgp found" << endl;
  }

  return havePgp;
}

void
Module::assignPGPBase(void)
{
  if (pgp)
    delete pgp;

  if(havePgp)
  {
    switch (pgpType)
    {
      case tGPG:
        kdDebug(5100) << "Kpgp: assign pgp - gpg" << endl;
        pgp = new BaseG();
        break;

      case tPGP2:
        kdDebug(5100) << "Kpgp: assign pgp - pgp 2" << endl;
        pgp = new Base2();
        break;

      case tPGP5:
        kdDebug(5100) << "Kpgp: assign pgp - pgp 5" << endl;
        pgp = new Base5();
        break;

      case tPGP6:
        kdDebug(5100) << "Kpgp: assign pgp - pgp 6" << endl;
        pgp = new Base6();
        break;

      case tOff:
        // dummy handler
        kdDebug(5100) << "Kpgp: pgpBase is dummy " << endl;
        pgp = new Base();
        break;

      case tAuto:
        kdDebug(5100) << "Kpgp: assign pgp - auto" << endl;
      default:
        kdDebug(5100) << "Kpgp: assign pgp - default" << endl;
        if(havePGP5)
        {
          kdDebug(5100) << "Kpgp: pgpBase is pgp 5" << endl;
          pgp = new Base5();
          pgpType = tPGP5;
        }
        else if (haveGpg)
        {
          kdDebug(5100) << "Kpgp: pgpBase is gpg " << endl;
          pgp = new BaseG();
          pgpType = tGPG;
        }
        else
        {
          Base6 *pgp_v6 = new Base6();
          if (!pgp_v6->isVersion6())
          {
            kdDebug(5100) << "Kpgp: pgpBase is pgp 2 " << endl;
            delete pgp_v6;
            pgp = new Base2();
            pgpType = tPGP2;
          }
          else
          {
            kdDebug(5100) << "Kpgp: pgpBase is pgp 6 " << endl;
            pgp = pgp_v6;
            pgpType = tPGP6;
          }
        }
    } // switch
  }
  else
  {
    // dummy handler
    kdDebug(5100) << "Kpgp: pgpBase is dummy " << endl;
    pgp = new Base();
    pgpType = tOff;
  }
}

QString
Module::canonicalAddress( const QString& _adress )
{
  int index,index2;

  QString address = _adress.simplifyWhiteSpace();
  address = address.stripWhiteSpace();

  // just leave pure e-mail adress.
  if((index = address.find("<")) != -1)
    if((index2 = address.find("@",index+1)) != -1)
      if((index2 = address.find(">",index2+1)) != -1)
	return address.mid(index,index2-index+1);

  if((index = address.find("@")) == -1)
  {
    // local address
    //char hostname[1024];
    //gethostname(hostname,1024);
    //return "<" + address + "@" + hostname + ">";
    return "<" + address + "@localdomain>";
  }
  else
  {
    int index1 = address.findRev(" ",index);
    int index2 = address.find(" ",index);
    if(index2 == -1) index2 = address.length();
    return "<" + address.mid(index1+1 ,index2-index1-1) + ">";
  }
}

QCString
Module::selectKey( const KeyList& keys, const QString& title, 
                   const QString& text, const QCString& keyId,
                   const bool encryption /* = false */,
                   const bool signing /* = false */ )
{
  QCString retval = QCString();
  KeySelectionDialog *dlg;

  if( encryption )
    dlg = new KeySelectionDialog( keys, title, text, keyId, false, 1 );
  else if( signing )
    dlg = new KeySelectionDialog( keys, title, text, keyId, false, 2 );
  else
    dlg = new KeySelectionDialog( keys, title, text, keyId, false, 0 );

  int n = 0;
  while (this->isBusy()) { n++; this->idle(); }
  bool rej = ( dlg->exec() == QDialog::Rejected );
  for (int j = 0; j < n; j++) this->setBusy();
  if( !rej )
    retval = dlg->key();
  delete dlg;
  
  return retval;
}

QCString
Module::selectKey( bool& rememberChoice,
                   const KeyList& keys, const QString& title,
                   const QString& text, const QCString& keyId,
                   const bool encryption /* = false */,
                   const bool signing /* = false */ )
{
  QCString retval = QCString();
  KeySelectionDialog *dlg;

  if( encryption )
    dlg = new KeySelectionDialog( keys, title, text, keyId, true, 1 );
  else if( signing )
    dlg = new KeySelectionDialog( keys, title, text, keyId, true, 2 );
  else
    dlg = new KeySelectionDialog( keys, title, text, keyId, true, 0 );

  int n = 0;
  while (this->isBusy()) { n++; this->idle(); }
  bool rej = ( dlg->exec() == QDialog::Rejected );
  for (int j = 0; j < n; j++) this->setBusy();
  if( !rej )
  {
    retval = dlg->key();
    rememberChoice = dlg->rememberSelection();
  }
  else
    rememberChoice = false;

  delete dlg;
  
  return retval;
}

void
Module::storeKeyForAddress( const QString& address, const QCString& keyID )
{
  QString addr = canonicalAddress( address ).lower();
  addressKeyDict.insert( addr, keyID );

  writeAddressKeyDict();
}

void
Module::removeKeyForAddress( const QString& address )
{
  addressKeyDict.remove( address );

  writeAddressKeyDict();
}

void
Module::readAddressKeyDict()
{
  QString address, keyID;

  KConfigGroup general( config, "General" );
  int num = general.readNumEntry( "addressKeyEntries", 0 );

  addressKeyDict.clear();
  for ( int i=1; i<=num; i++)
  {
    KConfigGroup addrKeyGroup( config, QString("AddressKeyEntry %1").arg(i).local8Bit() );
    address = addrKeyGroup.readEntry( "Address" );
    keyID = addrKeyGroup.readEntry( "Key ID" );
    if ( !address.isEmpty() && !keyID.isEmpty() )
      addressKeyDict.insert( address, keyID.local8Bit() );
  }
}

void
Module::writeAddressKeyDict()
{
  KConfigGroup general( config, "General" );
  general.writeEntry( "addressKeyEntries", addressKeyDict.count() );
  
  int i;
  AddressKeyDict::Iterator it;
  for ( i=1, it = addressKeyDict.begin();
        it != addressKeyDict.end();
        ++it, i++ )
  {
    KConfigGroup addrKeyGroup( config, QString("AddressKeyEntry %1").arg(i).local8Bit() );
    addrKeyGroup.writeEntry( "Address", it.key() );
    addrKeyGroup.writeEntry( "Key ID", QString(it.data()) );
  }

  config->sync();
}

void
Module::readPublicKeys( const bool force )
{
  if( 0 == pgp ) assignPGPBase();
  
  if( !usePGP() )
  {
    publicKeys.clear();
    needPublicKeys = true;
    return;
  }

  if( needPublicKeys || force )
  {
    publicKeys = pgp->publicKeys();
    publicKeys.setAutoDelete( true );
    needPublicKeys = false;
  }
}

EncryptPref
Module::readEncryptionPreference( const QString& address )
{
  KConfigGroup encrPrefs( config, "EncryptionPreferences" );
  return (EncryptPref) encrPrefs.readNumEntry(
                                           canonicalAddress( address ).lower(),
                                           UnknownEncryptPref );
}

void
Module::writeEncryptionPreference( const QString& address,
                                   const EncryptPref pref )
{
  if( pref != UnknownEncryptPref )
  {
    KConfigGroup encrPrefs( config, "EncryptionPreferences" );
    encrPrefs.writeEntry( canonicalAddress( address ).lower(), (int) pref );
    config->sync();
  }
}

}; // namespace Kpgp
