/***************************************************************************
                             kgpgfile.cpp
                             -------------------
    begin                : Fri Jan 23 2004
    copyright            : (C) 2004,2005 by Thomas Baumgart
    email                : thb@net-bembel.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#ifdef HAVE_CONFIG
#include <config.h>
#endif

// ----------------------------------------------------------------------------
// TQt Includes

#include <tqfile.h>
#include <tqdir.h>
#include <tqstring.h>

#include <tqeventloop.h>


// ----------------------------------------------------------------------------
// TDE Includes

#include <tdeapplication.h>
#include <tdelocale.h>
#include <kprocess.h>
#include <kpassdlg.h>
#include <klibloader.h>

// ----------------------------------------------------------------------------
// Project Includes

#include "kgpgfile.h"

#if 0
class KGPGFileFactory : public KLibFactory
{
public:
    KGPGFileFactory() : KLibFactory() {}
    ~KGPGFileFactory(){}
    TQObject *createObject( TQObject *, const char *, const char*, const TQStringList & )
    {
        return new KGPGFile;
    }
};

extern "C" {
    TDE_EXPORT void *init_libkgpgfile()
    {
        return new KGPGFileFactory;
    }
}
#endif

KGPGFile::KGPGFile(const TQString& fn, const TQString& homedir, const TQString& options) :
  m_options(options),
  m_homedir(homedir),
  m_readRemain(0),
  m_needExitLoop(false)
{
  setName(fn);
  m_exitStatus = -2;
  m_comment = "created by KGPGFile";
  // tqDebug("ungetchbuffer %d", m_ungetchBuffer.length());
}

KGPGFile::~KGPGFile()
{
  close();
}

void KGPGFile::init(void)
{
  setFlags(IO_Sequential);
  setStatus(IO_Ok);
  setState(0);
}

void KGPGFile::setName(const TQString& fn)
{
  m_fn = fn;
  if(fn[0] == '~') {
    m_fn = TQDir::homeDirPath()+fn.mid(1);

  } else if(TQDir::isRelativePath(m_fn)) {
    TQDir dir(fn);
    m_fn = dir.absPath();
  }
  // tqDebug("setName: '%s'", m_fn.data());
}

void KGPGFile::flush(void)
{
  // no functionality
}

void KGPGFile::addRecipient(const TQCString& recipient)
{
  m_recipient << recipient;
}

bool KGPGFile::open(int mode)
{
  return open(mode, TQString(), false);
}

bool KGPGFile::open(int mode, const TQString& cmdArgs, bool skipPasswd)
{
  bool useOwnPassphrase = (getenv("GPG_AGENT_INFO") == 0);

  // tqDebug("KGPGFile::open(%d)", mode);
  m_errmsg.resize(1);
  if(isOpen()) {
    // tqDebug("File already open");
    return false;
  }

  // tqDebug("check filename empty");
  if(m_fn.isEmpty())
    return false;

  // tqDebug("setup file structures");
  init();
  setMode(mode);

  // tqDebug("check valid access mode");
  if(!(isReadable() || isWritable()))
    return false;

  if(isWritable()) {
    // tqDebug("check recipient count");
    if(m_recipient.count() == 0)
      return false;
    // tqDebug("check access rights");
    if(!checkAccess(m_fn, W_OK))
      return false;
  }

  TQStringList args;
  if(cmdArgs.isEmpty()) {
    args << "--homedir" << TQString("\"%1\"").arg(m_homedir)
        << "-q"
        << "--batch";

    if(isWritable()) {
      args << "-ea"
          << "-z" << "6"
          << "--comment" << TQString("\"%1\"").arg(m_comment)
          << "--trust-model=always"
          << "-o" << TQString("\"%1\"").arg(m_fn);
      TQValueList<TQCString>::Iterator it;
      for(it = m_recipient.begin(); it != m_recipient.end(); ++it)
        args << "-r" << TQString("\"%1\"").arg(TQString(*it));

      // some versions of GPG had trouble to replace a file
      // so we delete it first
      TQFile::remove(m_fn);
    } else {
      args << "-da";
      if(useOwnPassphrase)
        args << "--passphrase-fd" << "0";
      else
        args << "--use-agent";
      args << "--no-default-recipient" << TQString("\"%1\"").arg(m_fn);
    }
  } else {
    args = TQStringList::split(" ", cmdArgs);
  }

  TQString pwd;
  if(isReadable() && useOwnPassphrase && !skipPasswd) {
    KPasswordDialog dlg(KPasswordDialog::Password,false,0);
    dlg.setPrompt(i18n("Enter passphrase"));
    dlg.addLine(i18n("File"), m_fn);
    dlg.adjustSize();
    if (dlg.exec() == TQDialog::Rejected)
      return false;
    pwd = dlg.password();
  }

  // tqDebug("starting GPG process");
  if(!startProcess(args))
    return false;

  // tqDebug("check GPG process running");
  if(!m_process) {
    // if the process is not present anymore, we have to check
    // if it was a read operation and we might already have data
    // and the process finished normally. In that case, we
    // just continue.
    if(isReadable()) {
      if(m_ungetchBuffer.isEmpty())
        return false;
    } else
      return false;
  }

  if(isReadable() && useOwnPassphrase && !skipPasswd) {
  	TQCString pwd2 = pwd.local8Bit();  // Local 8 bit length can be different from TQString length
    // tqDebug("Passphrase is '%s'", pwd2.data());
    if(_writeBlock(pwd2.data(), pwd2.length()) == -1) {
      // tqDebug("Sending passphrase failed");
      return false;
    }
    m_process->closeStdin();
  }

  setState( IO_Open );
  at( 0 );
  // tqDebug("File open");
  return true;
}

bool KGPGFile::startProcess(const TQStringList& args)
{
  // now start the TDEProcess with GPG
  m_process = new KShellProcess();
  *m_process << "gpg";
  *m_process << args;

  // TQString arglist = args.join(":");
  // tqDebug("gpg '%s'", arglist.data());

  connect(m_process, TQ_SIGNAL(processExited(TDEProcess *)),
          this, TQ_SLOT(slotGPGExited(TDEProcess *)));

  connect(m_process, TQ_SIGNAL(receivedStdout(TDEProcess*, char*, int)),
          this, TQ_SLOT(slotDataFromGPG(TDEProcess*, char*, int)));

  connect(m_process, TQ_SIGNAL(receivedStderr(TDEProcess*, char*, int)),
          this, TQ_SLOT(slotErrorFromGPG(TDEProcess*, char*, int)));

  connect(m_process, TQ_SIGNAL(wroteStdin(TDEProcess *)),
          this, TQ_SLOT(slotSendDataToGPG(TDEProcess *)));

  if(!m_process->start(TDEProcess::NotifyOnExit, (TDEProcess::Communication)(TDEProcess::Stdin|TDEProcess::Stdout|TDEProcess::Stderr))) {
    // tqDebug("m_process->start failed");
    delete m_process;
    m_process = 0;
    return false;
  }

  // let the process settle and see if it starts and survives ;-)
  kapp->processEvents(100);
  return true;
}

void KGPGFile::close(void)
{
  // tqDebug("KGPGFile::close()");
  if(!isOpen()) {
    // tqDebug("File not open");
    return;
  }

  // finish the TDEProcess and clean up things
  if(m_process) {
    if(isWritable()) {
      // tqDebug("Finish writing");
      if(m_process->isRunning()) {
        m_process->closeStdin();
        // now wait for GPG to finish
        m_needExitLoop = true;
        tqApp->enter_loop();
      } else
        m_process->kill();

    } else if(isReadable()) {
      // tqDebug("Finish reading");
      if(m_process->isRunning()) {
        m_process->closeStdout();
        // now wait for GPG to finish
        m_needExitLoop = true;
        tqApp->enter_loop();
      } else
        m_process->kill();
    }
  }
  m_ungetchBuffer = TQCString();
  setState(0);
  m_recipient.clear();
  // tqDebug("File closed");
}

int KGPGFile::getch(void)
{
  if(!isOpen())
    return EOF;
  if(!isReadable())
    return EOF;

  int ch;

  if(!m_ungetchBuffer.isEmpty()) {
    ch = (m_ungetchBuffer)[0] & 0xff;
    m_ungetchBuffer.remove(0, 1);

  } else {
    char buf[1];
    ch = (readBlock(buf,1) == 1) ? (buf[0] & 0xff) : EOF;
  }

  // tqDebug("getch returns 0x%02X", ch);
  return ch;
}

int KGPGFile::ungetch(int ch)
{
  if(!isOpen())
    return EOF;
  if(!isReadable())
    return EOF;

  if(ch != EOF) {
    // tqDebug("store 0x%02X in ungetchbuffer", ch & 0xff);
    m_ungetchBuffer.insert(0, ch & 0xff);
  }

  return ch;
}

int KGPGFile::putch(int c)
{
  char  buf[1];
  buf[0] = c;
  if(writeBlock(buf, 1) != EOF)
    return c;
  return EOF;
}

TQ_LONG KGPGFile::writeBlock(const char *data, TQ_ULONG maxlen)
{
  if(!isOpen())
    return EOF;
  if(!isWritable())
    return EOF;

  return _writeBlock(data, maxlen);
}

TQ_LONG KGPGFile::_writeBlock(const char *data, TQ_ULONG maxlen)
{
  if(!m_process)
    return EOF;
  if(!m_process->isRunning())
    return EOF;

  if(m_process->writeStdin(data, maxlen)) {
    // wait until the data has been written
    m_needExitLoop = true;
    tqApp->enter_loop();
    if(!m_process)
      return EOF;
    return maxlen;

  } else
    return EOF;
}

TQ_LONG KGPGFile::readBlock(char *data, TQ_ULONG maxlen)
{
  // char *oridata = data;
  if(maxlen == 0)
    return 0;

  if(!isOpen())
    return EOF;
  if(!isReadable())
    return EOF;

  TQ_ULONG nread = 0;
  if(!m_ungetchBuffer.isEmpty()) {
    unsigned l = m_ungetchBuffer.length();
    if(maxlen < l)
      l = maxlen;
    memcpy(data, m_ungetchBuffer, l);
    nread += l;
    data = &data[l];
    m_ungetchBuffer.remove(0, l);

    if(!m_process) {
      // tqDebug("read %d bytes from unget buffer", nread);
      // dumpBuffer(oridata, nread);
      return nread;
    }
  }

  // check for EOF
  if(!m_process) {
    // tqDebug("EOF (no process)");
    return EOF;
  }

  m_readRemain = maxlen - nread;
  m_ptrRemain = data;
  if(m_readRemain) {
    m_process->resume();
    m_needExitLoop = true;
    tqApp->enter_loop();
  }
  // if nothing has been read (maxlen-m_readRemain == 0) then we assume EOF
  if((maxlen - m_readRemain) == 0) {
    // tqDebug("EOF (nothing read)");
    return EOF;
  }
  // tqDebug("return %d bytes", maxlen - m_readRemain);
  // dumpBuffer(oridata, maxlen - m_readRemain);
  return maxlen - m_readRemain;
}

TQByteArray KGPGFile::readAll(void)
{
  // use a larger blocksize than in the TQIODevice version
  const int blocksize = 8192;
  int nread = 0;
  TQByteArray ba;
  while ( !atEnd() ) {
    ba.resize( nread + blocksize );
    int r = readBlock( ba.data()+nread, blocksize );
    if ( r < 0 )
      return TQByteArray();
    nread += r;
  }
  ba.resize( nread );
  return ba;
}

void KGPGFile::slotGPGExited(TDEProcess* )
{
  // tqDebug("GPG finished");
  if(m_process) {
    if(m_process->normalExit()) {
      m_exitStatus = m_process->exitStatus();
      if(m_exitStatus != 0)
        setStatus(IO_UnspecifiedError);
    } else {
      m_exitStatus = -1;
    }
    delete m_process;
    m_process = 0;
  }

  if(m_needExitLoop) {
    m_needExitLoop = false;
    tqApp->exit_loop();
  }
}

void KGPGFile::slotDataFromGPG(TDEProcess* proc, char* buf, int len)
{
  // tqDebug("Received %d bytes on stdout", len);

  // copy current buffer to application
  int copylen;
  copylen = m_readRemain < len ? m_readRemain : len;
  if(copylen != 0) {
    memcpy(m_ptrRemain, buf, copylen);
    m_ptrRemain += copylen;
    buf += copylen;
    m_readRemain -= copylen;
    len -= copylen;
  }

  // store rest of buffer in ungetch buffer
  while(len--) {
    m_ungetchBuffer += *buf++;
  }

  // if we have all the data the app requested, we can safely suspend
  if(m_readRemain == 0) {
    proc->suspend();
    // wake up the recipient
    if(m_needExitLoop) {
      m_needExitLoop = false;
      tqApp->exit_loop();
    }
  }
  // tqDebug("end slotDataFromGPG");
}

void KGPGFile::slotErrorFromGPG(TDEProcess *, char *buf, int len)
{
  // tqDebug("Received %d bytes on stderr", len);
  TQCString msg;
  msg.setRawData(buf, len);
  m_errmsg += msg;
  msg.resetRawData(buf, len);
}

void KGPGFile::slotSendDataToGPG(TDEProcess *)
{
  // tqDebug("wrote stdin");
  if(m_needExitLoop) {
    m_needExitLoop = false;
    tqApp->exit_loop();
  }
}

bool KGPGFile::GPGAvailable(void)
{
  TQString output;
  char  buffer[1024];
  TQ_LONG len;

  KGPGFile file;
  file.open(IO_ReadOnly, "--version", true);
  while((len = file.readBlock(buffer, sizeof(buffer)-1)) != EOF) {
    buffer[len] = 0;
    output += TQString(buffer);
  }
  file.close();
  return !output.isEmpty();
}

bool KGPGFile::keyAvailable(const TQString& name)
{
  TQStringList list;
  publicKeyList(list, name);
  return !list.isEmpty();
}

void KGPGFile::publicKeyList(TQStringList& list, const TQString& pattern)
{
  TQMap<TQString, TQString> map;
  TQString output;
  char  buffer[1024];
  TQ_LONG len;

  list.clear();
  KGPGFile file;
  TQString args("--list-keys --with-colons");
  if(!pattern.isEmpty())
    args += TQString(" %1").arg(pattern);
  file.open(IO_ReadOnly, args, true);
  while((len = file.readBlock(buffer, sizeof(buffer)-1)) != EOF) {
    buffer[len] = 0;
    output += TQString(buffer);
  }
  file.close();

  // now parse the data. it looks like:
  /*
    tru::0:1210616414:1214841688:3:1:5
    pub:u:1024:17:9C59DB40B75DD3BA:2001-06-23:::u:Thomas Baumgart <thomas.baumgart@syrocon.de>::scaESCA:
    uid:u::::2001-11-29::63493BF182C494227E198FE5DA00ACDF63961AFB::Thomas Baumgart <thb@net-bembel.de>:
    uid:u::::2001-11-29::00A393737BC120C98A6402B921599F6D72058DD8::Thomas Baumgart <ipwizard@users.sourceforge.net>:
    sub:u:1024:16:85968A70D1F83C2B:2001-06-23::::::e:
  */
  TQStringList lines = TQStringList::split("\n", output);
  TQStringList::iterator it;
  TQString currentKey;
  for(it = lines.begin(); it != lines.end(); ++it) {
    // tqDebug("Parsing: '%s'", (*it).data());
    TQStringList fields = TQStringList::split(":", (*it), true);
    TQString val;
    if(fields[0] == "pub") {
      TQDate expiration = TQDate::fromString(fields[6], TQt::ISODate);
      if(expiration >  TQDate::currentDate()) {
        currentKey = fields[4];
        val = TQString("%1:%2").arg(currentKey).arg(fields[9]);
        map[val] = val;
      } else {
        tqDebug(TQString("'%1' is expired").arg(fields[9]));
      }
    } else if(fields[0] == "uid") {
      val = TQString("%1:%2").arg(currentKey).arg(fields[9]);
      map[val] = val;
    }
  }
  list = map.values();
}


