You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kmymoney/libkgpgfile/kgpgfile.cpp

696 lines
17 KiB

/***************************************************************************
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" {
KDE_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"