diff --git a/lang/python/tests/t-callbacks.py b/lang/python/tests/t-callbacks.py
index 9a70cdae..b311e3d4 100755
--- a/lang/python/tests/t-callbacks.py
+++ b/lang/python/tests/t-callbacks.py
@@ -1,256 +1,256 @@
#!/usr/bin/env python
# Copyright (C) 2016 g10 Code GmbH
#
# This file is part of GPGME.
#
# GPGME 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.
#
# GPGME is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
# Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, see .
from __future__ import absolute_import, print_function, unicode_literals
del absolute_import, print_function, unicode_literals
import os
import gpg
import support
_ = support # to appease pyflakes.
c = gpg.Context()
c.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK)
source = gpg.Data("Hallo Leute\n")
sink = gpg.Data()
# Valid passphrases, both as string and bytes.
for passphrase in ('foo', b'foo'):
def passphrase_cb(hint, desc, prev_bad, hook=None):
assert hook == passphrase
return hook
c.set_passphrase_cb(passphrase_cb, passphrase)
c.op_encrypt([], 0, source, sink)
# Returning an invalid type.
def passphrase_cb(hint, desc, prev_bad, hook=None):
return 0
c.set_passphrase_cb(passphrase_cb, None)
try:
c.op_encrypt([], 0, source, sink)
except Exception as e:
assert type(e) == TypeError
assert str(e) == "expected str or bytes from passphrase callback, got int"
else:
assert False, "Expected an error, got none"
# Raising an exception inside callback.
myException = Exception()
def passphrase_cb(hint, desc, prev_bad, hook=None):
raise myException
c.set_passphrase_cb(passphrase_cb, None)
try:
c.op_encrypt([], 0, source, sink)
except Exception as e:
assert e == myException
else:
assert False, "Expected an error, got none"
# Wrong kind of callback function.
def bad_passphrase_cb():
pass
c.set_passphrase_cb(bad_passphrase_cb, None)
try:
c.op_encrypt([], 0, source, sink)
except Exception as e:
assert type(e) == TypeError
else:
assert False, "Expected an error, got none"
# Test the progress callback.
parms = """
Key-Type: RSA
Key-Length: 1024
Name-Real: Joe Tester
Name-Comment: with stupid passphrase
Name-Email: joe+gpg@example.org
Passphrase: Crypt0R0cks
-Expire-Date: 2020-12-31
+Expire-Date: 2099-12-31
"""
messages = []
def progress_cb(what, typ, current, total, hook=None):
assert hook == messages
messages.append(
"PROGRESS UPDATE: what = {}, type = {}, current = {}, total = {}"
.format(what, typ, current, total))
c = gpg.Context()
c.set_progress_cb(progress_cb, messages)
c.op_genkey(parms, None, None)
assert len(messages) > 0
# Test exception handling.
def progress_cb(what, typ, current, total, hook=None):
raise myException
c = gpg.Context()
c.set_progress_cb(progress_cb, None)
try:
c.op_genkey(parms, None, None)
except Exception as e:
assert e == myException
else:
assert False, "Expected an error, got none"
# Test the edit callback.
c = gpg.Context()
c.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK)
c.set_passphrase_cb(lambda *args: "abc")
sink = gpg.Data()
alpha = c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False)
cookie = object()
edit_cb_called = False
def edit_cb(status, args, hook):
global edit_cb_called
edit_cb_called = True
assert hook == cookie
return "quit" if args == "keyedit.prompt" else None
c.op_edit(alpha, edit_cb, cookie, sink)
assert edit_cb_called
# Test exceptions.
c = gpg.Context()
c.set_pinentry_mode(gpg.constants.PINENTRY_MODE_LOOPBACK)
c.set_passphrase_cb(lambda *args: "abc")
sink = gpg.Data()
def edit_cb(status, args):
raise myException
try:
c.op_edit(alpha, edit_cb, None, sink)
except Exception as e:
assert e == myException
else:
assert False, "Expected an error, got none"
# Test the status callback.
source = gpg.Data("Hallo Leute\n")
sink = gpg.Data()
status_cb_called = False
def status_cb(keyword, args, hook=None):
global status_cb_called
status_cb_called = True
assert hook == cookie
c = gpg.Context()
c.set_status_cb(status_cb, cookie)
c.set_ctx_flag("full-status", "1")
c.op_encrypt([alpha], gpg.constants.ENCRYPT_ALWAYS_TRUST, source, sink)
assert status_cb_called
# Test exceptions.
source = gpg.Data("Hallo Leute\n")
sink = gpg.Data()
def status_cb(keyword, args):
raise myException
c = gpg.Context()
c.set_status_cb(status_cb, None)
c.set_ctx_flag("full-status", "1")
try:
c.op_encrypt([alpha], gpg.constants.ENCRYPT_ALWAYS_TRUST, source, sink)
except Exception as e:
assert e == myException
else:
assert False, "Expected an error, got none"
# Test the data callbacks.
def read_cb(amount, hook=None):
assert hook == cookie
return 0
def release_cb(hook=None):
assert hook == cookie
data = gpg.Data(cbs=(read_cb, None, None, release_cb, cookie))
try:
data.read()
except Exception as e:
assert type(e) == TypeError
else:
assert False, "Expected an error, got none"
def read_cb(amount):
raise myException
data = gpg.Data(cbs=(read_cb, None, None, lambda: None))
try:
data.read()
except Exception as e:
assert e == myException
else:
assert False, "Expected an error, got none"
def write_cb(what, hook=None):
assert hook == cookie
return "wrong type"
data = gpg.Data(cbs=(None, write_cb, None, release_cb, cookie))
try:
data.write(b'stuff')
except Exception as e:
assert type(e) == TypeError
else:
assert False, "Expected an error, got none"
def write_cb(what):
raise myException
data = gpg.Data(cbs=(None, write_cb, None, lambda: None))
try:
data.write(b'stuff')
except Exception as e:
assert e == myException
else:
assert False, "Expected an error, got none"
def seek_cb(offset, whence, hook=None):
assert hook == cookie
return "wrong type"
data = gpg.Data(cbs=(None, None, seek_cb, release_cb, cookie))
try:
data.seek(0, os.SEEK_SET)
except Exception as e:
assert type(e) == TypeError
else:
assert False, "Expected an error, got none"
def seek_cb(offset, whence):
raise myException
data = gpg.Data(cbs=(None, None, seek_cb, lambda: None))
try:
data.seek(0, os.SEEK_SET)
except Exception as e:
assert e == myException
else:
assert False, "Expected an error, got none"
diff --git a/lang/qt/Makefile.am b/lang/qt/Makefile.am
index ab859609..a1b83e8d 100644
--- a/lang/qt/Makefile.am
+++ b/lang/qt/Makefile.am
@@ -1,24 +1,30 @@
# Makefile.am for GPGMEPP.
# Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
# Software engineering by Intevation GmbH
#
# This file is part of GPGMEPP.
#
# GPGME-CL 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.
#
# GPGME-CL is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# 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., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA
-SUBDIRS = src tests doc
+if RUN_GPG_TESTS
+tests = tests
+else
+tests =
+endif
+
+SUBDIRS = src ${tests} doc
EXTRA_DIST = README
diff --git a/lang/qt/src/qgpgmenewcryptoconfig.cpp b/lang/qt/src/qgpgmenewcryptoconfig.cpp
index ba028a97..070ab697 100644
--- a/lang/qt/src/qgpgmenewcryptoconfig.cpp
+++ b/lang/qt/src/qgpgmenewcryptoconfig.cpp
@@ -1,752 +1,752 @@
/*
qgpgmenewcryptoconfig.cpp
This file is part of qgpgme, the Qt API binding for gpgme
Copyright (c) 2010 Klarälvdalens Datakonsult AB
Copyright (c) 2016 by Bundesamt für Sicherheit in der Informationstechnik
Software engineering by Intevation GmbH
QGpgME 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.
QGpgME is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
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
In addition, as a special exception, the copyright holders give
permission to link the code of this program with any edition of
the Qt library by Trolltech AS, Norway (or with modified versions
of Qt that use the same license as Qt), and distribute linked
combinations including the two. You must obey the GNU General
Public License in all respects for all of the code used other than
Qt. If you modify this file, you may extend this exception to
your version of the file, but you are not obligated to do so. If
you do not wish to do so, delete this exception statement from
your version.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "qgpgmenewcryptoconfig.h"
#include
#include "gpgme_backend_debug.h"
#include
+#include
#include "global.h"
#include "error.h"
#include
#include
#include
#include
#include
using namespace QGpgME;
using namespace GpgME;
using namespace GpgME::Configuration;
namespace
{
struct Select1St {
template
const U &operator()(const std::pair &p) const
{
return p.first;
}
template
const U &operator()(const QPair &p) const
{
return p.first;
}
};
}
// Just for the Q_ASSERT in the dtor. Not thread-safe, but who would
// have 2 threads talking to gpgconf anyway? :)
static bool s_duringClear = false;
QGpgMENewCryptoConfig::QGpgMENewCryptoConfig()
: m_parsed(false)
{
}
QGpgMENewCryptoConfig::~QGpgMENewCryptoConfig()
{
clear();
}
void QGpgMENewCryptoConfig::reloadConfiguration(bool)
{
clear();
Error error;
const std::vector components = Component::load(error);
#ifndef NDEBUG
{
std::stringstream ss;
ss << "error: " << error
<< "components:\n";
std::copy(components.begin(), components.end(),
std::ostream_iterator(ss, "\n"));
qCDebug(GPGPME_BACKEND_LOG) << ss.str().c_str();
}
#endif
#if 0
TODO port?
if (error && showErrors) {
const QString wmsg = i18n("Failed to execute gpgconf:%1
", QString::fromLocal8Bit(error.asString()));
qCWarning(GPGPME_BACKEND_LOG) << wmsg; // to see it from test_cryptoconfig.cpp
KMessageBox::error(0, wmsg);
}
#endif
Q_FOREACH(const Component & c, components) {
const std::shared_ptr comp(new QGpgMENewCryptoConfigComponent);
comp->setComponent(c);
m_componentsByName[ comp->name() ] = comp;
}
m_parsed = true;
}
QStringList QGpgMENewCryptoConfig::componentList() const
{
if (!m_parsed) {
const_cast(this)->reloadConfiguration(true);
}
QStringList result;
std::transform(m_componentsByName.begin(), m_componentsByName.end(),
std::back_inserter(result),
mem_fn(&QGpgMENewCryptoConfigComponent::name));
return result;
}
QGpgMENewCryptoConfigComponent *QGpgMENewCryptoConfig::component(const QString &name) const
{
if (!m_parsed) {
const_cast(this)->reloadConfiguration(false);
}
return m_componentsByName.value(name).get();
}
void QGpgMENewCryptoConfig::sync(bool runtime)
{
Q_FOREACH(const std::shared_ptr &c, m_componentsByName)
c->sync(runtime);
}
void QGpgMENewCryptoConfig::clear()
{
s_duringClear = true;
m_componentsByName.clear();
s_duringClear = false;
m_parsed = false; // next call to componentList/component will need to run gpgconf again
}
////
QGpgMENewCryptoConfigComponent::QGpgMENewCryptoConfigComponent()
: CryptoConfigComponent(),
m_component()
{
}
void QGpgMENewCryptoConfigComponent::setComponent(const Component &component)
{
m_component = component;
m_groupsByName.clear();
std::shared_ptr group;
const std::vector options = m_component.options();
Q_FOREACH(const Option & o, options)
if (o.flags() & Group) {
if (group) {
m_groupsByName[group->name()] = group;
}
group.reset(new QGpgMENewCryptoConfigGroup(shared_from_this(), o));
} else if (group) {
const std::shared_ptr entry(new QGpgMENewCryptoConfigEntry(group, o));
const QString name = entry->name();
group->m_entryNames.push_back(name);
group->m_entriesByName[name] = entry;
} else {
qCWarning(GPGPME_BACKEND_LOG) << "found no group for entry" << o.name() << "of component" << name();
}
if (group) {
m_groupsByName[group->name()] = group;
}
}
QGpgMENewCryptoConfigComponent::~QGpgMENewCryptoConfigComponent() {}
QString QGpgMENewCryptoConfigComponent::name() const
{
return QString::fromUtf8(m_component.name());
}
QString QGpgMENewCryptoConfigComponent::description() const
{
return QString::fromUtf8(m_component.description());
}
QStringList QGpgMENewCryptoConfigComponent::groupList() const
{
QStringList result;
result.reserve(m_groupsByName.size());
std::transform(m_groupsByName.begin(), m_groupsByName.end(),
std::back_inserter(result),
std::mem_fn(&QGpgMENewCryptoConfigGroup::name));
return result;
}
QGpgMENewCryptoConfigGroup *QGpgMENewCryptoConfigComponent::group(const QString &name) const
{
return m_groupsByName.value(name).get();
}
void QGpgMENewCryptoConfigComponent::sync(bool runtime)
{
Q_UNUSED(runtime) // runtime is always set by engine_gpgconf
if (const Error err = m_component.save()) {
qCWarning(GPGPME_BACKEND_LOG) << ":"
<< "Error from gpgconf while saving configuration: %1"
<< QString::fromLocal8Bit(err.asString());
}
}
////
QGpgMENewCryptoConfigGroup::QGpgMENewCryptoConfigGroup(const std::shared_ptr &comp, const Option &option)
: CryptoConfigGroup(),
m_component(comp),
m_option(option)
{
}
QGpgMENewCryptoConfigGroup::~QGpgMENewCryptoConfigGroup() {}
QString QGpgMENewCryptoConfigGroup::name() const
{
return QString::fromUtf8(m_option.name());
}
QString QGpgMENewCryptoConfigGroup::description() const
{
return QString::fromUtf8(m_option.description());
}
QString QGpgMENewCryptoConfigGroup::path() const
{
if (const std::shared_ptr c = m_component.lock()) {
return c->name() + QLatin1Char('/') + name();
} else {
return QString();
}
}
CryptoConfigEntry::Level QGpgMENewCryptoConfigGroup::level() const
{
// two casts to make SunCC happy:
return static_cast(static_cast(m_option.level()));
}
QStringList QGpgMENewCryptoConfigGroup::entryList() const
{
return m_entryNames;
}
QGpgMENewCryptoConfigEntry *QGpgMENewCryptoConfigGroup::entry(const QString &name) const
{
return m_entriesByName.value(name).get();
}
static QString urlpart_encode(const QString &str)
{
QString enc(str);
enc.replace(QLatin1Char('%'), QStringLiteral("%25")); // first!
enc.replace(QLatin1Char(':'), QStringLiteral("%3a"));
//qCDebug(GPGPME_BACKEND_LOG) <<" urlpart_encode:" << str <<" ->" << enc;
return enc;
}
static QString urlpart_decode(const QString &str)
{
return QUrl::fromPercentEncoding(str.toLatin1());
}
// gpgconf arg type number -> NewCryptoConfigEntry arg type enum mapping
static QGpgME::CryptoConfigEntry::ArgType knownArgType(int argType, bool &ok)
{
ok = true;
switch (argType) {
case 0: // none
return QGpgME::CryptoConfigEntry::ArgType_None;
case 1: // string
return QGpgME::CryptoConfigEntry::ArgType_String;
case 2: // int32
return QGpgME::CryptoConfigEntry::ArgType_Int;
case 3: // uint32
return QGpgME::CryptoConfigEntry::ArgType_UInt;
case 32: // pathname
return QGpgME::CryptoConfigEntry::ArgType_Path;
case 33: // ldap server
return QGpgME::CryptoConfigEntry::ArgType_LDAPURL;
default:
ok = false;
return QGpgME::CryptoConfigEntry::ArgType_None;
}
}
QGpgMENewCryptoConfigEntry::QGpgMENewCryptoConfigEntry(const std::shared_ptr &group, const Option &option)
: m_group(group), m_option(option)
{
}
#if 0
QVariant QGpgMENewCryptoConfigEntry::stringToValue(const QString &str, bool unescape) const
{
const bool isString = isStringType();
if (isList()) {
if (argType() == ArgType_None) {
bool ok = true;
const QVariant v = str.isEmpty() ? 0U : str.toUInt(&ok);
if (!ok) {
qCWarning(GPGPME_BACKEND_LOG) << "list-of-none should have an unsigned int as value:" << str;
}
return v;
}
QList lst;
QStringList items = str.split(',', QString::SkipEmptyParts);
for (QStringList::const_iterator valit = items.constBegin(); valit != items.constEnd(); ++valit) {
QString val = *valit;
if (isString) {
if (val.isEmpty()) {
lst << QVariant(QString());
continue;
} else if (unescape) {
if (val[0] != '"') { // see README.gpgconf
qCWarning(GPGPME_BACKEND_LOG) << "String value should start with '\"' :" << val;
}
val = val.mid(1);
}
}
lst << QVariant(unescape ? gpgconf_unescape(val) : val);
}
return lst;
} else { // not a list
QString val(str);
if (isString) {
if (val.isEmpty()) {
return QVariant(QString()); // not set [ok with lists too?]
} else if (unescape) {
if (val[0] != '"') { // see README.gpgconf
qCWarning(GPGPME_BACKEND_LOG) << "String value should start with '\"' :" << val;
}
val = val.mid(1);
}
}
return QVariant(unescape ? gpgconf_unescape(val) : val);
}
}
#endif
QGpgMENewCryptoConfigEntry::~QGpgMENewCryptoConfigEntry()
{
#ifndef NDEBUG
if (!s_duringClear && m_option.dirty())
qCWarning(GPGPME_BACKEND_LOG) << "Deleting a QGpgMENewCryptoConfigEntry that was modified (" << m_option.description() << ")"
<< "You forgot to call sync() (to commit) or clear() (to discard)";
#endif
}
QString QGpgMENewCryptoConfigEntry::name() const
{
return QString::fromUtf8(m_option.name());
}
QString QGpgMENewCryptoConfigEntry::description() const
{
return QString::fromUtf8(m_option.description());
}
QString QGpgMENewCryptoConfigEntry::path() const
{
if (const std::shared_ptr g = m_group.lock()) {
return g->path() + QLatin1Char('/') + name();
} else {
return QString();
}
}
bool QGpgMENewCryptoConfigEntry::isOptional() const
{
return m_option.flags() & Optional;
}
bool QGpgMENewCryptoConfigEntry::isReadOnly() const
{
return m_option.flags() & NoChange;
}
bool QGpgMENewCryptoConfigEntry::isList() const
{
return m_option.flags() & List;
}
bool QGpgMENewCryptoConfigEntry::isRuntime() const
{
return m_option.flags() & Runtime;
}
CryptoConfigEntry::Level QGpgMENewCryptoConfigEntry::level() const
{
// two casts to make SunCC happy:
return static_cast(static_cast(m_option.level()));
}
CryptoConfigEntry::ArgType QGpgMENewCryptoConfigEntry::argType() const
{
bool ok = false;
const ArgType type = knownArgType(m_option.type(), ok);
if (ok) {
return type;
} else {
return knownArgType(m_option.alternateType(), ok);
}
}
bool QGpgMENewCryptoConfigEntry::isSet() const
{
return m_option.set();
}
bool QGpgMENewCryptoConfigEntry::boolValue() const
{
Q_ASSERT(m_option.alternateType() == NoType);
Q_ASSERT(!isList());
return m_option.currentValue().boolValue();
}
QString QGpgMENewCryptoConfigEntry::stringValue() const
{
//return toString( false );
Q_ASSERT(m_option.alternateType() == StringType);
Q_ASSERT(!isList());
return QString::fromUtf8(m_option.currentValue().stringValue());
}
int QGpgMENewCryptoConfigEntry::intValue() const
{
Q_ASSERT(m_option.alternateType() == IntegerType);
Q_ASSERT(!isList());
return m_option.currentValue().intValue();
}
unsigned int QGpgMENewCryptoConfigEntry::uintValue() const
{
Q_ASSERT(m_option.alternateType() == UnsignedIntegerType);
Q_ASSERT(!isList());
return m_option.currentValue().uintValue();
}
static QUrl parseURL(int mRealArgType, const QString &str)
{
if (mRealArgType == 33) { // LDAP server
// The format is HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN
QStringList items = str.split(QLatin1Char(':'));
if (items.count() == 5) {
QStringList::const_iterator it = items.constBegin();
QUrl url;
url.setScheme(QStringLiteral("ldap"));
url.setHost(urlpart_decode(*it++));
bool ok;
const int port = (*it++).toInt(&ok);
if (ok) {
url.setPort(port);
} else if (!it->isEmpty()) {
qCWarning(GPGPME_BACKEND_LOG) << "parseURL: malformed LDAP server port, ignoring: \"" << *it << "\"";
}
const QString userName = urlpart_decode(*it++);
if (!userName.isEmpty()) {
url.setUserName(userName);
}
const QString passWord = urlpart_decode(*it++);
if (!passWord.isEmpty()) {
url.setPassword(passWord);
}
url.setQuery(urlpart_decode(*it));
return url;
} else {
qCWarning(GPGPME_BACKEND_LOG) << "parseURL: malformed LDAP server:" << str;
}
}
// other URLs : assume wellformed URL syntax.
return QUrl(str);
}
// The opposite of parseURL
static QString splitURL(int mRealArgType, const QUrl &url)
{
if (mRealArgType == 33) { // LDAP server
// The format is HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN
Q_ASSERT(url.scheme() == QLatin1String("ldap"));
return urlpart_encode(url.host()) + QLatin1Char(':') +
(url.port() != -1 ? QString::number(url.port()) : QString()) + QLatin1Char(':') + // -1 is used for default ports, omit
urlpart_encode(url.userName()) + QLatin1Char(':') +
urlpart_encode(url.password()) + QLatin1Char(':') +
urlpart_encode(url.query());
}
return url.path();
}
QUrl QGpgMENewCryptoConfigEntry::urlValue() const
{
const Type type = m_option.type();
Q_ASSERT(type == FilenameType || type == LdapServerType);
Q_ASSERT(!isList());
if (type == FilenameType) {
- QUrl url;
- url.setPath(QFile::decodeName(m_option.currentValue().stringValue()));
+ QUrl url = QUrl::fromLocalFile(m_option.currentValue().stringValue());
return url;
}
return parseURL(type, stringValue());
}
unsigned int QGpgMENewCryptoConfigEntry::numberOfTimesSet() const
{
Q_ASSERT(m_option.alternateType() == NoType);
Q_ASSERT(isList());
return m_option.currentValue().uintValue();
}
std::vector QGpgMENewCryptoConfigEntry::intValueList() const
{
Q_ASSERT(m_option.alternateType() == IntegerType);
Q_ASSERT(isList());
return m_option.currentValue().intValues();
}
std::vector QGpgMENewCryptoConfigEntry::uintValueList() const
{
Q_ASSERT(m_option.alternateType() == UnsignedIntegerType);
Q_ASSERT(isList());
return m_option.currentValue().uintValues();
}
QStringList QGpgMENewCryptoConfigEntry::stringValueList() const
{
Q_ASSERT(isList());
const Argument arg = m_option.currentValue();
const std::vector values = arg.stringValues();
QStringList ret;
for(const char *value: values) {
ret << QString::fromUtf8(value);
}
return ret;
}
QList QGpgMENewCryptoConfigEntry::urlValueList() const
{
const Type type = m_option.type();
Q_ASSERT(type == FilenameType || type == LdapServerType);
Q_ASSERT(isList());
const Argument arg = m_option.currentValue();
const std::vector values = arg.stringValues();
QList ret;
Q_FOREACH(const char *value, values)
if (type == FilenameType) {
QUrl url;
url.setPath(QFile::decodeName(value));
ret << url;
} else {
ret << parseURL(type, QString::fromUtf8(value));
}
return ret;
}
void QGpgMENewCryptoConfigEntry::resetToDefault()
{
m_option.resetToDefaultValue();
}
void QGpgMENewCryptoConfigEntry::setBoolValue(bool b)
{
Q_ASSERT(m_option.alternateType() == NoType);
Q_ASSERT(!isList());
// A "no arg" option is either set or not set.
// Being set means createNoneArgument(), being unset means resetToDefault()
m_option.setNewValue(m_option.createNoneArgument(b));
}
void QGpgMENewCryptoConfigEntry::setStringValue(const QString &str)
{
Q_ASSERT(m_option.alternateType() == StringType);
Q_ASSERT(!isList());
const Type type = m_option.type();
// When setting a string to empty (and there's no default), we need to act like resetToDefault
// Otherwise we try e.g. "ocsp-responder:0:" and gpgconf answers:
// "gpgconf: argument required for option ocsp-responder"
if (str.isEmpty() && !isOptional()) {
m_option.resetToDefaultValue();
} else if (type == FilenameType) {
m_option.setNewValue(m_option.createStringArgument(QFile::encodeName(str).constData()));
} else {
m_option.setNewValue(m_option.createStringArgument(str.toUtf8().constData()));
}
}
void QGpgMENewCryptoConfigEntry::setIntValue(int i)
{
Q_ASSERT(m_option.alternateType() == IntegerType);
Q_ASSERT(!isList());
m_option.setNewValue(m_option.createIntArgument(i));
}
void QGpgMENewCryptoConfigEntry::setUIntValue(unsigned int i)
{
Q_ASSERT(m_option.alternateType() == UnsignedIntegerType);
Q_ASSERT(!isList());
m_option.setNewValue(m_option.createUIntArgument(i));
}
void QGpgMENewCryptoConfigEntry::setURLValue(const QUrl &url)
{
const Type type = m_option.type();
Q_ASSERT(type == FilenameType || type == LdapServerType);
Q_ASSERT(!isList());
const QString str = splitURL(type, url);
// cf. setStringValue()
if (str.isEmpty() && !isOptional()) {
m_option.resetToDefaultValue();
} else if (type == FilenameType) {
- m_option.setNewValue(m_option.createStringArgument(QFile::encodeName(str).constData()));
+ m_option.setNewValue(m_option.createStringArgument(QDir::toNativeSeparators(url.toLocalFile()).toUtf8().constData()));
} else {
m_option.setNewValue(m_option.createStringArgument(str.toUtf8().constData()));
}
}
void QGpgMENewCryptoConfigEntry::setNumberOfTimesSet(unsigned int i)
{
Q_ASSERT(m_option.alternateType() == NoType);
Q_ASSERT(isList());
m_option.setNewValue(m_option.createNoneListArgument(i));
}
void QGpgMENewCryptoConfigEntry::setIntValueList(const std::vector &lst)
{
Q_ASSERT(m_option.alternateType() == IntegerType);
Q_ASSERT(isList());
m_option.setNewValue(m_option.createIntListArgument(lst));
}
void QGpgMENewCryptoConfigEntry::setUIntValueList(const std::vector &lst)
{
Q_ASSERT(m_option.alternateType() == UnsignedIntegerType);
Q_ASSERT(isList());
m_option.setNewValue(m_option.createUIntListArgument(lst));
}
void QGpgMENewCryptoConfigEntry::setURLValueList(const QList &urls)
{
const Type type = m_option.type();
Q_ASSERT(m_option.alternateType() == StringType);
Q_ASSERT(isList());
std::vector values;
values.reserve(urls.size());
Q_FOREACH (const QUrl &url, urls)
if (type == FilenameType) {
values.push_back(QFile::encodeName(url.path()).constData());
} else {
values.push_back(splitURL(type, url).toUtf8().constData());
}
m_option.setNewValue(m_option.createStringListArgument(values));
}
bool QGpgMENewCryptoConfigEntry::isDirty() const
{
return m_option.dirty();
}
#if 0
QString QGpgMENewCryptoConfigEntry::toString(bool escape) const
{
// Basically the opposite of stringToValue
if (isStringType()) {
if (mValue.isNull()) {
return QString();
} else if (isList()) { // string list
QStringList lst = mValue.toStringList();
if (escape) {
for (QStringList::iterator it = lst.begin(); it != lst.end(); ++it) {
if (!(*it).isNull()) {
*it = gpgconf_escape(*it).prepend("\"");
}
}
}
QString res = lst.join(",");
//qCDebug(GPGPME_BACKEND_LOG) <<"toString:" << res;
return res;
} else { // normal string
QString res = mValue.toString();
if (escape) {
res = gpgconf_escape(res).prepend("\"");
}
return res;
}
}
if (!isList()) { // non-list non-string
if (mArgType == ArgType_None) {
return mValue.toBool() ? QString::fromLatin1("1") : QString();
} else { // some int
Q_ASSERT(mArgType == ArgType_Int || mArgType == ArgType_UInt);
return mValue.toString(); // int to string conversion
}
}
// Lists (of other types than strings)
if (mArgType == ArgType_None) {
return QString::number(numberOfTimesSet());
}
QStringList ret;
QList lst = mValue.toList();
for (QList::const_iterator it = lst.constBegin(); it != lst.constEnd(); ++it) {
ret << (*it).toString(); // QVariant does the conversion
}
return ret.join(",");
}
QString QGpgMENewCryptoConfigEntry::outputString() const
{
Q_ASSERT(mSet);
return toString(true);
}
bool QGpgMENewCryptoConfigEntry::isStringType() const
{
return (mArgType == QGpgME::NewCryptoConfigEntry::ArgType_String
|| mArgType == QGpgME::NewCryptoConfigEntry::ArgType_Path
|| mArgType == QGpgME::NewCryptoConfigEntry::ArgType_URL
|| mArgType == QGpgME::NewCryptoConfigEntry::ArgType_LDAPURL);
}
void QGpgMENewCryptoConfigEntry::setDirty(bool b)
{
mDirty = b;
}
#endif
diff --git a/src/engine-gpgsm.c b/src/engine-gpgsm.c
index da7e524f..7b221831 100644
--- a/src/engine-gpgsm.c
+++ b/src/engine-gpgsm.c
@@ -1,2222 +1,2231 @@
/* engine-gpgsm.c - GpgSM engine.
Copyright (C) 2000 Werner Koch (dd9jn)
Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009,
2010 g10 Code GmbH
This file is part of GPGME.
GPGME is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of
the License, or (at your option) any later version.
GPGME is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. */
#if HAVE_CONFIG_H
#include
#endif
#include
#include
#ifdef HAVE_SYS_TYPES_H
# include
#endif
#include
#ifdef HAVE_UNISTD_H
# include
#endif
#ifdef HAVE_LOCALE_H
#include
#endif
#include /* FIXME */
#include
#include "gpgme.h"
#include "util.h"
#include "ops.h"
#include "wait.h"
#include "priv-io.h"
#include "sema.h"
#include "data.h"
#include "assuan.h"
#include "debug.h"
#include "engine-backend.h"
typedef struct
{
int fd; /* FD we talk about. */
int server_fd;/* Server FD for this connection. */
int dir; /* Inbound/Outbound, maybe given implicit? */
void *data; /* Handler-specific data. */
void *tag; /* ID from the user for gpgme_remove_io_callback. */
char server_fd_str[15]; /* Same as SERVER_FD but as a string. We
need this because _gpgme_io_fd2str can't
be used on a closed descriptor. */
} iocb_data_t;
struct engine_gpgsm
{
assuan_context_t assuan_ctx;
int lc_ctype_set;
int lc_messages_set;
iocb_data_t status_cb;
/* Input, output etc are from the servers perspective. */
iocb_data_t input_cb;
gpgme_data_t input_helper_data; /* Input helper data object. */
void *input_helper_memory; /* Input helper memory block. */
iocb_data_t output_cb;
iocb_data_t message_cb;
struct
{
engine_status_handler_t fnc;
void *fnc_value;
gpgme_status_cb_t mon_cb;
void *mon_cb_value;
} status;
struct
{
engine_colon_line_handler_t fnc;
void *fnc_value;
struct
{
char *line;
int linesize;
int linelen;
} attic;
int any; /* any data line seen */
} colon;
gpgme_data_t inline_data; /* Used to collect D lines. */
char request_origin[10];
struct gpgme_io_cbs io_cbs;
};
typedef struct engine_gpgsm *engine_gpgsm_t;
static void gpgsm_io_event (void *engine,
gpgme_event_io_t type, void *type_data);
static char *
gpgsm_get_version (const char *file_name)
{
return _gpgme_get_program_version (file_name ? file_name
: _gpgme_get_default_gpgsm_name ());
}
static const char *
gpgsm_get_req_version (void)
{
return "2.0.4";
}
static void
close_notify_handler (int fd, void *opaque)
{
engine_gpgsm_t gpgsm = opaque;
assert (fd != -1);
if (gpgsm->status_cb.fd == fd)
{
if (gpgsm->status_cb.tag)
(*gpgsm->io_cbs.remove) (gpgsm->status_cb.tag);
gpgsm->status_cb.fd = -1;
gpgsm->status_cb.tag = NULL;
}
else if (gpgsm->input_cb.fd == fd)
{
if (gpgsm->input_cb.tag)
(*gpgsm->io_cbs.remove) (gpgsm->input_cb.tag);
gpgsm->input_cb.fd = -1;
gpgsm->input_cb.tag = NULL;
if (gpgsm->input_helper_data)
{
gpgme_data_release (gpgsm->input_helper_data);
gpgsm->input_helper_data = NULL;
}
if (gpgsm->input_helper_memory)
{
free (gpgsm->input_helper_memory);
gpgsm->input_helper_memory = NULL;
}
}
else if (gpgsm->output_cb.fd == fd)
{
if (gpgsm->output_cb.tag)
(*gpgsm->io_cbs.remove) (gpgsm->output_cb.tag);
gpgsm->output_cb.fd = -1;
gpgsm->output_cb.tag = NULL;
}
else if (gpgsm->message_cb.fd == fd)
{
if (gpgsm->message_cb.tag)
(*gpgsm->io_cbs.remove) (gpgsm->message_cb.tag);
gpgsm->message_cb.fd = -1;
gpgsm->message_cb.tag = NULL;
}
}
/* This is the default inquiry callback. We use it to handle the
Pinentry notifications. */
static gpgme_error_t
default_inq_cb (engine_gpgsm_t gpgsm, const char *line)
{
(void)gpgsm;
if (!strncmp (line, "PINENTRY_LAUNCHED", 17) && (line[17]==' '||!line[17]))
{
_gpgme_allow_set_foreground_window ((pid_t)strtoul (line+17, NULL, 10));
}
return 0;
}
static gpgme_error_t
gpgsm_cancel (void *engine)
{
engine_gpgsm_t gpgsm = engine;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
if (gpgsm->status_cb.fd != -1)
_gpgme_io_close (gpgsm->status_cb.fd);
if (gpgsm->input_cb.fd != -1)
_gpgme_io_close (gpgsm->input_cb.fd);
if (gpgsm->output_cb.fd != -1)
_gpgme_io_close (gpgsm->output_cb.fd);
if (gpgsm->message_cb.fd != -1)
_gpgme_io_close (gpgsm->message_cb.fd);
if (gpgsm->assuan_ctx)
{
assuan_release (gpgsm->assuan_ctx);
gpgsm->assuan_ctx = NULL;
}
return 0;
}
static void
gpgsm_release (void *engine)
{
engine_gpgsm_t gpgsm = engine;
if (!gpgsm)
return;
gpgsm_cancel (engine);
free (gpgsm->colon.attic.line);
free (gpgsm);
}
static gpgme_error_t
gpgsm_new (void **engine, const char *file_name, const char *home_dir,
const char *version)
{
gpgme_error_t err = 0;
engine_gpgsm_t gpgsm;
const char *pgmname;
const char *argv[5];
int argc;
#if !USE_DESCRIPTOR_PASSING
int fds[2];
int child_fds[4];
#endif
char *dft_display = NULL;
char dft_ttyname[64];
char *env_tty = NULL;
char *dft_ttytype = NULL;
char *optstr;
(void)version; /* Not yet used. */
gpgsm = calloc (1, sizeof *gpgsm);
if (!gpgsm)
return gpg_error_from_syserror ();
gpgsm->status_cb.fd = -1;
gpgsm->status_cb.dir = 1;
gpgsm->status_cb.tag = 0;
gpgsm->status_cb.data = gpgsm;
gpgsm->input_cb.fd = -1;
gpgsm->input_cb.dir = 0;
gpgsm->input_cb.tag = 0;
gpgsm->input_cb.server_fd = -1;
*gpgsm->input_cb.server_fd_str = 0;
gpgsm->output_cb.fd = -1;
gpgsm->output_cb.dir = 1;
gpgsm->output_cb.tag = 0;
gpgsm->output_cb.server_fd = -1;
*gpgsm->output_cb.server_fd_str = 0;
gpgsm->message_cb.fd = -1;
gpgsm->message_cb.dir = 0;
gpgsm->message_cb.tag = 0;
gpgsm->message_cb.server_fd = -1;
*gpgsm->message_cb.server_fd_str = 0;
gpgsm->status.fnc = 0;
gpgsm->colon.fnc = 0;
gpgsm->colon.attic.line = 0;
gpgsm->colon.attic.linesize = 0;
gpgsm->colon.attic.linelen = 0;
gpgsm->colon.any = 0;
gpgsm->inline_data = NULL;
gpgsm->io_cbs.add = NULL;
gpgsm->io_cbs.add_priv = NULL;
gpgsm->io_cbs.remove = NULL;
gpgsm->io_cbs.event = NULL;
gpgsm->io_cbs.event_priv = NULL;
#if !USE_DESCRIPTOR_PASSING
if (_gpgme_io_pipe (fds, 0) < 0)
{
err = gpg_error_from_syserror ();
goto leave;
}
gpgsm->input_cb.fd = fds[1];
gpgsm->input_cb.server_fd = fds[0];
if (_gpgme_io_pipe (fds, 1) < 0)
{
err = gpg_error_from_syserror ();
goto leave;
}
gpgsm->output_cb.fd = fds[0];
gpgsm->output_cb.server_fd = fds[1];
if (_gpgme_io_pipe (fds, 0) < 0)
{
err = gpg_error_from_syserror ();
goto leave;
}
gpgsm->message_cb.fd = fds[1];
gpgsm->message_cb.server_fd = fds[0];
child_fds[0] = gpgsm->input_cb.server_fd;
child_fds[1] = gpgsm->output_cb.server_fd;
child_fds[2] = gpgsm->message_cb.server_fd;
child_fds[3] = -1;
#endif
pgmname = file_name ? file_name : _gpgme_get_default_gpgsm_name ();
argc = 0;
argv[argc++] = _gpgme_get_basename (pgmname);
if (home_dir)
{
argv[argc++] = "--homedir";
argv[argc++] = home_dir;
}
argv[argc++] = "--server";
argv[argc++] = NULL;
err = assuan_new_ext (&gpgsm->assuan_ctx, GPG_ERR_SOURCE_GPGME,
&_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb,
NULL);
if (err)
goto leave;
assuan_ctx_set_system_hooks (gpgsm->assuan_ctx, &_gpgme_assuan_system_hooks);
#if USE_DESCRIPTOR_PASSING
err = assuan_pipe_connect (gpgsm->assuan_ctx, pgmname, argv,
NULL, NULL, NULL, ASSUAN_PIPE_CONNECT_FDPASSING);
#else
{
assuan_fd_t achild_fds[4];
int i;
/* For now... */
for (i = 0; i < 4; i++)
achild_fds[i] = (assuan_fd_t) child_fds[i];
err = assuan_pipe_connect (gpgsm->assuan_ctx, pgmname, argv,
achild_fds, NULL, NULL, 0);
/* For now... */
for (i = 0; i < 4; i++)
child_fds[i] = (int) achild_fds[i];
}
/* On Windows, handles are inserted in the spawned process with
DuplicateHandle, and child_fds contains the server-local names
for the inserted handles when assuan_pipe_connect returns. */
if (!err)
{
/* Note: We don't use _gpgme_io_fd2str here. On W32 the
returned handles are real W32 system handles, not whatever
GPGME uses internally (which may be a system handle, a C
library handle or a GLib/Qt channel. Confusing, yes, but
remember these are server-local names, so they are not part
of GPGME at all. */
snprintf (gpgsm->input_cb.server_fd_str,
sizeof gpgsm->input_cb.server_fd_str, "%d", child_fds[0]);
snprintf (gpgsm->output_cb.server_fd_str,
sizeof gpgsm->output_cb.server_fd_str, "%d", child_fds[1]);
snprintf (gpgsm->message_cb.server_fd_str,
sizeof gpgsm->message_cb.server_fd_str, "%d", child_fds[2]);
}
#endif
if (err)
goto leave;
err = _gpgme_getenv ("DISPLAY", &dft_display);
if (err)
goto leave;
if (dft_display)
{
if (gpgrt_asprintf (&optstr, "OPTION display=%s", dft_display) < 0)
{
free (dft_display);
err = gpg_error_from_syserror ();
goto leave;
}
free (dft_display);
err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL,
NULL, NULL, NULL);
gpgrt_free (optstr);
if (err)
goto leave;
}
err = _gpgme_getenv ("GPG_TTY", &env_tty);
if (isatty (1) || env_tty || err)
{
int rc = 0;
if (err)
goto leave;
else if (env_tty)
{
snprintf (dft_ttyname, sizeof (dft_ttyname), "%s", env_tty);
free (env_tty);
}
else
rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname));
/* Even though isatty() returns 1, ttyname_r() may fail in many
ways, e.g., when /dev/pts is not accessible under chroot. */
if (!rc)
{
if (gpgrt_asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL,
NULL, NULL, NULL);
gpgrt_free (optstr);
if (err)
goto leave;
err = _gpgme_getenv ("TERM", &dft_ttytype);
if (err)
goto leave;
if (dft_ttytype)
{
if (gpgrt_asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype)< 0)
{
free (dft_ttytype);
err = gpg_error_from_syserror ();
goto leave;
}
free (dft_ttytype);
err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL,
NULL, NULL, NULL, NULL);
gpgrt_free (optstr);
if (err)
goto leave;
}
}
}
/* Ask gpgsm to enable the audit log support. */
if (!err)
{
err = assuan_transact (gpgsm->assuan_ctx, "OPTION enable-audit-log=1",
NULL, NULL, NULL, NULL, NULL, NULL);
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
err = 0; /* This is an optional feature of gpgsm. */
}
#ifdef HAVE_W32_SYSTEM
/* Under Windows we need to use AllowSetForegroundWindow. Tell
gpgsm to tell us when it needs it. */
if (!err)
{
err = assuan_transact (gpgsm->assuan_ctx, "OPTION allow-pinentry-notify",
NULL, NULL, NULL, NULL, NULL, NULL);
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
err = 0; /* This is a new feature of gpgsm. */
}
#endif /*HAVE_W32_SYSTEM*/
#if !USE_DESCRIPTOR_PASSING
if (!err
&& (_gpgme_io_set_close_notify (gpgsm->input_cb.fd,
close_notify_handler, gpgsm)
|| _gpgme_io_set_close_notify (gpgsm->output_cb.fd,
close_notify_handler, gpgsm)
|| _gpgme_io_set_close_notify (gpgsm->message_cb.fd,
close_notify_handler, gpgsm)))
{
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
#endif
leave:
/* Close the server ends of the pipes (because of this, we must use
the stored server_fd_str in the function start). Our ends are
closed in gpgsm_release(). */
#if !USE_DESCRIPTOR_PASSING
if (gpgsm->input_cb.server_fd != -1)
_gpgme_io_close (gpgsm->input_cb.server_fd);
if (gpgsm->output_cb.server_fd != -1)
_gpgme_io_close (gpgsm->output_cb.server_fd);
if (gpgsm->message_cb.server_fd != -1)
_gpgme_io_close (gpgsm->message_cb.server_fd);
#endif
if (err)
gpgsm_release (gpgsm);
else
*engine = gpgsm;
return err;
}
/* Copy flags from CTX into the engine object. */
static void
gpgsm_set_engine_flags (void *engine, const gpgme_ctx_t ctx)
{
engine_gpgsm_t gpgsm = engine;
if (ctx->request_origin)
{
if (strlen (ctx->request_origin) + 1 > sizeof gpgsm->request_origin)
strcpy (gpgsm->request_origin, "xxx"); /* Too long - force error */
else
strcpy (gpgsm->request_origin, ctx->request_origin);
}
else
*gpgsm->request_origin = 0;
}
static gpgme_error_t
gpgsm_set_locale (void *engine, int category, const char *value)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
char *optstr;
const char *catstr;
/* FIXME: If value is NULL, we need to reset the option to default.
But we can't do this. So we error out here. GPGSM needs support
for this. */
if (0)
;
#ifdef LC_CTYPE
else if (category == LC_CTYPE)
{
catstr = "lc-ctype";
if (!value && gpgsm->lc_ctype_set)
return gpg_error (GPG_ERR_INV_VALUE);
if (value)
gpgsm->lc_ctype_set = 1;
}
#endif
#ifdef LC_MESSAGES
else if (category == LC_MESSAGES)
{
catstr = "lc-messages";
if (!value && gpgsm->lc_messages_set)
return gpg_error (GPG_ERR_INV_VALUE);
if (value)
gpgsm->lc_messages_set = 1;
}
#endif /* LC_MESSAGES */
else
return gpg_error (GPG_ERR_INV_VALUE);
/* FIXME: Reset value to default. */
if (!value)
return 0;
if (gpgrt_asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0)
err = gpg_error_from_syserror ();
else
{
err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL,
NULL, NULL, NULL, NULL);
gpgrt_free (optstr);
}
return err;
}
static gpgme_error_t
gpgsm_assuan_simple_command (engine_gpgsm_t gpgsm, const char *cmd,
engine_status_handler_t status_fnc,
void *status_fnc_value)
{
assuan_context_t ctx = gpgsm->assuan_ctx;
gpg_error_t err, cb_err;
char *line;
size_t linelen;
err = assuan_write_line (ctx, cmd);
if (err)
return err;
cb_err = 0;
do
{
err = assuan_read_line (ctx, &line, &linelen);
if (err)
break;
if (*line == '#' || !linelen)
continue;
if (linelen >= 2
&& line[0] == 'O' && line[1] == 'K'
&& (line[2] == '\0' || line[2] == ' '))
break;
else if (linelen >= 4
&& line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
&& line[3] == ' ')
{
/* We prefer a callback generated error because that one is
more related to gpgme and thus probably more important
than the error returned by the engine. */
err = cb_err? cb_err : atoi (&line[4]);
cb_err = 0;
}
else if (linelen >= 2
&& line[0] == 'S' && line[1] == ' ')
{
/* After an error from a status callback we skip all further
status lines. */
if (!cb_err)
{
char *rest;
gpgme_status_code_t r;
rest = strchr (line + 2, ' ');
if (!rest)
rest = line + linelen; /* set to an empty string */
else
*(rest++) = 0;
r = _gpgme_parse_status (line + 2);
if (gpgsm->status.mon_cb && r != GPGME_STATUS_PROGRESS)
{
/* Note that we call the monitor even if we do
* not know the status code (r < 0). */
cb_err = gpgsm->status.mon_cb (gpgsm->status.mon_cb_value,
line + 2, rest);
}
if (r >= 0 && status_fnc && !cb_err)
cb_err = status_fnc (status_fnc_value, r, rest);
}
}
else
{
/* Invalid line or INQUIRY. We can't do anything else than
to stop. As with ERR we prefer a status callback
generated error code, though. */
err = cb_err ? cb_err : gpg_error (GPG_ERR_GENERAL);
cb_err = 0;
}
}
while (!err);
/* We only want the first error from the status handler, thus we
* take the one saved in CB_ERR. */
if (!err && cb_err)
err = cb_err;
return err;
}
typedef enum { INPUT_FD, OUTPUT_FD, MESSAGE_FD } fd_type_t;
static void
gpgsm_clear_fd (engine_gpgsm_t gpgsm, fd_type_t fd_type)
{
#if !USE_DESCRIPTOR_PASSING
switch (fd_type)
{
case INPUT_FD:
_gpgme_io_close (gpgsm->input_cb.fd);
break;
case OUTPUT_FD:
_gpgme_io_close (gpgsm->output_cb.fd);
break;
case MESSAGE_FD:
_gpgme_io_close (gpgsm->message_cb.fd);
break;
}
#else
(void)gpgsm;
(void)fd_type;
#endif
}
#define COMMANDLINELEN 40
static gpgme_error_t
gpgsm_set_fd (engine_gpgsm_t gpgsm, fd_type_t fd_type, const char *opt)
{
gpg_error_t err = 0;
char line[COMMANDLINELEN];
const char *which;
iocb_data_t *iocb_data;
#if USE_DESCRIPTOR_PASSING
int dir;
#endif
switch (fd_type)
{
case INPUT_FD:
which = "INPUT";
iocb_data = &gpgsm->input_cb;
break;
case OUTPUT_FD:
which = "OUTPUT";
iocb_data = &gpgsm->output_cb;
break;
case MESSAGE_FD:
which = "MESSAGE";
iocb_data = &gpgsm->message_cb;
break;
default:
return gpg_error (GPG_ERR_INV_VALUE);
}
#if USE_DESCRIPTOR_PASSING
dir = iocb_data->dir;
/* We try to short-cut the communication by giving GPGSM direct
access to the file descriptor, rather than using a pipe. */
iocb_data->server_fd = _gpgme_data_get_fd (iocb_data->data);
if (iocb_data->server_fd < 0)
{
int fds[2];
if (_gpgme_io_pipe (fds, dir) < 0)
return gpg_error_from_syserror ();
iocb_data->fd = dir ? fds[0] : fds[1];
iocb_data->server_fd = dir ? fds[1] : fds[0];
if (_gpgme_io_set_close_notify (iocb_data->fd,
close_notify_handler, gpgsm))
{
err = gpg_error (GPG_ERR_GENERAL);
goto leave_set_fd;
}
}
err = assuan_sendfd (gpgsm->assuan_ctx, iocb_data->server_fd);
if (err)
goto leave_set_fd;
_gpgme_io_close (iocb_data->server_fd);
iocb_data->server_fd = -1;
if (opt)
snprintf (line, COMMANDLINELEN, "%s FD %s", which, opt);
else
snprintf (line, COMMANDLINELEN, "%s FD", which);
#else
if (opt)
snprintf (line, COMMANDLINELEN, "%s FD=%s %s",
which, iocb_data->server_fd_str, opt);
else
snprintf (line, COMMANDLINELEN, "%s FD=%s",
which, iocb_data->server_fd_str);
#endif
err = gpgsm_assuan_simple_command (gpgsm, line, NULL, NULL);
#if USE_DESCRIPTOR_PASSING
leave_set_fd:
if (err)
{
_gpgme_io_close (iocb_data->fd);
iocb_data->fd = -1;
if (iocb_data->server_fd != -1)
{
_gpgme_io_close (iocb_data->server_fd);
iocb_data->server_fd = -1;
}
}
#endif
return err;
}
static const char *
map_data_enc (gpgme_data_t d)
{
switch (gpgme_data_get_encoding (d))
{
case GPGME_DATA_ENCODING_NONE:
break;
case GPGME_DATA_ENCODING_BINARY:
return "--binary";
case GPGME_DATA_ENCODING_BASE64:
return "--base64";
case GPGME_DATA_ENCODING_ARMOR:
return "--armor";
default:
break;
}
return NULL;
}
static gpgme_error_t
status_handler (void *opaque, int fd)
{
struct io_cb_data *data = (struct io_cb_data *) opaque;
engine_gpgsm_t gpgsm = (engine_gpgsm_t) data->handler_value;
gpgme_error_t err = 0;
char *line;
size_t linelen;
do
{
err = assuan_read_line (gpgsm->assuan_ctx, &line, &linelen);
if (err)
{
/* Try our best to terminate the connection friendly. */
/* assuan_write_line (gpgsm->assuan_ctx, "BYE"); */
TRACE3 (DEBUG_CTX, "gpgme:status_handler", gpgsm,
"fd 0x%x: error from assuan (%d) getting status line : %s",
fd, err, gpg_strerror (err));
}
else if (linelen >= 3
&& line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
&& (line[3] == '\0' || line[3] == ' '))
{
if (line[3] == ' ')
err = atoi (&line[4]);
if (! err)
err = gpg_error (GPG_ERR_GENERAL);
TRACE2 (DEBUG_CTX, "gpgme:status_handler", gpgsm,
"fd 0x%x: ERR line - mapped to: %s",
fd, err ? gpg_strerror (err) : "ok");
/* Try our best to terminate the connection friendly. */
/* assuan_write_line (gpgsm->assuan_ctx, "BYE"); */
}
else if (linelen >= 2
&& line[0] == 'O' && line[1] == 'K'
&& (line[2] == '\0' || line[2] == ' '))
{
if (gpgsm->status.fnc)
{
char emptystring[1] = {0};
err = gpgsm->status.fnc (gpgsm->status.fnc_value,
GPGME_STATUS_EOF, emptystring);
if (gpg_err_code (err) == GPG_ERR_FALSE)
err = 0; /* Drop special error code. */
}
if (!err && gpgsm->colon.fnc && gpgsm->colon.any)
{
/* We must tell a colon function about the EOF. We do
this only when we have seen any data lines. Note
that this inlined use of colon data lines will
eventually be changed into using a regular data
channel. */
gpgsm->colon.any = 0;
err = gpgsm->colon.fnc (gpgsm->colon.fnc_value, NULL);
}
TRACE2 (DEBUG_CTX, "gpgme:status_handler", gpgsm,
"fd 0x%x: OK line - final status: %s",
fd, err ? gpg_strerror (err) : "ok");
_gpgme_io_close (gpgsm->status_cb.fd);
return err;
}
else if (linelen > 2
&& line[0] == 'D' && line[1] == ' '
&& gpgsm->colon.fnc)
{
/* We are using the colon handler even for plain inline data
- strange name for that function but for historic reasons
we keep it. */
/* FIXME We can't use this for binary data because we
assume this is a string. For the current usage of colon
output it is correct. */
char *src = line + 2;
char *end = line + linelen;
char *dst;
char **aline = &gpgsm->colon.attic.line;
int *alinelen = &gpgsm->colon.attic.linelen;
if (gpgsm->colon.attic.linesize < *alinelen + linelen + 1)
{
char *newline = realloc (*aline, *alinelen + linelen + 1);
if (!newline)
err = gpg_error_from_syserror ();
else
{
*aline = newline;
gpgsm->colon.attic.linesize = *alinelen + linelen + 1;
}
}
if (!err)
{
dst = *aline + *alinelen;
while (!err && src < end)
{
if (*src == '%' && src + 2 < end)
{
/* Handle escaped characters. */
++src;
*dst = _gpgme_hextobyte (src);
(*alinelen)++;
src += 2;
}
else
{
*dst = *src++;
(*alinelen)++;
}
if (*dst == '\n')
{
/* Terminate the pending line, pass it to the colon
handler and reset it. */
gpgsm->colon.any = 1;
if (*alinelen > 1 && *(dst - 1) == '\r')
dst--;
*dst = '\0';
/* FIXME How should we handle the return code? */
err = gpgsm->colon.fnc (gpgsm->colon.fnc_value, *aline);
if (!err)
{
dst = *aline;
*alinelen = 0;
}
}
else
dst++;
}
}
TRACE2 (DEBUG_CTX, "gpgme:status_handler", gpgsm,
"fd 0x%x: D line; final status: %s",
fd, err? gpg_strerror (err):"ok");
}
else if (linelen > 2
&& line[0] == 'D' && line[1] == ' '
&& gpgsm->inline_data)
{
char *src = line + 2;
char *end = line + linelen;
char *dst = src;
gpgme_ssize_t nwritten;
linelen = 0;
while (src < end)
{
if (*src == '%' && src + 2 < end)
{
/* Handle escaped characters. */
++src;
*dst++ = _gpgme_hextobyte (src);
src += 2;
}
else
*dst++ = *src++;
linelen++;
}
src = line + 2;
while (linelen > 0)
{
nwritten = gpgme_data_write (gpgsm->inline_data, src, linelen);
if (!nwritten || (nwritten < 0 && errno != EINTR)
|| nwritten > linelen)
{
err = gpg_error_from_syserror ();
break;
}
src += nwritten;
linelen -= nwritten;
}
TRACE2 (DEBUG_CTX, "gpgme:status_handler", gpgsm,
"fd 0x%x: D inlinedata; final status: %s",
fd, err? gpg_strerror (err):"ok");
}
else if (linelen > 2
&& line[0] == 'S' && line[1] == ' ')
{
char *rest;
gpgme_status_code_t r;
rest = strchr (line + 2, ' ');
if (!rest)
rest = line + linelen; /* set to an empty string */
else
*(rest++) = 0;
r = _gpgme_parse_status (line + 2);
+ if (gpgsm->status.mon_cb && r != GPGME_STATUS_PROGRESS)
+ {
+ /* Note that we call the monitor even if we do
+ * not know the status code (r < 0). */
+ err = gpgsm->status.mon_cb (gpgsm->status.mon_cb_value,
+ line + 2, rest);
+ }
+ else
+ err = 0;
- if (r >= 0)
+ if (r >= 0 && !err)
{
if (gpgsm->status.fnc)
{
err = gpgsm->status.fnc (gpgsm->status.fnc_value, r, rest);
if (gpg_err_code (err) == GPG_ERR_FALSE)
err = 0; /* Drop special error code. */
}
}
else
fprintf (stderr, "[UNKNOWN STATUS]%s %s", line + 2, rest);
TRACE3 (DEBUG_CTX, "gpgme:status_handler", gpgsm,
"fd 0x%x: S line (%s) - final status: %s",
fd, line+2, err? gpg_strerror (err):"ok");
}
else if (linelen >= 7
&& line[0] == 'I' && line[1] == 'N' && line[2] == 'Q'
&& line[3] == 'U' && line[4] == 'I' && line[5] == 'R'
&& line[6] == 'E'
&& (line[7] == '\0' || line[7] == ' '))
{
char *keyword = line+7;
while (*keyword == ' ')
keyword++;;
default_inq_cb (gpgsm, keyword);
assuan_write_line (gpgsm->assuan_ctx, "END");
}
}
while (!err && assuan_pending_line (gpgsm->assuan_ctx));
return err;
}
static gpgme_error_t
add_io_cb (engine_gpgsm_t gpgsm, iocb_data_t *iocbd, gpgme_io_cb_t handler)
{
gpgme_error_t err;
TRACE_BEG2 (DEBUG_ENGINE, "engine-gpgsm:add_io_cb", gpgsm,
"fd %d, dir %d", iocbd->fd, iocbd->dir);
err = (*gpgsm->io_cbs.add) (gpgsm->io_cbs.add_priv,
iocbd->fd, iocbd->dir,
handler, iocbd->data, &iocbd->tag);
if (err)
return TRACE_ERR (err);
if (!iocbd->dir)
/* FIXME Kludge around poll() problem. */
err = _gpgme_io_set_nonblocking (iocbd->fd);
return TRACE_ERR (err);
}
static gpgme_error_t
start (engine_gpgsm_t gpgsm, const char *command)
{
gpgme_error_t err;
assuan_fd_t afdlist[5];
int fdlist[5];
int nfds;
int i;
if (*gpgsm->request_origin)
{
char *cmd;
cmd = _gpgme_strconcat ("OPTION request-origin=",
gpgsm->request_origin, NULL);
if (!cmd)
return gpg_error_from_syserror ();
err = gpgsm_assuan_simple_command (gpgsm, cmd, NULL, NULL);
free (cmd);
if (err && gpg_err_code (err) != GPG_ERR_UNKNOWN_OPTION)
return err;
}
/* We need to know the fd used by assuan for reads. We do this by
using the assumption that the first returned fd from
assuan_get_active_fds() is always this one. */
nfds = assuan_get_active_fds (gpgsm->assuan_ctx, 0 /* read fds */,
afdlist, DIM (afdlist));
if (nfds < 1)
return gpg_error (GPG_ERR_GENERAL); /* FIXME */
/* For now... */
for (i = 0; i < nfds; i++)
fdlist[i] = (int) afdlist[i];
/* We "duplicate" the file descriptor, so we can close it here (we
can't close fdlist[0], as that is closed by libassuan, and
closing it here might cause libassuan to close some unrelated FD
later). Alternatively, we could special case status_fd and
register/unregister it manually as needed, but this increases
code duplication and is more complicated as we can not use the
close notifications etc. A third alternative would be to let
Assuan know that we closed the FD, but that complicates the
Assuan interface. */
gpgsm->status_cb.fd = _gpgme_io_dup (fdlist[0]);
if (gpgsm->status_cb.fd < 0)
return gpg_error_from_syserror ();
if (_gpgme_io_set_close_notify (gpgsm->status_cb.fd,
close_notify_handler, gpgsm))
{
_gpgme_io_close (gpgsm->status_cb.fd);
gpgsm->status_cb.fd = -1;
return gpg_error (GPG_ERR_GENERAL);
}
err = add_io_cb (gpgsm, &gpgsm->status_cb, status_handler);
if (!err && gpgsm->input_cb.fd != -1)
err = add_io_cb (gpgsm, &gpgsm->input_cb, _gpgme_data_outbound_handler);
if (!err && gpgsm->output_cb.fd != -1)
err = add_io_cb (gpgsm, &gpgsm->output_cb, _gpgme_data_inbound_handler);
if (!err && gpgsm->message_cb.fd != -1)
err = add_io_cb (gpgsm, &gpgsm->message_cb, _gpgme_data_outbound_handler);
if (!err)
err = assuan_write_line (gpgsm->assuan_ctx, command);
if (!err)
gpgsm_io_event (gpgsm, GPGME_EVENT_START, NULL);
return err;
}
#if USE_DESCRIPTOR_PASSING
static gpgme_error_t
gpgsm_reset (void *engine)
{
engine_gpgsm_t gpgsm = engine;
/* IF we have an active connection we must send a reset because we
need to reset the list of signers. Note that RESET does not
reset OPTION commands. */
return (gpgsm->assuan_ctx
? gpgsm_assuan_simple_command (gpgsm, "RESET", NULL, NULL)
: 0);
}
#endif
static gpgme_error_t
gpgsm_decrypt (void *engine,
gpgme_decrypt_flags_t flags,
gpgme_data_t ciph, gpgme_data_t plain,
int export_session_key, const char *override_session_key,
int auto_key_retrieve)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
(void)flags;
/* gpgsm is not capable of exporting session keys right now, so we
* will ignore this if requested. */
(void)export_session_key;
(void)override_session_key;
/* --auto-key-retrieve is also not supported. */
(void)auto_key_retrieve;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
gpgsm->input_cb.data = ciph;
err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
if (err)
return gpg_error (GPG_ERR_GENERAL); /* FIXME */
gpgsm->output_cb.data = plain;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0);
if (err)
return gpg_error (GPG_ERR_GENERAL); /* FIXME */
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (engine, "DECRYPT");
return err;
}
static gpgme_error_t
gpgsm_delete (void *engine, gpgme_key_t key, unsigned int flags)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
char *fpr = key->subkeys ? key->subkeys->fpr : NULL;
char *linep = fpr;
char *line;
int length = 8; /* "DELKEYS " */
(void)flags;
if (!fpr)
return gpg_error (GPG_ERR_INV_VALUE);
while (*linep)
{
length++;
if (*linep == '%' || *linep == ' ' || *linep == '+')
length += 2;
linep++;
}
length++;
line = malloc (length);
if (!line)
return gpg_error_from_syserror ();
strcpy (line, "DELKEYS ");
linep = &line[8];
while (*fpr)
{
switch (*fpr)
{
case '%':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = '5';
break;
case ' ':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = '0';
break;
case '+':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = 'B';
break;
default:
*(linep++) = *fpr;
break;
}
fpr++;
}
*linep = '\0';
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, line);
free (line);
return err;
}
static gpgme_error_t
set_recipients (engine_gpgsm_t gpgsm, gpgme_key_t recp[])
{
gpgme_error_t err = 0;
char *line;
int linelen;
int invalid_recipients = 0;
int i;
linelen = 10 + 40 + 1; /* "RECIPIENT " + guess + '\0'. */
line = malloc (10 + 40 + 1);
if (!line)
return gpg_error_from_syserror ();
strcpy (line, "RECIPIENT ");
for (i =0; !err && recp[i]; i++)
{
char *fpr;
int newlen;
if (!recp[i]->subkeys || !recp[i]->subkeys->fpr)
{
invalid_recipients++;
continue;
}
fpr = recp[i]->subkeys->fpr;
newlen = 11 + strlen (fpr);
if (linelen < newlen)
{
char *newline = realloc (line, newlen);
if (! newline)
{
int saved_err = gpg_error_from_syserror ();
free (line);
return saved_err;
}
line = newline;
linelen = newlen;
}
strcpy (&line[10], fpr);
err = gpgsm_assuan_simple_command (gpgsm, line, gpgsm->status.fnc,
gpgsm->status.fnc_value);
/* FIXME: This requires more work. */
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
invalid_recipients++;
else if (err)
{
free (line);
return err;
}
}
free (line);
return gpg_error (invalid_recipients
? GPG_ERR_UNUSABLE_PUBKEY : GPG_ERR_NO_ERROR);
}
/* Take recipients from the LF delimited STRING and send RECIPIENT
* commands to gpgsm. */
static gpgme_error_t
set_recipients_from_string (engine_gpgsm_t gpgsm, const char *string)
{
gpg_error_t err = 0;
char *line = NULL;
int no_pubkey = 0;
const char *s;
int n;
for (;;)
{
while (*string == ' ' || *string == '\t')
string++;
if (!*string)
break;
s = strchr (string, '\n');
if (s)
n = s - string;
else
n = strlen (string);
while (n && (string[n-1] == ' ' || string[n-1] == '\t'))
n--;
gpgrt_free (line);
if (gpgrt_asprintf (&line, "RECIPIENT %.*s", n, string) < 0)
{
err = gpg_error_from_syserror ();
break;
}
string += n + !!s;
err = gpgsm_assuan_simple_command (gpgsm, line, gpgsm->status.fnc,
gpgsm->status.fnc_value);
/* Fixme: Improve error reporting. */
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
no_pubkey++;
else if (err)
break;
}
gpgrt_free (line);
return err? err : no_pubkey? gpg_error (GPG_ERR_NO_PUBKEY) : 0;
}
static gpgme_error_t
gpgsm_encrypt (void *engine, gpgme_key_t recp[], const char *recpstring,
gpgme_encrypt_flags_t flags,
gpgme_data_t plain, gpgme_data_t ciph, int use_armor)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
if (!recp)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
if ((flags & GPGME_ENCRYPT_NO_ENCRYPT_TO))
{
err = gpgsm_assuan_simple_command (gpgsm,
"OPTION no-encrypt-to", NULL, NULL);
if (err)
return err;
}
gpgsm->input_cb.data = plain;
err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
if (err)
return err;
gpgsm->output_cb.data = ciph;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor"
: map_data_enc (gpgsm->output_cb.data));
if (err)
return err;
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
if (!recp && recpstring)
err = set_recipients_from_string (gpgsm, recpstring);
else
err = set_recipients (gpgsm, recp);
if (!err)
err = start (gpgsm, "ENCRYPT");
return err;
}
static gpgme_error_t
gpgsm_export (void *engine, const char *pattern, gpgme_export_mode_t mode,
gpgme_data_t keydata, int use_armor)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err = 0;
char *cmd;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
if (!pattern)
pattern = "";
cmd = malloc (7 + 9 + 9 + strlen (pattern) + 1);
if (!cmd)
return gpg_error_from_syserror ();
strcpy (cmd, "EXPORT ");
if ((mode & GPGME_EXPORT_MODE_SECRET))
{
strcat (cmd, "--secret ");
if ((mode & GPGME_EXPORT_MODE_RAW))
strcat (cmd, "--raw ");
else if ((mode & GPGME_EXPORT_MODE_PKCS12))
strcat (cmd, "--pkcs12 ");
}
strcat (cmd, pattern);
gpgsm->output_cb.data = keydata;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor"
: map_data_enc (gpgsm->output_cb.data));
if (err)
return err;
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, cmd);
free (cmd);
return err;
}
static gpgme_error_t
gpgsm_export_ext (void *engine, const char *pattern[], gpgme_export_mode_t mode,
gpgme_data_t keydata, int use_armor)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err = 0;
char *line;
/* Length is "EXPORT " + "--secret " + "--pkcs12 " + p + '\0'. */
int length = 7 + 9 + 9 + 1;
char *linep;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
if (pattern && *pattern)
{
const char **pat = pattern;
while (*pat)
{
const char *patlet = *pat;
while (*patlet)
{
length++;
if (*patlet == '%' || *patlet == ' ' || *patlet == '+')
length += 2;
patlet++;
}
pat++;
length++;
}
}
line = malloc (length);
if (!line)
return gpg_error_from_syserror ();
strcpy (line, "EXPORT ");
if ((mode & GPGME_EXPORT_MODE_SECRET))
{
strcat (line, "--secret ");
if ((mode & GPGME_EXPORT_MODE_RAW))
strcat (line, "--raw ");
else if ((mode & GPGME_EXPORT_MODE_PKCS12))
strcat (line, "--pkcs12 ");
}
linep = &line[strlen (line)];
if (pattern && *pattern)
{
while (*pattern)
{
const char *patlet = *pattern;
while (*patlet)
{
switch (*patlet)
{
case '%':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = '5';
break;
case ' ':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = '0';
break;
case '+':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = 'B';
break;
default:
*(linep++) = *patlet;
break;
}
patlet++;
}
pattern++;
if (*pattern)
*linep++ = ' ';
}
}
*linep = '\0';
gpgsm->output_cb.data = keydata;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor"
: map_data_enc (gpgsm->output_cb.data));
if (err)
return err;
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, line);
free (line);
return err;
}
static gpgme_error_t
gpgsm_genkey (void *engine,
const char *userid, const char *algo,
unsigned long reserved, unsigned long expires,
gpgme_key_t key, unsigned int flags,
gpgme_data_t help_data, unsigned int extraflags,
gpgme_data_t pubkey, gpgme_data_t seckey)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
(void)reserved;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
if (help_data)
{
if (!pubkey || seckey)
return gpg_error (GPG_ERR_INV_VALUE);
gpgsm->input_cb.data = help_data;
err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
if (err)
return err;
gpgsm->output_cb.data = pubkey;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD,
(extraflags & GENKEY_EXTRAFLAG_ARMOR)? "--armor"
: map_data_enc (gpgsm->output_cb.data));
if (err)
return err;
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, "GENKEY");
return err;
}
(void)userid;
(void)algo;
(void)expires;
(void)key;
(void)flags;
/* The new interface has not yet been implemented, */
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
static gpgme_error_t
gpgsm_import (void *engine, gpgme_data_t keydata, gpgme_key_t *keyarray)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
gpgme_data_encoding_t dataenc;
int idx;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
if (keydata && keyarray)
return gpg_error (GPG_ERR_INV_VALUE); /* Only one is allowed. */
dataenc = gpgme_data_get_encoding (keydata);
if (keyarray)
{
size_t buflen;
char *buffer, *p;
/* Fist check whether the engine already features the
--re-import option. */
err = gpgsm_assuan_simple_command
(gpgsm, "GETINFO cmd_has_option IMPORT re-import", NULL, NULL);
if (err)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
/* Create an internal data object with a list of all
fingerprints. The data object and its memory (to avoid an
extra copy by gpgme_data_new_from_mem) are stored in two
variables which are released by the close_notify_handler. */
for (idx=0, buflen=0; keyarray[idx]; idx++)
{
if (keyarray[idx]->protocol == GPGME_PROTOCOL_CMS
&& keyarray[idx]->subkeys
&& keyarray[idx]->subkeys->fpr
&& *keyarray[idx]->subkeys->fpr)
buflen += strlen (keyarray[idx]->subkeys->fpr) + 1;
}
/* Allocate a bufer with extra space for the trailing Nul
introduced by the use of stpcpy. */
buffer = malloc (buflen+1);
if (!buffer)
return gpg_error_from_syserror ();
for (idx=0, p = buffer; keyarray[idx]; idx++)
{
if (keyarray[idx]->protocol == GPGME_PROTOCOL_CMS
&& keyarray[idx]->subkeys
&& keyarray[idx]->subkeys->fpr
&& *keyarray[idx]->subkeys->fpr)
p = stpcpy (stpcpy (p, keyarray[idx]->subkeys->fpr), "\n");
}
err = gpgme_data_new_from_mem (&gpgsm->input_helper_data,
buffer, buflen, 0);
if (err)
{
free (buffer);
return err;
}
gpgsm->input_helper_memory = buffer;
gpgsm->input_cb.data = gpgsm->input_helper_data;
err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
if (err)
{
gpgme_data_release (gpgsm->input_helper_data);
gpgsm->input_helper_data = NULL;
free (gpgsm->input_helper_memory);
gpgsm->input_helper_memory = NULL;
return err;
}
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
return start (gpgsm, "IMPORT --re-import");
}
else if (dataenc == GPGME_DATA_ENCODING_URL
|| dataenc == GPGME_DATA_ENCODING_URL0
|| dataenc == GPGME_DATA_ENCODING_URLESC)
{
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
else
{
gpgsm->input_cb.data = keydata;
err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
if (err)
return err;
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
return start (gpgsm, "IMPORT");
}
}
static gpgme_error_t
gpgsm_keylist (void *engine, const char *pattern, int secret_only,
gpgme_keylist_mode_t mode, int engine_flags)
{
engine_gpgsm_t gpgsm = engine;
char *line;
gpgme_error_t err;
int list_mode = 0;
if (mode & GPGME_KEYLIST_MODE_LOCAL)
list_mode |= 1;
if (mode & GPGME_KEYLIST_MODE_EXTERN)
list_mode |= 2;
if (!pattern)
pattern = "";
/* Hack to make sure that the agent is started. Only if the agent
has been started an application may connect to the agent via
GPGME_PROTOCOL_ASSUAN - for example to look for smartcards. We
do this only if a secret key listing has been requested. In
general this is not needed because a secret key listing starts
the agent. However on a fresh installation no public keys are
available and thus there is no need for gpgsm to ask the agent
whether a secret key exists for the public key. */
if (secret_only || (mode & GPGME_KEYLIST_MODE_WITH_SECRET))
gpgsm_assuan_simple_command (gpgsm, "GETINFO agent-check", NULL, NULL);
/* Always send list-mode option because RESET does not reset it. */
if (gpgrt_asprintf (&line, "OPTION list-mode=%d", (list_mode & 3)) < 0)
return gpg_error_from_syserror ();
err = gpgsm_assuan_simple_command (gpgsm, line, NULL, NULL);
gpgrt_free (line);
if (err)
return err;
/* Always send key validation because RESET does not reset it. */
/* Use the validation mode if requested. We don't check for an error
yet because this is a pretty fresh gpgsm features. */
gpgsm_assuan_simple_command (gpgsm,
(mode & GPGME_KEYLIST_MODE_VALIDATE)?
"OPTION with-validation=1":
"OPTION with-validation=0" ,
NULL, NULL);
/* Include the ephemeral keys if requested. We don't check for an error
yet because this is a pretty fresh gpgsm features. */
gpgsm_assuan_simple_command (gpgsm,
(mode & GPGME_KEYLIST_MODE_EPHEMERAL)?
"OPTION with-ephemeral-keys=1":
"OPTION with-ephemeral-keys=0" ,
NULL, NULL);
gpgsm_assuan_simple_command (gpgsm,
(mode & GPGME_KEYLIST_MODE_WITH_SECRET)?
"OPTION with-secret=1":
"OPTION with-secret=0" ,
NULL, NULL);
gpgsm_assuan_simple_command (gpgsm,
(engine_flags & GPGME_ENGINE_FLAG_OFFLINE)?
"OPTION offline=1":
"OPTION offline=0" ,
NULL, NULL);
/* Length is "LISTSECRETKEYS " + p + '\0'. */
line = malloc (15 + strlen (pattern) + 1);
if (!line)
return gpg_error_from_syserror ();
if (secret_only)
{
strcpy (line, "LISTSECRETKEYS ");
strcpy (&line[15], pattern);
}
else
{
strcpy (line, "LISTKEYS ");
strcpy (&line[9], pattern);
}
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, line);
free (line);
return err;
}
static gpgme_error_t
gpgsm_keylist_ext (void *engine, const char *pattern[], int secret_only,
int reserved, gpgme_keylist_mode_t mode, int engine_flags)
{
engine_gpgsm_t gpgsm = engine;
char *line;
gpgme_error_t err;
/* Length is "LISTSECRETKEYS " + p + '\0'. */
int length = 15 + 1;
char *linep;
int any_pattern = 0;
int list_mode = 0;
if (reserved)
return gpg_error (GPG_ERR_INV_VALUE);
if (mode & GPGME_KEYLIST_MODE_LOCAL)
list_mode |= 1;
if (mode & GPGME_KEYLIST_MODE_EXTERN)
list_mode |= 2;
/* Always send list-mode option because RESET does not reset it. */
if (gpgrt_asprintf (&line, "OPTION list-mode=%d", (list_mode & 3)) < 0)
return gpg_error_from_syserror ();
err = gpgsm_assuan_simple_command (gpgsm, line, NULL, NULL);
gpgrt_free (line);
if (err)
return err;
/* Always send key validation because RESET does not reset it. */
/* Use the validation mode if required. We don't check for an error
yet because this is a pretty fresh gpgsm features. */
gpgsm_assuan_simple_command (gpgsm,
(mode & GPGME_KEYLIST_MODE_VALIDATE)?
"OPTION with-validation=1":
"OPTION with-validation=0" ,
NULL, NULL);
gpgsm_assuan_simple_command (gpgsm,
(mode & GPGME_KEYLIST_MODE_WITH_SECRET)?
"OPTION with-secret=1":
"OPTION with-secret=0" ,
NULL, NULL);
gpgsm_assuan_simple_command (gpgsm,
(engine_flags & GPGME_ENGINE_FLAG_OFFLINE)?
"OPTION offline=1":
"OPTION offline=0" ,
NULL, NULL);
if (pattern && *pattern)
{
const char **pat = pattern;
while (*pat)
{
const char *patlet = *pat;
while (*patlet)
{
length++;
if (*patlet == '%' || *patlet == ' ' || *patlet == '+')
length += 2;
patlet++;
}
pat++;
length++;
}
}
line = malloc (length);
if (!line)
return gpg_error_from_syserror ();
if (secret_only)
{
strcpy (line, "LISTSECRETKEYS ");
linep = &line[15];
}
else
{
strcpy (line, "LISTKEYS ");
linep = &line[9];
}
if (pattern && *pattern)
{
while (*pattern)
{
const char *patlet = *pattern;
while (*patlet)
{
switch (*patlet)
{
case '%':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = '5';
break;
case ' ':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = '0';
break;
case '+':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = 'B';
break;
default:
*(linep++) = *patlet;
break;
}
patlet++;
}
any_pattern = 1;
*linep++ = ' ';
pattern++;
}
}
if (any_pattern)
linep--;
*linep = '\0';
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, line);
free (line);
return err;
}
static gpgme_error_t
gpgsm_sign (void *engine, gpgme_data_t in, gpgme_data_t out,
gpgme_sig_mode_t mode, int use_armor, int use_textmode,
int include_certs, gpgme_ctx_t ctx /* FIXME */)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
char *assuan_cmd;
int i;
gpgme_key_t key;
(void)use_textmode;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
/* FIXME: This does not work as RESET does not reset it so we can't
revert back to default. */
if (include_certs != GPGME_INCLUDE_CERTS_DEFAULT)
{
/* FIXME: Make sure that if we run multiple operations, that we
can reset any previously set value in case the default is
requested. */
if (gpgrt_asprintf (&assuan_cmd,
"OPTION include-certs %i", include_certs) < 0)
return gpg_error_from_syserror ();
err = gpgsm_assuan_simple_command (gpgsm, assuan_cmd, NULL, NULL);
gpgrt_free (assuan_cmd);
if (err)
return err;
}
for (i = 0; (key = gpgme_signers_enum (ctx, i)); i++)
{
const char *s = key->subkeys ? key->subkeys->fpr : NULL;
if (s && strlen (s) < 80)
{
char buf[100];
strcpy (stpcpy (buf, "SIGNER "), s);
err = gpgsm_assuan_simple_command (gpgsm, buf,
gpgsm->status.fnc,
gpgsm->status.fnc_value);
}
else
err = gpg_error (GPG_ERR_INV_VALUE);
gpgme_key_unref (key);
if (err)
return err;
}
gpgsm->input_cb.data = in;
err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
if (err)
return err;
gpgsm->output_cb.data = out;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor"
: map_data_enc (gpgsm->output_cb.data));
if (err)
return err;
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, mode == GPGME_SIG_MODE_DETACH
? "SIGN --detached" : "SIGN");
return err;
}
static gpgme_error_t
gpgsm_verify (void *engine, gpgme_data_t sig, gpgme_data_t signed_text,
gpgme_data_t plaintext, gpgme_ctx_t ctx)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
(void)ctx;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
gpgsm->input_cb.data = sig;
err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
if (err)
return err;
if (plaintext)
{
/* Normal or cleartext signature. */
gpgsm->output_cb.data = plaintext;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
}
else
{
/* Detached signature. */
gpgsm->message_cb.data = signed_text;
err = gpgsm_set_fd (gpgsm, MESSAGE_FD, 0);
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
}
gpgsm->inline_data = NULL;
if (!err)
err = start (gpgsm, "VERIFY");
return err;
}
/* Send the GETAUDITLOG command. The result is saved to a gpgme data
object. */
static gpgme_error_t
gpgsm_getauditlog (void *engine, gpgme_data_t output, unsigned int flags)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err = 0;
if (!gpgsm || !output)
return gpg_error (GPG_ERR_INV_VALUE);
#if USE_DESCRIPTOR_PASSING
gpgsm->output_cb.data = output;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0);
if (err)
return err;
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
# define CMD "GETAUDITLOG"
#else
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = output;
# define CMD "GETAUDITLOG --data"
#endif
err = start (gpgsm, (flags & GPGME_AUDITLOG_HTML)? CMD " --html" : CMD);
return err;
}
/* This sets a status callback for monitoring status lines before they
* are passed to a caller set handler. */
static void
gpgsm_set_status_cb (void *engine, gpgme_status_cb_t cb, void *cb_value)
{
engine_gpgsm_t gpgsm = engine;
gpgsm->status.mon_cb = cb;
gpgsm->status.mon_cb_value = cb_value;
}
static void
gpgsm_set_status_handler (void *engine, engine_status_handler_t fnc,
void *fnc_value)
{
engine_gpgsm_t gpgsm = engine;
gpgsm->status.fnc = fnc;
gpgsm->status.fnc_value = fnc_value;
}
static gpgme_error_t
gpgsm_set_colon_line_handler (void *engine, engine_colon_line_handler_t fnc,
void *fnc_value)
{
engine_gpgsm_t gpgsm = engine;
gpgsm->colon.fnc = fnc;
gpgsm->colon.fnc_value = fnc_value;
gpgsm->colon.any = 0;
return 0;
}
static void
gpgsm_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
{
engine_gpgsm_t gpgsm = engine;
gpgsm->io_cbs = *io_cbs;
}
static void
gpgsm_io_event (void *engine, gpgme_event_io_t type, void *type_data)
{
engine_gpgsm_t gpgsm = engine;
TRACE3 (DEBUG_ENGINE, "gpgme:gpgsm_io_event", gpgsm,
"event %p, type %d, type_data %p",
gpgsm->io_cbs.event, type, type_data);
if (gpgsm->io_cbs.event)
(*gpgsm->io_cbs.event) (gpgsm->io_cbs.event_priv, type, type_data);
}
static gpgme_error_t
gpgsm_passwd (void *engine, gpgme_key_t key, unsigned int flags)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
char *line;
(void)flags;
if (!key || !key->subkeys || !key->subkeys->fpr)
return gpg_error (GPG_ERR_INV_CERT_OBJ);
if (gpgrt_asprintf (&line, "PASSWD -- %s", key->subkeys->fpr) < 0)
return gpg_error_from_syserror ();
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, line);
gpgrt_free (line);
return err;
}
struct engine_ops _gpgme_engine_ops_gpgsm =
{
/* Static functions. */
_gpgme_get_default_gpgsm_name,
NULL,
gpgsm_get_version,
gpgsm_get_req_version,
gpgsm_new,
/* Member functions. */
gpgsm_release,
#if USE_DESCRIPTOR_PASSING
gpgsm_reset,
#else
NULL, /* reset */
#endif
gpgsm_set_status_cb,
gpgsm_set_status_handler,
NULL, /* set_command_handler */
gpgsm_set_colon_line_handler,
gpgsm_set_locale,
NULL, /* set_protocol */
gpgsm_set_engine_flags,
gpgsm_decrypt,
gpgsm_delete, /* decrypt_verify */
NULL, /* edit */
gpgsm_encrypt,
NULL, /* encrypt_sign */
gpgsm_export,
gpgsm_export_ext,
gpgsm_genkey,
gpgsm_import,
gpgsm_keylist,
gpgsm_keylist_ext,
NULL, /* keylist_data */
NULL, /* keysign */
NULL, /* tofu_policy */
gpgsm_sign,
NULL, /* trustlist */
gpgsm_verify,
gpgsm_getauditlog,
NULL, /* opassuan_transact */
NULL, /* conf_load */
NULL, /* conf_save */
NULL, /* conf_dir */
NULL, /* query_swdb */
gpgsm_set_io_cbs,
gpgsm_io_event,
gpgsm_cancel,
NULL, /* cancel_op */
gpgsm_passwd,
NULL, /* set_pinentry_mode */
NULL /* opspawn */
};
diff --git a/src/gpgme-json.c b/src/gpgme-json.c
index f1e9f256..fb5f149b 100644
--- a/src/gpgme-json.c
+++ b/src/gpgme-json.c
@@ -1,1772 +1,1778 @@
/* gpgme-json.c - JSON based interface to gpgme (server)
* Copyright (C) 2018 g10 Code GmbH
*
* This file is part of GPGME.
*
* GPGME is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* GPGME is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, see .
* SPDX-License-Identifier: LGPL-2.1+
*/
/* This is tool implements the Native Messaging protocol of web
* browsers and provides the server part of it. A Javascript based
* client can be found in lang/javascript.
*/
#include
#include
#include
#include
#include
#ifdef HAVE_LOCALE_H
#include
#endif
#include
#include
#define GPGRT_ENABLE_ES_MACROS 1
#define GPGRT_ENABLE_LOG_MACROS 1
#define GPGRT_ENABLE_ARGPARSE_MACROS 1
#include "gpgme.h"
#include "cJSON.h"
#if GPGRT_VERSION_NUMBER < 0x011c00 /* 1.28 */
int main (void){fputs ("Build with Libgpg-error >= 1.28!\n", stderr);return 1;}
#else /* libgpg-error >= 1.28 */
/* We don't allow a request with more than 64 MiB. */
#define MAX_REQUEST_SIZE (64 * 1024 * 1024)
/* Minimal, default and maximum chunk size for returned data. The
* first chunk is returned directly. If the "more" flag is also
* returned, a "getmore" command needs to be used to get the next
* chunk. Right now this value covers just the value of the "data"
* element; so to cover for the other returned objects this values
* needs to be lower than the maximum allowed size of the browser. */
#define MIN_REPLY_CHUNK_SIZE 512
#define DEF_REPLY_CHUNK_SIZE (512 * 1024)
#define MAX_REPLY_CHUNK_SIZE (10 * 1024 * 1024)
static void xoutofcore (const char *type) GPGRT_ATTR_NORETURN;
static cjson_t error_object_v (cjson_t json, const char *message,
va_list arg_ptr) GPGRT_ATTR_PRINTF(2,0);
static cjson_t error_object (cjson_t json, const char *message,
...) GPGRT_ATTR_PRINTF(2,3);
static char *error_object_string (const char *message,
...) GPGRT_ATTR_PRINTF(1,2);
/* True if interactive mode is active. */
static int opt_interactive;
/* True is debug mode is active. */
static int opt_debug;
/* Pending data to be returned by a getmore command. */
static struct
{
char *buffer; /* Malloced data or NULL if not used. */
size_t length; /* Length of that data. */
size_t written; /* # of already written bytes from BUFFER. */
const char *type;/* The "type" of the data. */
int base64; /* The "base64" flag of the data. */
} pending_data;
/*
* Helper functions and macros
*/
#define xtrymalloc(a) gpgrt_malloc ((a))
#define xtrystrdup(a) gpgrt_strdup ((a))
#define xmalloc(a) ({ \
void *_r = gpgrt_malloc ((a)); \
if (!_r) \
xoutofcore ("malloc"); \
_r; })
#define xcalloc(a,b) ({ \
void *_r = gpgrt_calloc ((a), (b)); \
if (!_r) \
xoutofcore ("calloc"); \
_r; })
#define xstrdup(a) ({ \
char *_r = gpgrt_strdup ((a)); \
if (!_r) \
xoutofcore ("strdup"); \
_r; })
#define xstrconcat(a, ...) ({ \
char *_r = gpgrt_strconcat ((a), __VA_ARGS__); \
if (!_r) \
xoutofcore ("strconcat"); \
_r; })
#define xfree(a) gpgrt_free ((a))
#define spacep(p) (*(p) == ' ' || *(p) == '\t')
#ifndef HAVE_STPCPY
static GPGRT_INLINE char *
_my_stpcpy (char *a, const char *b)
{
while (*b)
*a++ = *b++;
*a = 0;
return a;
}
#define stpcpy(a,b) _my_stpcpy ((a), (b))
#endif /*!HAVE_STPCPY*/
static void
xoutofcore (const char *type)
{
gpg_error_t err = gpg_error_from_syserror ();
log_error ("%s failed: %s\n", type, gpg_strerror (err));
exit (2);
}
/* Call cJSON_CreateObject but terminate in case of an error. */
static cjson_t
xjson_CreateObject (void)
{
cjson_t json = cJSON_CreateObject ();
if (!json)
xoutofcore ("cJSON_CreateObject");
return json;
}
/* Wrapper around cJSON_AddStringToObject which returns an gpg-error
* code instead of the NULL or the new object. */
static gpg_error_t
cjson_AddStringToObject (cjson_t object, const char *name, const char *string)
{
if (!cJSON_AddStringToObject (object, name, string))
return gpg_error_from_syserror ();
return 0;
}
/* Same as cjson_AddStringToObject but prints an error message and
* terminates the process. */
static void
xjson_AddStringToObject (cjson_t object, const char *name, const char *string)
{
if (!cJSON_AddStringToObject (object, name, string))
xoutofcore ("cJSON_AddStringToObject");
}
/* Wrapper around cJSON_AddBoolToObject which terminates the process
* in case of an error. */
static void
xjson_AddBoolToObject (cjson_t object, const char *name, int abool)
{
if (!cJSON_AddBoolToObject (object, name, abool))
xoutofcore ("cJSON_AddStringToObject");
return ;
}
/* This is similar to cJSON_AddStringToObject but takes (DATA,
* DATALEN) and adds it under NAME as a base 64 encoded string to
* OBJECT. */
static gpg_error_t
add_base64_to_object (cjson_t object, const char *name,
const void *data, size_t datalen)
{
#if GPGRT_VERSION_NUMBER < 0x011d00 /* 1.29 */
return gpg_error (GPG_ERR_NOT_SUPPORTED);
#else
gpg_err_code_t err;
estream_t fp = NULL;
gpgrt_b64state_t state = NULL;
cjson_t j_str = NULL;
void *buffer = NULL;
fp = es_fopenmem (0, "rwb");
if (!fp)
{
err = gpg_err_code_from_syserror ();
goto leave;
}
state = gpgrt_b64enc_start (fp, "");
if (!state)
{
err = gpg_err_code_from_syserror ();
goto leave;
}
err = gpgrt_b64enc_write (state, data, datalen);
if (err)
goto leave;
err = gpgrt_b64enc_finish (state);
state = NULL;
if (err)
return err;
es_fputc (0, fp);
if (es_fclose_snatch (fp, &buffer, NULL))
{
fp = NULL;
err = gpg_error_from_syserror ();
goto leave;
}
fp = NULL;
j_str = cJSON_CreateStringConvey (buffer);
if (!j_str)
{
err = gpg_error_from_syserror ();
goto leave;
}
buffer = NULL;
if (!cJSON_AddItemToObject (object, name, j_str))
{
err = gpg_error_from_syserror ();
cJSON_Delete (j_str);
j_str = NULL;
goto leave;
}
j_str = NULL;
leave:
xfree (buffer);
cJSON_Delete (j_str);
gpgrt_b64enc_finish (state);
es_fclose (fp);
return err;
#endif
}
/* Create a JSON error object. If JSON is not NULL the error message
* is appended to that object. An existing "type" item will be replaced. */
static cjson_t
error_object_v (cjson_t json, const char *message, va_list arg_ptr)
{
cjson_t response, j_tmp;
char *msg;
msg = gpgrt_vbsprintf (message, arg_ptr);
if (!msg)
xoutofcore ("error_object");
response = json? json : xjson_CreateObject ();
if (!(j_tmp = cJSON_GetObjectItem (response, "type")))
xjson_AddStringToObject (response, "type", "error");
else /* Replace existing "type". */
{
j_tmp = cJSON_CreateString ("error");
if (!j_tmp)
xoutofcore ("cJSON_CreateString");
cJSON_ReplaceItemInObject (response, "type", j_tmp);
}
xjson_AddStringToObject (response, "msg", msg);
xfree (msg);
return response;
}
/* Call cJSON_Print but terminate in case of an error. */
static char *
xjson_Print (cjson_t object)
{
char *buf;
buf = cJSON_Print (object);
if (!buf)
xoutofcore ("cJSON_Print");
return buf;
}
static cjson_t
error_object (cjson_t json, const char *message, ...)
{
cjson_t response;
va_list arg_ptr;
va_start (arg_ptr, message);
response = error_object_v (json, message, arg_ptr);
va_end (arg_ptr);
return response;
}
static char *
error_object_string (const char *message, ...)
{
cjson_t response;
va_list arg_ptr;
char *msg;
va_start (arg_ptr, message);
response = error_object_v (NULL, message, arg_ptr);
va_end (arg_ptr);
msg = xjson_Print (response);
cJSON_Delete (response);
return msg;
}
/* Get the boolean property NAME from the JSON object and store true
* or valse at R_VALUE. If the name is unknown the value of DEF_VALUE
* is returned. If the type of the value is not boolean,
* GPG_ERR_INV_VALUE is returned and R_VALUE set to DEF_VALUE. */
static gpg_error_t
get_boolean_flag (cjson_t json, const char *name, int def_value, int *r_value)
{
cjson_t j_item;
j_item = cJSON_GetObjectItem (json, name);
if (!j_item)
*r_value = def_value;
else if (cjson_is_true (j_item))
*r_value = 1;
else if (cjson_is_false (j_item))
*r_value = 0;
else
{
*r_value = def_value;
return gpg_error (GPG_ERR_INV_VALUE);
}
return 0;
}
/* Get the boolean property PROTOCOL from the JSON object and store
* its value at R_PROTOCOL. The default is OpenPGP. */
static gpg_error_t
get_protocol (cjson_t json, gpgme_protocol_t *r_protocol)
{
cjson_t j_item;
*r_protocol = GPGME_PROTOCOL_OpenPGP;
j_item = cJSON_GetObjectItem (json, "protocol");
if (!j_item)
;
else if (!cjson_is_string (j_item))
return gpg_error (GPG_ERR_INV_VALUE);
else if (!strcmp(j_item->valuestring, "openpgp"))
;
else if (!strcmp(j_item->valuestring, "cms"))
*r_protocol = GPGME_PROTOCOL_CMS;
else
return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
return 0;
}
/* Get the chunksize from JSON and store it at R_CHUNKSIZE. */
static gpg_error_t
get_chunksize (cjson_t json, size_t *r_chunksize)
{
cjson_t j_item;
*r_chunksize = DEF_REPLY_CHUNK_SIZE;
j_item = cJSON_GetObjectItem (json, "chunksize");
if (!j_item)
;
else if (!cjson_is_number (j_item))
return gpg_error (GPG_ERR_INV_VALUE);
else if ((size_t)j_item->valueint < MIN_REPLY_CHUNK_SIZE)
*r_chunksize = MIN_REPLY_CHUNK_SIZE;
else if ((size_t)j_item->valueint > MAX_REPLY_CHUNK_SIZE)
*r_chunksize = MAX_REPLY_CHUNK_SIZE;
else
*r_chunksize = (size_t)j_item->valueint;
return 0;
}
/* Extract the keys from the "keys" array in the JSON object. On
* success a string with the keys identifiers is stored at R_KEYS.
* The keys in that string are LF delimited. On failure an error code
* is returned. */
static gpg_error_t
get_keys (cjson_t json, char **r_keystring)
{
cjson_t j_keys, j_item;
int i, nkeys;
char *p;
size_t length;
*r_keystring = NULL;
j_keys = cJSON_GetObjectItem (json, "keys");
if (!j_keys)
return gpg_error (GPG_ERR_NO_KEY);
if (!cjson_is_array (j_keys) && !cjson_is_string (j_keys))
return gpg_error (GPG_ERR_INV_VALUE);
/* Fixme: We should better use a membuf like thing. */
length = 1; /* For the EOS. */
if (cjson_is_string (j_keys))
{
nkeys = 1;
length += strlen (j_keys->valuestring);
if (strchr (j_keys->valuestring, '\n'))
return gpg_error (GPG_ERR_INV_USER_ID);
}
else
{
nkeys = cJSON_GetArraySize (j_keys);
if (!nkeys)
return gpg_error (GPG_ERR_NO_KEY);
for (i=0; i < nkeys; i++)
{
j_item = cJSON_GetArrayItem (j_keys, i);
if (!j_item || !cjson_is_string (j_item))
return gpg_error (GPG_ERR_INV_VALUE);
if (i)
length++; /* Space for delimiter. */
length += strlen (j_item->valuestring);
if (strchr (j_item->valuestring, '\n'))
return gpg_error (GPG_ERR_INV_USER_ID);
}
}
p = *r_keystring = xtrymalloc (length);
if (!p)
return gpg_error_from_syserror ();
if (cjson_is_string (j_keys))
{
strcpy (p, j_keys->valuestring);
}
else
{
for (i=0; i < nkeys; i++)
{
j_item = cJSON_GetArrayItem (j_keys, i);
if (i)
*p++ = '\n'; /* Add delimiter. */
p = stpcpy (p, j_item->valuestring);
}
}
return 0;
}
/*
* GPGME support functions.
*/
/* Helper for get_context. */
static gpgme_ctx_t
_create_new_context (gpgme_protocol_t proto)
{
gpg_error_t err;
gpgme_ctx_t ctx;
err = gpgme_new (&ctx);
if (err)
log_fatal ("error creating GPGME context: %s\n", gpg_strerror (err));
gpgme_set_protocol (ctx, proto);
gpgme_set_ctx_flag (ctx, "request-origin", "browser");
return ctx;
}
/* Return a context object for protocol PROTO. This is currently a
* statuically allocated context initialized for PROTO. Termnates
* process on failure. */
static gpgme_ctx_t
get_context (gpgme_protocol_t proto)
{
static gpgme_ctx_t ctx_openpgp, ctx_cms;
if (proto == GPGME_PROTOCOL_OpenPGP)
{
if (!ctx_openpgp)
ctx_openpgp = _create_new_context (proto);
return ctx_openpgp;
}
else if (proto == GPGME_PROTOCOL_CMS)
{
if (!ctx_cms)
ctx_cms = _create_new_context (proto);
return ctx_cms;
}
else
log_bug ("invalid protocol %d requested\n", proto);
}
/* Free context object retrieved by get_context. */
static void
release_context (gpgme_ctx_t ctx)
{
/* Nothing to do right now. */
(void)ctx;
}
/* Given a Base-64 encoded string object in JSON return a gpgme data
* object at R_DATA. */
static gpg_error_t
data_from_base64_string (gpgme_data_t *r_data, cjson_t json)
{
#if GPGRT_VERSION_NUMBER < 0x011d00 /* 1.29 */
*r_data = NULL;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
#else
gpg_error_t err;
size_t len;
char *buf = NULL;
gpgrt_b64state_t state = NULL;
gpgme_data_t data = NULL;
*r_data = NULL;
/* A quick check on the JSON. */
if (!cjson_is_string (json))
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
state = gpgrt_b64dec_start (NULL);
if (!state)
{
err = gpg_err_code_from_syserror ();
goto leave;
}
/* Fixme: Data duplication - we should see how to snatch the memory
* from the json object. */
len = strlen (json->valuestring);
buf = xtrystrdup (json->valuestring);
if (!buf)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gpgrt_b64dec_proc (state, buf, len, &len);
if (err)
goto leave;
err = gpgrt_b64dec_finish (state);
state = NULL;
if (err)
goto leave;
err = gpgme_data_new_from_mem (&data, buf, len, 1);
if (err)
goto leave;
*r_data = data;
data = NULL;
leave:
xfree (data);
xfree (buf);
gpgrt_b64dec_finish (state);
return err;
#endif
}
/*
* Implementation of the commands.
*/
/* Create a "data" object and the "type", "base64" and "more" flags
* from DATA and append them to RESULT. Ownership if DATA is
* transferred to this function. TYPE must be a fixed string.
* CHUNKSIZE is the chunksize requested from the caller. If BASE64 is
* -1 the need for base64 encoding is determined by the content of
* DATA, all other values are take as rtue or false. Note that
* op_getmore has similar code but works on PENDING_DATA which is set
* here. */
static gpg_error_t
make_data_object (cjson_t result, gpgme_data_t data, size_t chunksize,
const char *type, int base64)
{
gpg_error_t err;
char *buffer;
- size_t buflen;
+ const char *s;
+ size_t buflen, n;
int c;
if (!base64 || base64 == -1) /* Make sure that we really have a string. */
gpgme_data_write (data, "", 1);
buffer = gpgme_data_release_and_get_mem (data, &buflen);
data = NULL;
if (!buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (base64 == -1)
{
base64 = 0;
if (!buflen)
log_fatal ("Appended Nul byte got lost\n");
- if (memchr (buffer, 0, buflen-1))
- {
- buflen--; /* Adjust for the extra nul byte. */
- base64 = 1;
- }
- /* Fixme: We might want to do more advanced heuristics than to
- * only look for a Nul. */
+ /* Figure out if there is any Nul octet in the buffer. In that
+ * case we need to Base-64 the buffer. Due to problems with the
+ * browser's Javascript we use Base-64 also in case an UTF-8
+ * character is in the buffer. This is because the chunking may
+ * split an UTF-8 characters and JS can't handle this. */
+ for (s=buffer, n=0; n < buflen -1; s++, n++)
+ if (!*s || (*s & 0x80))
+ {
+ buflen--; /* Adjust for the extra nul byte. */
+ base64 = 1;
+ break;
+ }
}
/* Adjust the chunksize if we need to do base64 conversion. */
if (base64)
chunksize = (chunksize / 4) * 3;
xjson_AddStringToObject (result, "type", type);
xjson_AddBoolToObject (result, "base64", base64);
if (buflen > chunksize)
{
xjson_AddBoolToObject (result, "more", 1);
c = buffer[chunksize];
buffer[chunksize] = 0;
if (base64)
err = add_base64_to_object (result, "data", buffer, chunksize);
else
err = cjson_AddStringToObject (result, "data", buffer);
buffer[chunksize] = c;
if (err)
goto leave;
pending_data.buffer = buffer;
buffer = NULL;
pending_data.length = buflen;
pending_data.written = chunksize;
pending_data.type = type;
pending_data.base64 = base64;
}
else
{
if (base64)
err = add_base64_to_object (result, "data", buffer, buflen);
else
err = cjson_AddStringToObject (result, "data", buffer);
}
leave:
gpgme_free (buffer);
return err;
}
static const char hlp_encrypt[] =
"op: \"encrypt\"\n"
"keys: Array of strings with the fingerprints or user-ids\n"
" of the keys to encrypt the data. For a single key\n"
" a String may be used instead of an array.\n"
"data: Input data. \n"
"\n"
"Optional parameters:\n"
"protocol: Either \"openpgp\" (default) or \"cms\".\n"
"chunksize: Max number of bytes in the resulting \"data\".\n"
"\n"
"Optional boolean flags (default is false):\n"
"base64: Input data is base64 encoded.\n"
"mime: Indicate that data is a MIME object.\n"
"armor: Request output in armored format.\n"
"always-trust: Request --always-trust option.\n"
"no-encrypt-to: Do not use a default recipient.\n"
"no-compress: Do not compress the plaintext first.\n"
"throw-keyids: Request the --throw-keyids option.\n"
"want-address: Require that the keys include a mail address.\n"
"wrap: Assume the input is an OpenPGP message.\n"
"\n"
"Response on success:\n"
"type: \"ciphertext\"\n"
"data: Unless armor mode is used a Base64 encoded binary\n"
" ciphertext. In armor mode a string with an armored\n"
" OpenPGP or a PEM message.\n"
"base64: Boolean indicating whether data is base64 encoded.\n"
"more: Optional boolean indicating that \"getmore\" is required.";
static gpg_error_t
op_encrypt (cjson_t request, cjson_t result)
{
gpg_error_t err;
gpgme_ctx_t ctx = NULL;
gpgme_protocol_t protocol;
size_t chunksize;
int opt_base64;
int opt_mime;
char *keystring = NULL;
cjson_t j_input;
gpgme_data_t input = NULL;
gpgme_data_t output = NULL;
int abool;
gpgme_encrypt_flags_t encrypt_flags = 0;
if ((err = get_protocol (request, &protocol)))
goto leave;
ctx = get_context (protocol);
if ((err = get_chunksize (request, &chunksize)))
goto leave;
if ((err = get_boolean_flag (request, "base64", 0, &opt_base64)))
goto leave;
if ((err = get_boolean_flag (request, "mime", 0, &opt_mime)))
goto leave;
if ((err = get_boolean_flag (request, "armor", 0, &abool)))
goto leave;
gpgme_set_armor (ctx, abool);
if ((err = get_boolean_flag (request, "always-trust", 0, &abool)))
goto leave;
if (abool)
encrypt_flags |= GPGME_ENCRYPT_ALWAYS_TRUST;
if ((err = get_boolean_flag (request, "no-encrypt-to", 0,&abool)))
goto leave;
if (abool)
encrypt_flags |= GPGME_ENCRYPT_NO_ENCRYPT_TO;
if ((err = get_boolean_flag (request, "no-compress", 0, &abool)))
goto leave;
if (abool)
encrypt_flags |= GPGME_ENCRYPT_NO_COMPRESS;
if ((err = get_boolean_flag (request, "throw-keyids", 0, &abool)))
goto leave;
if (abool)
encrypt_flags |= GPGME_ENCRYPT_THROW_KEYIDS;
if ((err = get_boolean_flag (request, "wrap", 0, &abool)))
goto leave;
if (abool)
encrypt_flags |= GPGME_ENCRYPT_WRAP;
if ((err = get_boolean_flag (request, "want-address", 0, &abool)))
goto leave;
if (abool)
encrypt_flags |= GPGME_ENCRYPT_WANT_ADDRESS;
/* Get the keys. */
err = get_keys (request, &keystring);
if (err)
{
/* Provide a custom error response. */
error_object (result, "Error getting keys: %s", gpg_strerror (err));
goto leave;
}
/* Get the data. Note that INPUT is a shallow data object with the
* storage hold in REQUEST. */
j_input = cJSON_GetObjectItem (request, "data");
if (!j_input)
{
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
if (!cjson_is_string (j_input))
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (opt_base64)
{
err = data_from_base64_string (&input, j_input);
if (err)
{
error_object (result, "Error decoding Base-64 encoded 'data': %s",
gpg_strerror (err));
goto leave;
}
}
else
{
err = gpgme_data_new_from_mem (&input, j_input->valuestring,
strlen (j_input->valuestring), 0);
if (err)
{
error_object (result, "Error getting 'data': %s", gpg_strerror (err));
goto leave;
}
}
if (opt_mime)
gpgme_data_set_encoding (input, GPGME_DATA_ENCODING_MIME);
/* Create an output data object. */
err = gpgme_data_new (&output);
if (err)
{
error_object (result, "Error creating output data object: %s",
gpg_strerror (err));
goto leave;
}
/* Encrypt. */
err = gpgme_op_encrypt_ext (ctx, NULL, keystring, encrypt_flags,
input, output);
/* encrypt_result = gpgme_op_encrypt_result (ctx); */
if (err)
{
error_object (result, "Encryption failed: %s", gpg_strerror (err));
goto leave;
}
gpgme_data_release (input);
input = NULL;
/* We need to base64 if armoring has not been requested. */
err = make_data_object (result, output, chunksize,
"ciphertext", !gpgme_get_armor (ctx));
output = NULL;
leave:
xfree (keystring);
release_context (ctx);
gpgme_data_release (input);
gpgme_data_release (output);
return err;
}
static const char hlp_decrypt[] =
"op: \"decrypt\"\n"
"data: The encrypted data.\n"
"\n"
"Optional parameters:\n"
"protocol: Either \"openpgp\" (default) or \"cms\".\n"
"chunksize: Max number of bytes in the resulting \"data\".\n"
"\n"
"Optional boolean flags (default is false):\n"
"base64: Input data is base64 encoded.\n"
"\n"
"Response on success:\n"
"type: \"plaintext\"\n"
"data: The decrypted data. This may be base64 encoded.\n"
"base64: Boolean indicating whether data is base64 encoded.\n"
"mime: A Boolean indicating whether the data is a MIME object.\n"
"info: An optional object with extra information.\n"
"more: Optional boolean indicating that \"getmore\" is required.";
static gpg_error_t
op_decrypt (cjson_t request, cjson_t result)
{
gpg_error_t err;
gpgme_ctx_t ctx = NULL;
gpgme_protocol_t protocol;
size_t chunksize;
int opt_base64;
cjson_t j_input;
gpgme_data_t input = NULL;
gpgme_data_t output = NULL;
gpgme_decrypt_result_t decrypt_result;
if ((err = get_protocol (request, &protocol)))
goto leave;
ctx = get_context (protocol);
if ((err = get_chunksize (request, &chunksize)))
goto leave;
if ((err = get_boolean_flag (request, "base64", 0, &opt_base64)))
goto leave;
/* Get the data. Note that INPUT is a shallow data object with the
* storage hold in REQUEST. */
j_input = cJSON_GetObjectItem (request, "data");
if (!j_input)
{
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
if (!cjson_is_string (j_input))
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (opt_base64)
{
err = data_from_base64_string (&input, j_input);
if (err)
{
error_object (result, "Error decoding Base-64 encoded 'data': %s",
gpg_strerror (err));
goto leave;
}
}
else
{
err = gpgme_data_new_from_mem (&input, j_input->valuestring,
strlen (j_input->valuestring), 0);
if (err)
{
error_object (result, "Error getting 'data': %s", gpg_strerror (err));
goto leave;
}
}
/* Create an output data object. */
err = gpgme_data_new (&output);
if (err)
{
error_object (result, "Error creating output data object: %s",
gpg_strerror (err));
goto leave;
}
/* Decrypt. */
err = gpgme_op_decrypt_ext (ctx, GPGME_DECRYPT_VERIFY,
input, output);
decrypt_result = gpgme_op_decrypt_result (ctx);
if (err)
{
error_object (result, "Decryption failed: %s", gpg_strerror (err));
goto leave;
}
gpgme_data_release (input);
input = NULL;
if (decrypt_result->is_mime)
xjson_AddBoolToObject (result, "mime", 1);
err = make_data_object (result, output, chunksize, "plaintext", -1);
output = NULL;
leave:
release_context (ctx);
gpgme_data_release (input);
gpgme_data_release (output);
return err;
}
static const char hlp_getmore[] =
"op: \"getmore\"\n"
"\n"
"Optional parameters:\n"
"chunksize: Max number of bytes in the \"data\" object.\n"
"\n"
"Response on success:\n"
"type: Type of the pending data\n"
"data: The next chunk of data\n"
"base64: Boolean indicating whether data is base64 encoded\n"
"more: Optional boolean requesting another \"getmore\".";
static gpg_error_t
op_getmore (cjson_t request, cjson_t result)
{
gpg_error_t err;
int c;
size_t n;
size_t chunksize;
if ((err = get_chunksize (request, &chunksize)))
goto leave;
/* Adjust the chunksize if we need to do base64 conversion. */
if (pending_data.base64)
chunksize = (chunksize / 4) * 3;
/* Do we have anything pending? */
if (!pending_data.buffer)
{
err = gpg_error (GPG_ERR_NO_DATA);
error_object (result, "Operation not possible: %s", gpg_strerror (err));
goto leave;
}
xjson_AddStringToObject (result, "type", pending_data.type);
xjson_AddBoolToObject (result, "base64", pending_data.base64);
if (pending_data.written >= pending_data.length)
{
/* EOF reached. This should not happen but we return an empty
* string once in case of client errors. */
gpgme_free (pending_data.buffer);
pending_data.buffer = NULL;
xjson_AddBoolToObject (result, "more", 0);
err = cjson_AddStringToObject (result, "data", "");
}
else
{
n = pending_data.length - pending_data.written;
if (n > chunksize)
{
n = chunksize;
xjson_AddBoolToObject (result, "more", 1);
}
else
xjson_AddBoolToObject (result, "more", 0);
c = pending_data.buffer[pending_data.written + n];
pending_data.buffer[pending_data.written + n] = 0;
if (pending_data.base64)
err = add_base64_to_object (result, "data",
(pending_data.buffer
+ pending_data.written), n);
else
err = cjson_AddStringToObject (result, "data",
(pending_data.buffer
+ pending_data.written));
pending_data.buffer[pending_data.written + n] = c;
if (!err)
{
pending_data.written += n;
if (pending_data.written >= pending_data.length)
{
gpgme_free (pending_data.buffer);
pending_data.buffer = NULL;
}
}
}
leave:
return err;
}
static const char hlp_help[] =
"The tool expects a JSON object with the request and responds with\n"
"another JSON object. Even on error a JSON object is returned. The\n"
"property \"op\" is mandatory and its string value selects the\n"
"operation; if the property \"help\" with the value \"true\" exists, the\n"
"operation is not performned but a string with the documentation\n"
"returned. To list all operations it is allowed to leave out \"op\" in\n"
"help mode. Supported values for \"op\" are:\n\n"
" encrypt Encrypt data.\n"
" getmore Retrieve remaining data.\n"
" help Help overview.";
static gpg_error_t
op_help (cjson_t request, cjson_t result)
{
cjson_t j_tmp;
char *buffer = NULL;
const char *msg;
j_tmp = cJSON_GetObjectItem (request, "interactive_help");
if (opt_interactive && j_tmp && cjson_is_string (j_tmp))
msg = buffer = xstrconcat (hlp_help, "\n", j_tmp->valuestring, NULL);
else
msg = hlp_help;
xjson_AddStringToObject (result, "type", "help");
xjson_AddStringToObject (result, "msg", msg);
xfree (buffer);
return 0;
}
/*
* Dispatcher
*/
/* Process a request and return the response. The response is a newly
* allocated string or NULL in case of an error. */
static char *
process_request (const char *request)
{
static struct {
const char *op;
gpg_error_t (*handler)(cjson_t request, cjson_t result);
const char * const helpstr;
} optbl[] = {
{ "encrypt", op_encrypt, hlp_encrypt },
{ "decrypt", op_decrypt, hlp_decrypt },
{ "getmore", op_getmore, hlp_getmore },
{ "help", op_help, hlp_help },
{ NULL }
};
size_t erroff;
cjson_t json;
cjson_t j_tmp, j_op;
cjson_t response;
int helpmode;
const char *op;
char *res;
int idx;
response = xjson_CreateObject ();
json = cJSON_Parse (request, &erroff);
if (!json)
{
log_string (GPGRT_LOGLVL_INFO, request);
log_info ("invalid JSON object at offset %zu\n", erroff);
error_object (response, "invalid JSON object at offset %zu\n", erroff);
goto leave;
}
j_tmp = cJSON_GetObjectItem (json, "help");
helpmode = (j_tmp && cjson_is_true (j_tmp));
j_op = cJSON_GetObjectItem (json, "op");
if (!j_op || !cjson_is_string (j_op))
{
if (!helpmode)
{
error_object (response, "Property \"op\" missing");
goto leave;
}
op = "help"; /* Help summary. */
}
else
op = j_op->valuestring;
for (idx=0; optbl[idx].op; idx++)
if (!strcmp (op, optbl[idx].op))
break;
if (optbl[idx].op)
{
if (helpmode && strcmp (op, "help"))
{
xjson_AddStringToObject (response, "type", "help");
xjson_AddStringToObject (response, "op", op);
xjson_AddStringToObject (response, "msg", optbl[idx].helpstr);
}
else
{
gpg_error_t err;
/* If this is not the "getmore" command and we have any
* pending data release that data. */
if (pending_data.buffer && optbl[idx].handler != op_getmore)
{
gpgme_free (pending_data.buffer);
pending_data.buffer = NULL;
}
err = optbl[idx].handler (json, response);
if (err)
{
if (!(j_tmp = cJSON_GetObjectItem (response, "type"))
|| !cjson_is_string (j_tmp)
|| strcmp (j_tmp->valuestring, "error"))
{
/* No error type response - provide a generic one. */
error_object (response, "Operation failed: %s",
gpg_strerror (err));
}
xjson_AddStringToObject (response, "op", op);
}
}
}
else /* Operation not supported. */
{
error_object (response, "Unknown operation '%s'", op);
xjson_AddStringToObject (response, "op", op);
}
leave:
cJSON_Delete (json);
if (opt_interactive)
res = cJSON_Print (response);
else
res = cJSON_PrintUnformatted (response);
if (!res)
log_error ("Printing JSON data failed\n");
cJSON_Delete (response);
return res;
}
/*
* Driver code
*/
static char *
get_file (const char *fname)
{
gpg_error_t err;
estream_t fp;
struct stat st;
char *buf;
size_t buflen;
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
return NULL;
}
if (fstat (es_fileno(fp), &st))
{
err = gpg_error_from_syserror ();
log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err));
es_fclose (fp);
return NULL;
}
buflen = st.st_size;
buf = xmalloc (buflen+1);
if (es_fread (buf, buflen, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
es_fclose (fp);
xfree (buf);
return NULL;
}
buf[buflen] = 0;
es_fclose (fp);
return buf;
}
/* Return a malloced line or NULL on EOF. Terminate on read
* error. */
static char *
get_line (void)
{
char *line = NULL;
size_t linesize = 0;
gpg_error_t err;
size_t maxlength = 2048;
int n;
const char *s;
char *p;
again:
n = es_read_line (es_stdin, &line, &linesize, &maxlength);
if (n < 0)
{
err = gpg_error_from_syserror ();
log_error ("error reading line: %s\n", gpg_strerror (err));
exit (1);
}
if (!n)
{
xfree (line);
line = NULL;
return NULL; /* EOF */
}
if (!maxlength)
{
log_info ("line too long - skipped\n");
goto again;
}
if (memchr (line, 0, n))
log_info ("warning: line shortened due to embedded Nul character\n");
if (line[n-1] == '\n')
line[n-1] = 0;
/* Trim leading spaces. */
for (s=line; spacep (s); s++)
;
if (s != line)
{
for (p=line; *s;)
*p++ = *s++;
*p = 0;
n = p - line;
}
return line;
}
/* Process meta commands used with the standard REPL. */
static char *
process_meta_commands (const char *request)
{
char *result = NULL;
while (spacep (request))
request++;
if (!strncmp (request, "help", 4) && (spacep (request+4) || !request[4]))
{
if (request[4])
{
char *buf = xstrconcat ("{ \"help\":true, \"op\":\"", request+5,
"\" }", NULL);
result = process_request (buf);
xfree (buf);
}
else
result = process_request ("{ \"op\": \"help\","
" \"interactive_help\": "
"\"\\nMeta commands:\\n"
" ,read FNAME Process data from FILE\\n"
" ,help CMD Print help for a command\\n"
" ,quit Terminate process\""
"}");
}
else if (!strncmp (request, "quit", 4) && (spacep (request+4) || !request[4]))
exit (0);
else if (!strncmp (request, "read", 4) && (spacep (request+4) || !request[4]))
{
if (!request[4])
log_info ("usage: ,read FILENAME\n");
else
{
char *buffer = get_file (request + 5);
if (buffer)
{
result = process_request (buffer);
xfree (buffer);
}
}
}
else
log_info ("invalid meta command\n");
return result;
}
/* If STRING has a help response, return the MSG property in a human
* readable format. */
static char *
get_help_msg (const char *string)
{
cjson_t json, j_type, j_msg;
const char *msg;
char *buffer = NULL;
char *p;
json = cJSON_Parse (string, NULL);
if (json)
{
j_type = cJSON_GetObjectItem (json, "type");
if (j_type && cjson_is_string (j_type)
&& !strcmp (j_type->valuestring, "help"))
{
j_msg = cJSON_GetObjectItem (json, "msg");
if (j_msg || cjson_is_string (j_msg))
{
msg = j_msg->valuestring;
buffer = malloc (strlen (msg)+1);
if (buffer)
{
for (p=buffer; *msg; msg++)
{
if (*msg == '\\' && msg[1] == '\n')
*p++ = '\n';
else
*p++ = *msg;
}
*p = 0;
}
}
}
cJSON_Delete (json);
}
return buffer;
}
/* An interactive standard REPL. */
static void
interactive_repl (void)
{
char *line = NULL;
char *request = NULL;
char *response = NULL;
char *p;
int first;
es_setvbuf (es_stdin, NULL, _IONBF, 0);
#if GPGRT_VERSION_NUMBER >= 0x011d00 /* 1.29 */
es_fprintf (es_stderr, "%s %s ready (enter \",help\" for help)\n",
gpgrt_strusage (11), gpgrt_strusage (13));
#endif
do
{
es_fputs ("> ", es_stderr);
es_fflush (es_stderr);
es_fflush (es_stdout);
xfree (line);
line = get_line ();
es_fflush (es_stderr);
es_fflush (es_stdout);
first = !request;
if (line && *line)
{
if (!request)
request = xstrdup (line);
else
request = xstrconcat (request, "\n", line, NULL);
}
if (!line)
es_fputs ("\n", es_stderr);
if (!line || !*line || (first && *request == ','))
{
/* Process the input. */
xfree (response);
response = NULL;
if (request && *request == ',')
{
response = process_meta_commands (request+1);
}
else if (request)
{
response = process_request (request);
}
xfree (request);
request = NULL;
if (response)
{
if (opt_interactive)
{
char *msg = get_help_msg (response);
if (msg)
{
xfree (response);
response = msg;
}
}
es_fputs ("===> ", es_stderr);
es_fflush (es_stderr);
for (p=response; *p; p++)
{
if (*p == '\n')
{
es_fflush (es_stdout);
es_fputs ("\n===> ", es_stderr);
es_fflush (es_stderr);
}
else
es_putc (*p, es_stdout);
}
es_fflush (es_stdout);
es_fputs ("\n", es_stderr);
}
}
}
while (line);
xfree (request);
xfree (response);
xfree (line);
}
/* Read and process a single request. */
static void
read_and_process_single_request (void)
{
char *line = NULL;
char *request = NULL;
char *response = NULL;
size_t n;
for (;;)
{
xfree (line);
line = get_line ();
if (line && *line)
request = (request? xstrconcat (request, "\n", line, NULL)
/**/ : xstrdup (line));
if (!line)
{
if (request)
{
xfree (response);
response = process_request (request);
if (response)
{
es_fputs (response, es_stdout);
if ((n = strlen (response)) && response[n-1] != '\n')
es_fputc ('\n', es_stdout);
}
es_fflush (es_stdout);
}
break;
}
}
xfree (response);
xfree (request);
xfree (line);
}
/* The Native Messaging processing loop. */
static void
native_messaging_repl (void)
{
gpg_error_t err;
uint32_t nrequest, nresponse;
char *request = NULL;
char *response = NULL;
size_t n;
/* Due to the length octets we need to switch the I/O stream into
* binary mode. */
es_set_binary (es_stdin);
es_set_binary (es_stdout);
es_setbuf (es_stdin, NULL); /* stdin needs to be unbuffered! */
for (;;)
{
/* Read length. Note that the protocol uses native endianess.
* Is it allowed to call such a thing a well thought out
* protocol? */
if (es_read (es_stdin, &nrequest, sizeof nrequest, &n))
{
err = gpg_error_from_syserror ();
log_error ("error reading request header: %s\n", gpg_strerror (err));
break;
}
if (!n)
break; /* EOF */
if (n != sizeof nrequest)
{
log_error ("error reading request header: short read\n");
break;
}
if (nrequest > MAX_REQUEST_SIZE)
{
log_error ("error reading request: request too long (%zu MiB)\n",
(size_t)nrequest / (1024*1024));
/* Fixme: Shall we read the request to the bit bucket and
* return an error reponse or just return an error reponse
* and terminate? Needs some testing. */
break;
}
/* Read request. */
request = xtrymalloc (nrequest);
if (!request)
{
err = gpg_error_from_syserror ();
log_error ("error reading request: Not enough memory for %zu MiB)\n",
(size_t)nrequest / (1024*1024));
/* FIXME: See comment above. */
break;
}
if (es_read (es_stdin, request, nrequest, &n))
{
err = gpg_error_from_syserror ();
log_error ("error reading request: %s\n", gpg_strerror (err));
break;
}
if (n != nrequest)
{
/* That is a protocol violation. */
xfree (response);
response = error_object_string ("Invalid request:"
" short read (%zu of %zu bytes)\n",
n, (size_t)nrequest);
}
else /* Process request */
{
if (opt_debug)
log_debug ("request='%s'\n", request);
xfree (response);
response = process_request (request);
if (opt_debug)
log_debug ("response='%s'\n", response);
}
nresponse = strlen (response);
/* Write response */
if (es_write (es_stdout, &nresponse, sizeof nresponse, &n))
{
err = gpg_error_from_syserror ();
log_error ("error writing request header: %s\n", gpg_strerror (err));
break;
}
if (n != sizeof nrequest)
{
log_error ("error writing request header: short write\n");
break;
}
if (es_write (es_stdout, response, nresponse, &n))
{
err = gpg_error_from_syserror ();
log_error ("error writing request: %s\n", gpg_strerror (err));
break;
}
if (n != nresponse)
{
log_error ("error writing request: short write\n");
break;
}
if (es_fflush (es_stdout) || es_ferror (es_stdout))
{
err = gpg_error_from_syserror ();
log_error ("error writing request: %s\n", gpg_strerror (err));
break;
}
}
xfree (response);
xfree (request);
}
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 9: p = "LGPL-2.1-or-later"; break;
case 11: p = "gpgme-json"; break;
case 13: p = PACKAGE_VERSION; break;
case 14: p = "Copyright (C) 2018 g10 Code GmbH"; break;
case 19: p = "Please report bugs to <" PACKAGE_BUGREPORT ">.\n"; break;
case 1:
case 40:
p = "Usage: gpgme-json [OPTIONS]";
break;
case 41:
p = "Native messaging based GPGME operations.\n";
break;
case 42:
p = "1"; /* Flag print 40 as part of 41. */
break;
default: p = NULL; break;
}
return p;
}
int
main (int argc, char *argv[])
{
#if GPGRT_VERSION_NUMBER < 0x011d00 /* 1.29 */
fprintf (stderr, "WARNING: Old libgpg-error - using limited mode\n");
native_messaging_repl ();
#else /* This is a modern libgp-error. */
enum { CMD_DEFAULT = 0,
CMD_INTERACTIVE = 'i',
CMD_SINGLE = 's',
CMD_LIBVERSION = 501,
} cmd = CMD_DEFAULT;
enum {
OPT_DEBUG = 600
};
static gpgrt_opt_t opts[] = {
ARGPARSE_c (CMD_INTERACTIVE, "interactive", "Interactive REPL"),
ARGPARSE_c (CMD_SINGLE, "single", "Single request mode"),
ARGPARSE_c (CMD_LIBVERSION, "lib-version", "Show library version"),
ARGPARSE_s_n(OPT_DEBUG, "debug", "Flyswatter"),
ARGPARSE_end()
};
gpgrt_argparse_t pargs = { &argc, &argv};
gpgrt_set_strusage (my_strusage);
#ifdef HAVE_SETLOCALE
setlocale (LC_ALL, "");
#endif
gpgme_check_version (NULL);
#ifdef LC_CTYPE
gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL));
#endif
#ifdef LC_MESSAGES
gpgme_set_locale (NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL));
#endif
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case CMD_INTERACTIVE:
opt_interactive = 1;
/* Fall trough. */
case CMD_SINGLE:
case CMD_LIBVERSION:
cmd = pargs.r_opt;
break;
case OPT_DEBUG: opt_debug = 1; break;
default:
pargs.err = ARGPARSE_PRINT_WARNING;
break;
}
}
gpgrt_argparse (NULL, &pargs, NULL);
if (!opt_debug)
{
const char *s = getenv ("GPGME_JSON_DEBUG");
if (s && atoi (s) > 0)
opt_debug = 1;
}
if (opt_debug)
{
const char *home = getenv ("HOME");
char *file = xstrconcat ("socket://",
home? home:"/tmp",
"/.gnupg/S.gpgme-json.log", NULL);
log_set_file (file);
xfree (file);
}
if (opt_debug)
{ int i;
for (i=0; argv[i]; i++)
log_debug ("argv[%d]='%s'\n", i, argv[i]);
}
switch (cmd)
{
case CMD_DEFAULT:
native_messaging_repl ();
break;
case CMD_SINGLE:
read_and_process_single_request ();
break;
case CMD_INTERACTIVE:
interactive_repl ();
break;
case CMD_LIBVERSION:
printf ("Version from header: %s (0x%06x)\n",
GPGME_VERSION, GPGME_VERSION_NUMBER);
printf ("Version from binary: %s\n", gpgme_check_version (NULL));
printf ("Copyright blurb ...:%s\n", gpgme_check_version ("\x01\x01"));
break;
}
if (opt_debug)
log_debug ("ready");
#endif /* This is a modern libgp-error. */
return 0;
}
#endif /* libgpg-error >= 1.28 */