void KGPGFile::secretKeyList(TQStringList& list)
{
  TQString output;
  char  buffer[1024];
  TQ_LONG len;

  list.clear();
  KGPGFile file;
  file.open(IO_ReadOnly, "--list-secret-keys --with-colons", true);
  while((len = file.readBlock(buffer, sizeof(buffer)-1)) != EOF) {
    buffer[len] = 0;
    output += TQString(buffer);
  }
  file.close();

  // now parse the data. it looks like:
  /*
    sec::1024:17:9C59DB40B75DD3BA:2001-06-23::::Thomas Baumgart <ipwizard@users.sourceforge.net>:::
    uid:::::::::Thomas Baumgart <thb@net-bembel.de>:
    ssb::1024:16:85968A70D1F83C2B:2001-06-23:::::::
    sec::1024:17:59B0F826D2B08440:2005-01-03:2010-01-02:::KMyMoney emergency data recovery <kmymoney-recover@users.sourceforge.net>:::
    ssb::2048:16:B3DABDC48C0FE2F3:2005-01-03:::::::
  */
  TQStringList lines = TQStringList::split("\n", output);
  TQStringList::iterator it;
  TQString currentKey;
  for(it = lines.begin(); it != lines.end(); ++it) {
    // tqDebug("Parsing: '%s'", (*it).data());
    TQStringList fields = TQStringList::split(":", (*it), true);
    if(fields[0] == "sec") {
      currentKey = fields[4];
      list << TQString("%1:%2").arg(currentKey).arg(fields[9]);
    } else if(fields[0] == "uid") {
      list << TQString("%1:%2").arg(currentKey).arg(fields[9]);
    }
  }
}

