summaryrefslogtreecommitdiffstats
path: root/libkpgp/kpgpbase.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libkpgp/kpgpbase.cpp')
-rw-r--r--libkpgp/kpgpbase.cpp684
1 files changed, 684 insertions, 0 deletions
diff --git a/libkpgp/kpgpbase.cpp b/libkpgp/kpgpbase.cpp
new file mode 100644
index 000000000..39762333d
--- /dev/null
+++ b/libkpgp/kpgpbase.cpp
@@ -0,0 +1,684 @@
+/*
+ kpgpbase.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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <kdebug.h>
+
+#include <config.h>
+
+#include "kpgpbase.h"
+#include "kpgp.h"
+#include "kpgpblock.h"
+
+#include <stdlib.h> /* setenv, unsetenv */
+#include <unistd.h> /* pipe, close, fork, dup2, execl, _exit, write, read */
+#include <sys/poll.h> /* poll, etc. */
+#include <sys/types.h> /* pid_t */
+#include <sys/wait.h> /* waitpid */
+#include <errno.h>
+
+#include <qapplication.h>
+
+
+namespace Kpgp {
+
+Base::Base()
+ : input(), output(), error(), errMsg(), status(OK)
+{
+}
+
+
+Base::~Base()
+{
+}
+
+
+void
+Base::clear()
+{
+ input = QCString();
+ output = QCString();
+ error = QCString();
+ errMsg = QString::null;
+ status = OK;
+}
+
+
+int
+Base::run( const char *cmd, const char *passphrase, bool onlyReadFromPGP )
+{
+ /* the pipe ppass is used for to pass the password to
+ * pgp. passing the password together with the normal input through
+ * stdin doesn't seem to work as expected (at least for pgp5.0)
+ */
+ char str[1025] = "\0";
+ int pin[2], pout[2], perr[2], ppass[2];
+ int len, len2;
+ FILE *pass;
+ pid_t child_pid;
+ int childExitStatus;
+ struct pollfd pollin, pollout, pollerr;
+ int pollstatus;
+
+ if(passphrase)
+ {
+ pipe(ppass);
+
+ pass = fdopen(ppass[1], "w");
+ fwrite(passphrase, sizeof(char), strlen(passphrase), pass);
+ fwrite("\n", sizeof(char), 1, pass);
+ fclose(pass);
+ close(ppass[1]);
+
+ // tell pgp which fd to use for the passphrase
+ QCString tmp;
+ tmp.sprintf("%d",ppass[0]);
+ ::setenv("PGPPASSFD",tmp.data(),1);
+
+ //Uncomment these lines for testing only! Doing so will decrease security!
+ //kdDebug(5100) << "pgp PGPPASSFD = " << tmp << endl;
+ //kdDebug(5100) << "pgp pass = " << passphrase << endl;
+ }
+ else
+ ::unsetenv("PGPPASSFD");
+
+ //Uncomment these lines for testing only! Doing so will decrease security!
+ kdDebug(5100) << "pgp cmd = " << cmd << endl;
+ //kdDebug(5100) << "pgp input = " << QString(input)
+ // << "input length = " << input.length() << endl;
+
+ error = "";
+ output = "";
+
+ pipe(pin);
+ pipe(pout);
+ pipe(perr);
+
+ QApplication::flushX();
+ if(!(child_pid = fork()))
+ {
+ /*We're the child.*/
+ close(pin[1]);
+ dup2(pin[0], 0);
+ close(pin[0]);
+
+ close(pout[0]);
+ dup2(pout[1], 1);
+ close(pout[1]);
+
+ close(perr[0]);
+ dup2(perr[1], 2);
+ close(perr[1]);
+
+ execl("/bin/sh", "sh", "-c", cmd, (void *)0);
+ _exit(127);
+ }
+
+ /*Only get here if we're the parent.*/
+ close(pin[0]);
+ close(pout[1]);
+ close(perr[1]);
+
+ // poll for "There is data to read."
+ pollout.fd = pout[0];
+ pollout.events = POLLIN;
+ pollout.revents = 0; // init with 0, just in case
+ pollerr.fd = perr[0];
+ pollerr.events = POLLIN;
+ pollerr.revents = 0; // init with 0, just in case
+
+ // poll for "Writing now will not block."
+ pollin.fd = pin[1];
+ pollin.events = POLLOUT;
+ pollin.revents = 0; // init with 0, just in case
+
+ if (!onlyReadFromPGP) {
+ if (!input.isEmpty()) {
+ // write to pin[1] one line after the other to prevent dead lock
+ for (unsigned int i=0; i<input.length(); i+=len2) {
+ len2 = 0;
+
+ // check if writing now to pin[1] will not block (5 ms timeout)
+ //kdDebug(5100) << "Polling pin[1]..." << endl;
+ pollstatus = poll(&pollin, 1, 5);
+ if (pollstatus == 1) {
+ //kdDebug(5100) << "Status for polling pin[1]: " << pollin.revents << endl;
+ if (pollin.revents & POLLERR) {
+ kdDebug(5100) << "PGP seems to have hung up" << endl;
+ break;
+ }
+ else if (pollin.revents & POLLOUT) {
+ // search end of next line
+ if ((len2 = input.find('\n', i)) == -1)
+ len2 = input.length()-i;
+ else
+ len2 = len2-i+1;
+
+ //kdDebug(5100) << "Trying to write " << len2 << " bytes to pin[1] ..." << endl;
+ len2 = write(pin[1], input.mid(i,len2).data(), len2);
+ //kdDebug(5100) << "Wrote " << len2 << " bytes to pin[1] ..." << endl;
+ }
+ }
+ else if (!pollstatus) {
+ //kdDebug(5100) << "Timeout while polling pin[1]: "
+ // << pollin.revents << endl;
+ }
+ else if (pollstatus == -1) {
+ kdDebug(5100) << "Error while polling pin[1]: "
+ << pollin.revents << endl;
+ }
+
+ if (pout[0] >= 0) {
+ do {
+ // check if there is data to read from pout[0]
+ //kdDebug(5100) << "Polling pout[0]..." << endl;
+ pollstatus = poll(&pollout, 1, 0);
+ if (pollstatus == 1) {
+ //kdDebug(5100) << "Status for polling pout[0]: " << pollout.revents << endl;
+ if (pollout.revents & POLLIN) {
+ //kdDebug(5100) << "Trying to read " << 1024 << " bytes from pout[0]" << endl;
+ if ((len = read(pout[0],str,1024))>0) {
+ //kdDebug(5100) << "Read " << len << " bytes from pout[0]" << endl;
+ str[len] ='\0';
+ output += str;
+ }
+ else
+ break;
+ }
+ }
+ else if (pollstatus == -1) {
+ kdDebug(5100) << "Error while polling pout[0]: "
+ << pollout.revents << endl;
+ }
+ } while ((pollstatus == 1) && (pollout.revents & POLLIN));
+ }
+
+ if (perr[0] >= 0) {
+ do {
+ // check if there is data to read from perr[0]
+ //kdDebug(5100) << "Polling perr[0]..." << endl;
+ pollstatus = poll(&pollerr, 1, 0);
+ if (pollstatus == 1) {
+ //kdDebug(5100) << "Status for polling perr[0]: " << pollerr.revents << endl;
+ if (pollerr.revents & POLLIN) {
+ //kdDebug(5100) << "Trying to read " << 1024 << " bytes from perr[0]" << endl;
+ if ((len = read(perr[0],str,1024))>0) {
+ //kdDebug(5100) << "Read " << len << " bytes from perr[0]" << endl;
+ str[len] ='\0';
+ error += str;
+ }
+ else
+ break;
+ }
+ }
+ else if (pollstatus == -1) {
+ kdDebug(5100) << "Error while polling perr[0]: "
+ << pollerr.revents << endl;
+ }
+ } while ((pollstatus == 1) && (pollerr.revents & POLLIN));
+ }
+
+ // abort writing to PGP if PGP hung up
+ if ((pollstatus == 1) &&
+ ((pollout.revents & POLLHUP) || (pollerr.revents & POLLHUP))) {
+ kdDebug(5100) << "PGP hung up" << endl;
+ break;
+ }
+ }
+ }
+ else // if input.isEmpty()
+ write(pin[1], "\n", 1);
+ //kdDebug(5100) << "All input was written to pin[1]" << endl;
+ }
+ close(pin[1]);
+
+ pid_t waitpidRetVal;
+
+ do {
+ //kdDebug(5100) << "Checking if PGP is still running..." << endl;
+ childExitStatus = 0;
+ waitpidRetVal = waitpid(child_pid, &childExitStatus, WNOHANG);
+ //kdDebug(5100) << "waitpid returned " << waitpidRetVal << endl;
+ if (pout[0] >= 0) {
+ do {
+ // check if there is data to read from pout[0]
+ //kdDebug(5100) << "Polling pout[0]..." << endl;
+ pollstatus = poll(&pollout, 1, 0);
+ if (pollstatus == 1) {
+ //kdDebug(5100) << "Status for polling pout[0]: " << pollout.revents << endl;
+ if (pollout.revents & POLLIN) {
+ //kdDebug(5100) << "Trying to read " << 1024 << " bytes from pout[0]" << endl;
+ if ((len = read(pout[0],str,1024))>0) {
+ //kdDebug(5100) << "Read " << len << " bytes from pout[0]" << endl;
+ str[len] ='\0';
+ output += str;
+ } else {
+ /*
+ * Apparently, on NetBSD when the child dies, the pipe begins
+ * receiving empty data packets *before* waitpid() has signaled
+ * that the child has died. Also, notice that this happens
+ * without any error bit being set in pollfd.revents (is this a
+ * NetBSD bug??? ). Notice that these anomalous packets exist
+ * according to poll(), but have length 0 according to read().
+ * Thus, kde can remain stuck inside this loop.
+ *
+ * A solution to this problem is to get out of the inner loop
+ * when read() returns <=0. In this way, kde has another chance
+ * to call waitpid() to check if the child has died -- and this
+ * time the call should succeed.
+ *
+ * Setting POLLHUP in pollfd.revents is not necessary, but I just
+ * like the idea of signaling that something strange has
+ * happened.
+ */
+ pollout.revents |= POLLHUP;
+ break;
+ }
+ }
+ }
+ else if (pollstatus == -1) {
+ kdDebug(5100) << "Error while polling pout[0]: "
+ << pollout.revents << endl;
+ }
+ } while ((pollstatus == 1) && (pollout.revents & POLLIN));
+ }
+
+ if (perr[0] >= 0) {
+ do {
+ // check if there is data to read from perr[0]
+ //kdDebug(5100) << "Polling perr[0]..." << endl;
+ pollstatus = poll(&pollerr, 1, 0);
+ if (pollstatus == 1) {
+ //kdDebug(5100) << "Status for polling perr[0]: " << pollerr.revents << endl;
+ if (pollerr.revents & POLLIN) {
+ //kdDebug(5100) << "Trying to read " << 1024 << " bytes from perr[0]" << endl;
+ if ((len = read(perr[0],str,1024))>0) {
+ //kdDebug(5100) << "Read " << len << " bytes from perr[0]" << endl;
+ str[len] ='\0';
+ error += str;
+ } else {
+ /*
+ * Apparently, on NetBSD when the child dies, the pipe begins
+ * receiving empty data packets *before* waitpid() has signaled
+ * that the child has died. Also, notice that this happens
+ * without any error bit being set in pollfd.revents (is this a
+ * NetBSD bug??? ). Notice that these anomalous packets exist
+ * according to poll(), but have length 0 according to read().
+ * Thus, kde can remain stuck inside this loop.
+ *
+ * A solution to this problem is to get out of the inner loop
+ * when read() returns <=0. In this way, kde has another chance
+ * to call waitpid() to check if the child has died -- and this
+ * time the call should succeed.
+ *
+ * Setting POLLHUP in pollfd.revents is not necessary, but I just
+ * like the idea of signaling that something strange has
+ * happened.
+ */
+ pollerr.revents |= POLLHUP;
+ break;
+ }
+ }
+ }
+ else if (pollstatus == -1) {
+ kdDebug(5100) << "Error while polling perr[0]: "
+ << pollerr.revents << endl;
+ }
+ } while ((pollstatus == 1) && (pollerr.revents & POLLIN));
+ }
+ } while (waitpidRetVal == 0);
+
+ close(pout[0]);
+ close(perr[0]);
+
+ unsetenv("PGPPASSFD");
+ if(passphrase)
+ close(ppass[0]);
+
+ // Did the child exit normally?
+ if (WIFEXITED(childExitStatus) != 0) {
+ // Get the return code of the child
+ childExitStatus = WEXITSTATUS(childExitStatus);
+ kdDebug(5100) << "PGP exited with exit status " << childExitStatus
+ << endl;
+ }
+ else {
+ childExitStatus = -1;
+ kdDebug(5100) << "PGP exited abnormally!" << endl;
+ }
+
+ //Uncomment these lines for testing only! Doing so will decrease security!
+ //kdDebug(5100) << "pgp output = " << QString(output) << endl;
+ //kdDebug(5100) << "pgp error = " << error << endl;
+
+ /* Make the information visible, so that a user can
+ * get to know what's going on during the pgp calls.
+ */
+ kdDebug(5100) << error << endl;
+
+ return childExitStatus;
+}
+
+
+int
+Base::runGpg( const char *cmd, const char *passphrase, bool onlyReadFromGnuPG )
+{
+ /* the pipe ppass is used for to pass the password to
+ * pgp. passing the password together with the normal input through
+ * stdin doesn't seem to work as expected (at least for pgp5.0)
+ */
+ char str[1025] = "\0";
+ int pin[2], pout[2], perr[2], ppass[2];
+ int len, len2;
+ FILE *pass;
+ pid_t child_pid;
+ int childExitStatus;
+ char gpgcmd[1024] = "\0";
+ struct pollfd poller[3];
+ int num_pollers = 0;
+ const int STD_OUT = 0;
+ const int STD_ERR = 1;
+ const int STD_IN = 2;
+ int pollstatus;
+
+ if(passphrase)
+ {
+ pipe(ppass);
+
+ pass = fdopen(ppass[1], "w");
+ fwrite(passphrase, sizeof(char), strlen(passphrase), pass);
+ fwrite("\n", sizeof(char), 1, pass);
+ fclose(pass);
+ close(ppass[1]);
+
+ //Uncomment these lines for testing only! Doing so will decrease security!
+ //kdDebug(5100) << "pass = " << passphrase << endl;
+ }
+
+ //Uncomment these lines for testing only! Doing so will decrease security!
+ //kdDebug(5100) << "pgp cmd = " << cmd << endl;
+ //kdDebug(5100) << "pgp input = " << QString(input)
+ // << "input length = " << input.length() << endl;
+
+ error = "";
+ output = "";
+
+ pipe(pin);
+ pipe(pout);
+ pipe(perr);
+
+ if( passphrase ) {
+ if( mVersion >= "1.0.7" ) {
+ // GnuPG >= 1.0.7 supports the gpg-agent, so we look for it.
+ if( 0 == getenv("GPG_AGENT_INFO") ) {
+ // gpg-agent not found, so we tell gpg not to use the agent
+ snprintf( gpgcmd, 1023,
+ "LANGUAGE=C gpg --no-use-agent --passphrase-fd %d %s",
+ ppass[0], cmd );
+ }
+ else {
+ // gpg-agent seems to be running, so we tell gpg to use the agent
+ snprintf( gpgcmd, 1023,
+ "LANGUAGE=C gpg --use-agent %s",
+ cmd );
+ }
+ }
+ else {
+ // GnuPG < 1.0.7 doesn't know anything about the gpg-agent
+ snprintf( gpgcmd, 1023,
+ "LANGUAGE=C gpg --passphrase-fd %d %s",
+ ppass[0], cmd );
+ }
+ }
+ else {
+ snprintf(gpgcmd, 1023, "LANGUAGE=C gpg %s",cmd);
+ }
+
+ QApplication::flushX();
+ if(!(child_pid = fork()))
+ {
+ /*We're the child.*/
+ close(pin[1]);
+ dup2(pin[0], 0);
+ close(pin[0]);
+
+ close(pout[0]);
+ dup2(pout[1], 1);
+ close(pout[1]);
+
+ close(perr[0]);
+ dup2(perr[1], 2);
+ close(perr[1]);
+
+ //#warning FIXME: there has to be a better way to do this
+ /* this is nasty nasty nasty (but it works) */
+ if( passphrase ) {
+ if( mVersion >= "1.0.7" ) {
+ // GnuPG >= 1.0.7 supports the gpg-agent, so we look for it.
+ if( 0 == getenv("GPG_AGENT_INFO") ) {
+ // gpg-agent not found, so we tell gpg not to use the agent
+ snprintf( gpgcmd, 1023,
+ "LANGUAGE=C gpg --no-use-agent --passphrase-fd %d %s",
+ ppass[0], cmd );
+ }
+ else {
+ // gpg-agent seems to be running, so we tell gpg to use the agent
+ snprintf( gpgcmd, 1023,
+ "LANGUAGE=C gpg --use-agent %s",
+ cmd );
+ }
+ }
+ else {
+ // GnuPG < 1.0.7 doesn't know anything about the gpg-agent
+ snprintf( gpgcmd, 1023,
+ "LANGUAGE=C gpg --passphrase-fd %d %s",
+ ppass[0], cmd );
+ }
+ }
+ else {
+ snprintf(gpgcmd, 1023, "LANGUAGE=C gpg %s",cmd);
+ }
+
+ kdDebug(5100) << "pgp cmd = " << gpgcmd << endl;
+
+ execl("/bin/sh", "sh", "-c", gpgcmd, (void *)0);
+ _exit(127);
+ }
+
+ // Only get here if we're the parent.
+
+ close(pin[0]);
+ close(pout[1]);
+ close(perr[1]);
+
+ // poll for "There is data to read."
+ poller[STD_OUT].fd = pout[0];
+ poller[STD_OUT].events = POLLIN;
+ poller[STD_ERR].fd = perr[0];
+ poller[STD_ERR].events = POLLIN;
+ num_pollers = 2;
+
+ if (!onlyReadFromGnuPG) {
+ // poll for "Writing now will not block."
+ poller[STD_IN].fd = pin[1];
+ poller[STD_IN].events = POLLOUT;
+ num_pollers = 3;
+ } else {
+ close (pin[1]);
+ pin[1] = -1;
+ }
+
+ pid_t waitpidRetVal;
+ unsigned int input_pos = 0;
+
+ do {
+ //kdDebug(5100) << "Checking if GnuPG is still running..." << endl;
+ childExitStatus = 0;
+ waitpidRetVal = waitpid(child_pid, &childExitStatus, WNOHANG);
+ //kdDebug(5100) << "waitpid returned " << waitpidRetVal << endl;
+ do {
+ // poll the pipes
+ pollstatus = poll(poller, num_pollers, 10);
+ if( 0 < pollstatus ) {
+ // Check stdout.
+ if (poller[STD_OUT].revents & POLLIN) {
+ //kdDebug(5100) << "Trying to read " << 1024 << " bytes from pout[0]" << endl;
+ if ((len = read(pout[0],str,1024))>0) {
+ //kdDebug(5100) << "Read " << len << " bytes from pout[0]" << endl;
+ str[len] ='\0';
+ output += str;
+ }
+ else {
+ // FreeBSD/NetBSD workaround
+ //
+ // Apparently, on Free/NetBSD when the child dies, the pipe begins
+ // receiving empty data packets *before* waitpid() has signaled
+ // that the child has died. Also, notice that this happens
+ // without any error bit being set in pollfd.revents (is this a
+ // Free/NetBSD bug??? ). Notice that these anomalous packets exist
+ // according to poll(), but have length 0 according to read().
+ // Thus, we can remain stuck inside this loop.
+ //
+ // A solution to this problem is to get out of the inner loop
+ // when read() returns <=0. In this way, we have another chance
+ // to call waitpid() to check if the child has died -- and this
+ // time the call should succeed.
+ //
+ // Set POLLHUP in pollfd.revents to signal that something strange
+ // has happened and disable polling of stdout.
+ poller[STD_OUT].revents |= POLLHUP;
+ poller[STD_OUT].events = 0;
+ }
+ } else if (poller[STD_OUT].revents & POLLHUP) {
+ // disable polling of stdout
+ poller[STD_OUT].events = 0;
+ }
+
+ // Check stderr.
+ if (poller[STD_ERR].revents & POLLIN) {
+ //kdDebug(5100) << "Trying to read " << 1024 << " bytes from perr[0]" << endl;
+ if ((len = read(poller[STD_ERR].fd,str,1024))>0) {
+ //kdDebug(5100) << "Read " << len << " bytes from perr[0]" << endl;
+ str[len] ='\0';
+ error += str;
+ }
+ else {
+ // FreeBSD/NetBSD workaround (for details see above)
+ poller[STD_ERR].revents |= POLLHUP;
+ poller[STD_ERR].events = 0;
+ }
+ } else if (poller[STD_ERR].revents & POLLHUP) {
+ // disable polling of stderr
+ poller[STD_ERR].events = 0;
+ }
+
+ if (num_pollers > 2) {
+ if (poller[STD_IN].revents & ( POLLERR | POLLHUP ) ) {
+ kdDebug(5100) << "GnuPG seems to have hung up" << endl;
+ close (pin[1]);
+ pin[1] = -1;
+ --num_pollers;
+ }
+ else if (poller[STD_IN].revents & POLLOUT) {
+ if (!input.isEmpty()) {
+ // search end of next line
+ if ((len2 = input.find('\n', input_pos)) == -1)
+ len2 = input.length()-input_pos;
+ else
+ len2 = len2-input_pos+1;
+
+ //kdDebug(5100) << "Trying to write " << len2 << " bytes to pin[1] ..." << endl;
+ len2 = write(pin[1], input.mid(input_pos,len2).data(), len2);
+ //kdDebug(5100) << "Wrote " << len2 << " bytes to pin[1] ..." << endl;
+ input_pos += len2;
+
+ // We are done.
+ if (input_pos >= input.length()) {
+ //kdDebug(5100) << "All input was written to pin[1]" << endl;
+ close (pin[1]);
+ pin[1] = -1;
+ --num_pollers;
+ }
+ }
+ else { // if input.isEmpty()
+ write(pin[1], "\n", 1);
+ //kdDebug(5100) << "All input was written to pin[1]" << endl;
+ close (pin[1]);
+ pin[1] = -1;
+ --num_pollers;
+ }
+ }
+ }
+ }
+ } while ( (pollstatus > 0) && ( (num_pollers > 2)
+ || (poller[STD_OUT].events != 0)
+ || (poller[STD_ERR].events != 0) ) );
+
+ if (pollstatus == -1) {
+ kdDebug(5100) << "GnuPG poll failed, errno: " << errno << endl;
+ }
+
+ } while(waitpidRetVal == 0);
+
+ if( 0 <= pin[1] )
+ close (pin[1]);
+ close(pout[0]);
+ close(perr[0]);
+
+ if(passphrase)
+ close(ppass[0]);
+
+ // Did the child exit normally?
+ if (WIFEXITED(childExitStatus) != 0) {
+ // Get the return code of the child
+ childExitStatus = WEXITSTATUS(childExitStatus);
+ kdDebug(5100) << "GnuPG exited with exit status " << childExitStatus
+ << endl;
+ }
+ else {
+ childExitStatus = -1;
+ kdDebug(5100) << "GnuPG exited abnormally!" << endl;
+ }
+
+ //Uncomment these lines for testing only! Doing so will decrease security!
+ //kdDebug(5100) << "gpg stdout:\n" << QString(output) << endl;
+
+ // Make the information visible, so that a user can
+ // get to know what's going on during the gpg calls.
+ kdDebug(5100) << "gpg stderr:\n" << error << endl;
+
+ return childExitStatus;
+}
+
+
+QCString
+Base::addUserId()
+{
+ QCString cmd;
+ QCString pgpUser = Module::getKpgp()->user();
+
+ if(!pgpUser.isEmpty())
+ {
+ cmd += " -u 0x";
+ cmd += pgpUser;
+ return cmd;
+ }
+ return QCString();
+}
+
+
+} // namespace Kpgp