/*************************************************************************** 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 // ---------------------------------------------------------------------------- // TQt Includes #include #include #include #include // ---------------------------------------------------------------------------- // TDE 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" { 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::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 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 ::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) { // 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 ::: 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) { // 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"