/*************************************************************************** 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 #endif #include "kdecompat.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // 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" { 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"; // qDebug("ungetchbuffer %d", m_ungetchBuffer.length()); } KGPGFile::~KGPGFile() { close(); } void KGPGFile::init(void) { setFlags(IO_Sequential); setqStatus(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(); } // qDebug("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); // qDebug("KGPGFile::open(%d)", mode); m_errmsg.resize(1); if(isOpen()) { // qDebug("File already open"); return false; } // qDebug("check filename empty"); if(m_fn.isEmpty()) return false; // qDebug("setup file structures"); init(); setMode(mode); // qDebug("check valid access mode"); if(!(isReadable() || isWritable())) return false; if(isWritable()) { // qDebug("check recipient count"); if(m_recipient.count() == 0) return false; // qDebug("check access rights"); if(!checkAccess(m_fn, W_OK)) return false; } TQStringList args; if(cmdArgs.isEmpty()) { args << "--homedir" << TQString("\"%1\"").tqarg(m_homedir) << "-q" << "--batch"; if(isWritable()) { args << "-ea" << "-z" << "6" << "--comment" << TQString("\"%1\"").tqarg(m_comment) << "--trust-model=always" << "-o" << TQString("\"%1\"").tqarg(m_fn); TQValueList::Iterator it; for(it = m_recipient.begin(); it != m_recipient.end(); ++it) args << "-r" << TQString("\"%1\"").tqarg(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\"").tqarg(m_fn); } } else { args = TQStringList::split(" ", cmdArgs); } TQCString 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 = TQCString(dlg.password()); } // qDebug("starting GPG process"); if(!startProcess(args)) return false; // qDebug("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) { // qDebug("Passphrase is '%s'", pwd.data()); if(_writeBlock(pwd.data(), pwd.length()) == -1) { // qDebug("Sending passphrase failed"); return false; } m_process->closeStdin(); } setState( IO_Open ); tqat( 0 ); // qDebug("File open"); return true; } bool KGPGFile::startProcess(const TQStringList& args) { // now start the KProcess with GPG m_process = new KShellProcess(); *m_process << "gpg"; *m_process << args; // TQString arglist = args.join(":"); // qDebug("gpg '%s'", arglist.data()); connect(m_process, TQT_SIGNAL(processExited(KProcess *)), this, TQT_SLOT(slotGPGExited(KProcess *))); connect(m_process, TQT_SIGNAL(receivedStdout(KProcess*, char*, int)), this, TQT_SLOT(slotDataFromGPG(KProcess*, char*, int))); connect(m_process, TQT_SIGNAL(receivedStderr(KProcess*, char*, int)), this, TQT_SLOT(slotErrorFromGPG(KProcess*, char*, int))); connect(m_process, TQT_SIGNAL(wroteStdin(KProcess *)), this, TQT_SLOT(slotSendDataToGPG(KProcess *))); if(!m_process->start(KProcess::NotifyOnExit, (KProcess::Communication)(KProcess::Stdin|KProcess::Stdout|KProcess::Stderr))) { // qDebug("m_process->start failed"); delete m_process; m_process = 0; return false; } // let the process settle and see if it starts and survives ;-) kapp->tqprocessEvents(100); return true; } void KGPGFile::close(void) { // qDebug("KGPGFile::close()"); if(!isOpen()) { // qDebug("File not open"); return; } // finish the KProcess and clean up things if(m_process) { if(isWritable()) { // qDebug("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()) { // qDebug("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(); // qDebug("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; } // qDebug("getch returns 0x%02X", ch); return ch; } int KGPGFile::ungetch(int ch) { if(!isOpen()) return EOF; if(!isReadable()) return EOF; if(ch != EOF) { // qDebug("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) { // qDebug("read %d bytes from unget buffer", nread); // dumpBuffer(oridata, nread); return nread; } } // check for EOF if(!m_process) { // qDebug("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) { // qDebug("EOF (nothing read)"); return EOF; } // qDebug("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(KProcess* ) { // qDebug("GPG finished"); if(m_process) { if(m_process->normalExit()) { m_exitStatus = m_process->exitStatus(); if(m_exitStatus != 0) setqStatus(IO_UnspecifiedError); } else { m_exitStatus = -1; } delete m_process; m_process = 0; } if(m_needExitLoop) { m_needExitLoop = false; tqApp->exit_loop(); } } void KGPGFile::slotDataFromGPG(KProcess* proc, char* buf, int len) { // qDebug("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(); } } // qDebug("end slotDataFromGPG"); } void KGPGFile::slotErrorFromGPG(KProcess *, char *buf, int len) { // qDebug("Received %d bytes on stderr", len); TQCString msg; msg.setRawData(buf, len); m_errmsg += msg; msg.resetRawData(buf, len); } void KGPGFile::slotSendDataToGPG(KProcess *) { // qDebug("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 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").tqarg(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 ::scaESCA: uid:u::::2001-11-29::63493BF182C494227E198FE5DA00ACDF63961AFB::Thomas Baumgart : uid:u::::2001-11-29::00A393737BC120C98A6402B921599F6D72058DD8::Thomas Baumgart : 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) { // qDebug("Parsing: '%s'", (*it).data()); TQStringList fields = TQStringList::split(":", (*it), true); TQString val; if(fields[0] == "pub") { TQDate expiration = TQDate::fromString(fields[6], Qt::ISODate); if(expiration > TQDate::tqcurrentDate()) { currentKey = fields[4]; val = TQString("%1:%2").tqarg(currentKey).tqarg(fields[9]); map[val] = val; } else { qDebug("'%s' is expired", fields[9].data()); } } else if(fields[0] == "uid") { val = TQString("%1:%2").tqarg(currentKey).tqarg(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 ::: uid:::::::::Thomas Baumgart : ssb::1024:16:85968A70D1F83C2B:2001-06-23::::::: sec::1024:17:59B0F826D2B08440:2005-01-03:2010-01-02:::KMyMoney emergency data recovery ::: 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) { // qDebug("Parsing: '%s'", (*it).data()); TQStringList fields = TQStringList::split(":", (*it), true); if(fields[0] == "sec") { currentKey = fields[4]; list << TQString("%1:%2").tqarg(currentKey).tqarg(fields[9]); } else if(fields[0] == "uid") { list << TQString("%1:%2").tqarg(currentKey).tqarg(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)) { qDebug("%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"