/*
// key generation
  char * gpg_input =
    g_strdup_printf("Key-Type: DSA\n"
                    "Key-Length: 1024\n"
                    "Subkey-Type: ELG-E\n"
                    "Subkey-Length: 1024\n"
                    "Name-Real: %s\n"
                    "Name-Comment: %s\n"
                    "Name-Email: %s\n"
                    "Passphrase: %s\n"
                    "%%commit\n",
                    username ? username : "",
                    idstring ? idstring : "",
                    email ? email : "",
                    passphrase ? passphrase : "");
  char * argv [] =
  { "gpg",
    "--batch",
    "-q",
    "--gen-key",
    "--keyring",
    "~/.gnucash/gnucash.pub",
    "--secret-keyring",
    "~/.gnucash/gnucash.sec",
    NULL
  };

  char * retval = gnc_gpg_transform(gpg_input, strlen(gpg_input), NULL, argv);
  g_free(gpg_input);
  return retval;

 */

#if KMM_DEBUG
void KGPGFile::dumpBuffer(char *s, int len) const
{
  TQString data, tmp, chars;
  unsigned long addr = 0x0;

  while(1) {
    if(addr && !(addr & 0x0f)) {
      tqDebug("%s %s", data.data(), chars.data());
      if(!len)
        break;
    }
    if(!(addr & 0x0f)) {
      data = tmp.sprintf("%08lX", addr);
      chars = TQString();
    }
    if(!(addr & 0x03)) {
      data += " ";
    }
    ++addr;

    if(!len) {
      data += "  ";
      chars += " ";
      continue;
    }

    data += tmp.sprintf("%02X", *s & 0xff);
    if(*s >= ' ' && *s <= '~')
      chars += *s & 0xff;
    else
      chars += '.';
    ++s;
    --len;
  }
}
#endif

#include "kgpgfile.moc"
