Page MenuHome GnuPG

No OneTemporary

diff --git a/src/conf/dirservconfigpage.cpp b/src/conf/dirservconfigpage.cpp
index e65d6cac8..a6f7d892b 100644
--- a/src/conf/dirservconfigpage.cpp
+++ b/src/conf/dirservconfigpage.cpp
@@ -1,484 +1,504 @@
/* -*- mode: c++; c-basic-offset:4 -*-
conf/dirservconfigpage.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2004, 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "dirservconfigpage.h"
#include "labelledwidget.h"
#include <settings.h>
#include <Libkleo/Compat>
#include <Libkleo/DirectoryServicesWidget>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyserverConfig>
#include <QGpgME/CryptoConfig>
#include <QGpgME/Protocol>
#include "kleopatra_debug.h"
#include <KConfig>
#include <KLocalizedString>
#include <KMessageBox>
#include <QSpinBox>
#include <QCheckBox>
#include <QGroupBox>
#include <QLabel>
#include <QLayout>
#include <QLineEdit>
#include <QTimeEdit>
#include <QVBoxLayout>
#include <gpgme++/engineinfo.h>
#include <gpgme.h>
using namespace Kleo;
using namespace QGpgME;
// Option for configuring X.509 servers (available via gpgconf since GnuPG 2.3.5 and 2.2.34)
static const char s_x509services_componentName[] = "dirmngr";
static const char s_x509services_entryName[] = "ldapserver";
// Legacy option for configuring X.509 servers (deprecated with GnuPG 2.2.28 and 2.3.2)
static const char s_x509services_legacy_componentName[] = "gpgsm";
static const char s_x509services_legacy_entryName[] = "keyserver";
static const char s_pgpservice_componentName[] = "dirmngr";
static const char s_pgpservice_entryName[] = "keyserver";
// legacy config entry used until GnuPG 2.2
static const char s_pgpservice_legacy_componentName[] = "gpg";
static const char s_pgpservice_legacy_entryName[] = "keyserver";
static const char s_timeout_componentName[] = "dirmngr";
static const char s_timeout_entryName[] = "ldaptimeout";
static const char s_maxitems_componentName[] = "dirmngr";
static const char s_maxitems_entryName[] = "max-replies";
class DirectoryServicesConfigurationPage::Private
{
DirectoryServicesConfigurationPage *q = nullptr;
public:
Private(DirectoryServicesConfigurationPage *q);
void load();
void save();
void defaults();
private:
enum EntryMultiplicity {
SingleValue,
ListValue,
};
enum ShowError {
DoNotShowError,
DoShowError,
};
void setX509ServerEntry(const std::vector<KeyserverConfig> &servers);
void load(const Kleo::Settings &settings);
QGpgME::CryptoConfigEntry *configEntry(const char *componentName,
const char *entryName,
QGpgME::CryptoConfigEntry::ArgType argType,
EntryMultiplicity multiplicity,
ShowError showError);
Kleo::LabelledWidget<QLineEdit> mOpenPGPKeyserverEdit;
Kleo::DirectoryServicesWidget *mDirectoryServices = nullptr;
Kleo::LabelledWidget<QTimeEdit> mTimeout;
Kleo::LabelledWidget<QSpinBox> mMaxItems;
QCheckBox *mFetchMissingSignerKeysCB = nullptr;
QCheckBox *mQueryWKDsForAllUserIDsCB = nullptr;
+ QCheckBox *mUseKeyServerCheckBox = nullptr;
QGpgME::CryptoConfigEntry *mOpenPGPServiceEntry = nullptr;
QGpgME::CryptoConfigEntry *mTimeoutConfigEntry = nullptr;
QGpgME::CryptoConfigEntry *mMaxItemsConfigEntry = nullptr;
QGpgME::CryptoConfig *mConfig = nullptr;
};
DirectoryServicesConfigurationPage::Private::Private(DirectoryServicesConfigurationPage *q)
{
mConfig = QGpgME::cryptoConfig();
auto glay = new QGridLayout(q);
// OpenPGP keyserver
int row = 0;
{
auto groupBox = new QGroupBox{i18nc("@title", "OpenPGP Keyserver")};
groupBox->setFlat(true);
auto groupBoxLayout = new QVBoxLayout{groupBox};
groupBoxLayout->setContentsMargins({});
- auto l = new QHBoxLayout{};
- l->setContentsMargins(0, 0, 0, 0);
-
- groupBoxLayout->addLayout(l);
groupBoxLayout->addWidget(new QLabel(i18n("Please note that only one OpenPGP keyserver can be configured.")));
- mOpenPGPKeyserverEdit.createWidgets(q);
- mOpenPGPKeyserverEdit.label()->setText(i18n("OpenPGP keyserver:"));
- if (engineIsVersion(2, 4, 4) //
- || (engineIsVersion(2, 2, 42) && !engineIsVersion(2, 3, 0))) {
- mOpenPGPKeyserverEdit.widget()->setToolTip( //
- xi18nc("@info:tooltip",
- "Enter the address of the keyserver to use when searching for OpenPGP certificates and "
- "when uploading OpenPGP certificates. If you do not enter an address then an internal "
- "default will be used. To disable the use of an OpenPGP keyserver enter the special value <emphasis>none</emphasis>."));
+ {
+ mUseKeyServerCheckBox = new QCheckBox(i18nc("@label:checkbox", "Use OpenPGP keyserver"));
+ groupBoxLayout->addWidget(mUseKeyServerCheckBox);
+ }
+ {
+ auto l = new QHBoxLayout{};
+ l->setContentsMargins(0, 0, 0, 0);
+
+ groupBoxLayout->addLayout(l);
+
+ mOpenPGPKeyserverEdit.createWidgets(q);
+ mOpenPGPKeyserverEdit.label()->setText(i18n("OpenPGP keyserver:"));
+ if (engineIsVersion(2, 4, 4) //
+ || (engineIsVersion(2, 2, 42) && !engineIsVersion(2, 3, 0))) {
+ mOpenPGPKeyserverEdit.widget()->setToolTip( //
+ xi18nc("@info:tooltip",
+ "Enter the address of the keyserver to use when searching for OpenPGP certificates and "
+ "when uploading OpenPGP certificates. If you do not enter an address then an internal "
+ "default will be used. To disable the use of an OpenPGP keyserver enter the special value <emphasis>none</emphasis>."));
+ }
+ l->addWidget(mOpenPGPKeyserverEdit.label());
+ l->addWidget(mOpenPGPKeyserverEdit.widget());
}
- l->addWidget(mOpenPGPKeyserverEdit.label());
- l->addWidget(mOpenPGPKeyserverEdit.widget());
+ connect(mUseKeyServerCheckBox, &QCheckBox::toggled, mOpenPGPKeyserverEdit.widget(), &QLineEdit::setEnabled);
+ connect(mUseKeyServerCheckBox, &QCheckBox::toggled, q, [this, q]() {
+ if (!mUseKeyServerCheckBox->isChecked()) {
+ mOpenPGPKeyserverEdit.widget()->setText(QStringLiteral("none"));
+ q->changed();
+ } else if (mOpenPGPKeyserverEdit.widget()->text() == QStringLiteral("none")) {
+ mOpenPGPKeyserverEdit.widget()->setText({});
+ q->changed();
+ }
+ });
glay->addWidget(groupBox, row, 0, 1, 3);
connect(mOpenPGPKeyserverEdit.widget(), &QLineEdit::textEdited, q, &DirectoryServicesConfigurationPage::changed);
}
// X.509 servers
if (Settings{}.cmsEnabled()) {
++row;
auto groupBox = new QGroupBox{i18n("X.509 Directory Services"), q};
groupBox->setFlat(true);
auto groupBoxLayout = new QVBoxLayout{groupBox};
groupBoxLayout->setContentsMargins({});
if (gpgme_check_version("1.16.0")) {
mDirectoryServices = new Kleo::DirectoryServicesWidget(q);
if (QLayout *l = mDirectoryServices->layout()) {
l->setContentsMargins(0, 0, 0, 0);
}
groupBoxLayout->addWidget(mDirectoryServices);
connect(mDirectoryServices, &DirectoryServicesWidget::changed, q, &DirectoryServicesConfigurationPage::changed);
} else {
// QGpgME does not properly support keyserver flags for X.509 keyservers (added in GnuPG 2.2.28);
// disable the configuration to prevent the configuration from being corrupted
groupBoxLayout->addWidget(new QLabel{i18n("Configuration of directory services is not possible "
"because the used gpgme libraries are too old."),
q});
}
glay->addWidget(groupBox, row, 0, 1, 3);
}
// LDAP timeout
++row;
mTimeout.createWidgets(q);
mTimeout.label()->setText(i18n("LDAP &timeout (minutes:seconds):"));
mTimeout.widget()->setDisplayFormat(QStringLiteral("mm:ss"));
connect(mTimeout.widget(), &QTimeEdit::timeChanged, q, &DirectoryServicesConfigurationPage::changed);
glay->addWidget(mTimeout.label(), row, 0);
glay->addWidget(mTimeout.widget(), row, 1);
// Max number of items returned by queries
++row;
mMaxItems.createWidgets(q);
mMaxItems.label()->setText(i18n("&Maximum number of items returned by query:"));
mMaxItems.widget()->setMinimum(0);
connect(mMaxItems.widget(), &QSpinBox::valueChanged, q, &DirectoryServicesConfigurationPage::changed);
glay->addWidget(mMaxItems.label(), row, 0);
glay->addWidget(mMaxItems.widget(), row, 1);
++row;
mFetchMissingSignerKeysCB = new QCheckBox{q};
mFetchMissingSignerKeysCB->setText(i18nc("@option:check", "Retrieve missing certification keys when importing new keys"));
mFetchMissingSignerKeysCB->setToolTip(xi18nc("@info:tooltip",
"If enabled, then Kleopatra will automatically try to retrieve the keys "
"that were used to certify the user IDs of newly imported OpenPGP keys."));
connect(mFetchMissingSignerKeysCB, &QCheckBox::toggled, q, &DirectoryServicesConfigurationPage::changed);
glay->addWidget(mFetchMissingSignerKeysCB, row, 0, 1, 3);
++row;
mQueryWKDsForAllUserIDsCB = new QCheckBox{q};
mQueryWKDsForAllUserIDsCB->setText(i18nc("@option:check", "Query certificate directories of providers for all user IDs"));
mQueryWKDsForAllUserIDsCB->setToolTip(xi18nc("@info:tooltip",
"By default, Kleopatra only queries the certificate directories of providers (WKD) "
"for user IDs that were originally retrieved from a WKD when you update an OpenPGP "
"certificate. If this option is enabled, then Kleopatra will query WKDs for all user IDs."));
connect(mQueryWKDsForAllUserIDsCB, &QCheckBox::toggled, q, &DirectoryServicesConfigurationPage::changed);
glay->addWidget(mQueryWKDsForAllUserIDsCB, row, 0, 1, 3);
glay->setRowStretch(++row, 1);
glay->setColumnStretch(2, 1);
}
static auto readKeyserverConfigs(const CryptoConfigEntry *configEntry)
{
std::vector<KeyserverConfig> servers;
if (configEntry) {
const auto urls = configEntry->urlValueList();
servers.reserve(urls.size());
std::transform(std::begin(urls), std::end(urls), std::back_inserter(servers), &KeyserverConfig::fromUrl);
}
return servers;
}
void DirectoryServicesConfigurationPage::Private::load(const Kleo::Settings &settings)
{
if (mDirectoryServices) {
mDirectoryServices->clear();
// gpgsm uses the deprecated keyserver option in gpgsm.conf additionally to the ldapserver option in dirmngr.conf;
// we (try to) read servers from both entries, but always write to the newest existing entry
const auto *const newEntry =
configEntry(s_x509services_componentName, s_x509services_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError);
const auto *const legacyEntry =
configEntry(s_x509services_legacy_componentName, s_x509services_legacy_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError);
auto entry = newEntry ? newEntry : legacyEntry;
if (entry) {
const auto additionalServers = readKeyserverConfigs(legacyEntry);
auto servers = readKeyserverConfigs(newEntry);
std::copy(std::begin(additionalServers), std::end(additionalServers), std::back_inserter(servers));
mDirectoryServices->setKeyservers(servers);
mDirectoryServices->setReadOnly(entry->isReadOnly());
} else {
qCWarning(KLEOPATRA_LOG) << "Unknown or wrong typed config entries" << s_x509services_componentName << "/" << s_x509services_entryName << "and"
<< s_x509services_legacy_componentName << "/" << s_x509services_legacy_entryName;
mDirectoryServices->setDisabled(true);
}
}
{
// gpg prefers the deprecated keyserver option in gpg.conf over the keyserver option in dirmngr.conf;
// therefore, we use the deprecated keyserver option if it is set or if the new option doesn't exist (gpg < 2.1.9)
auto const newEntry = configEntry(s_pgpservice_componentName, s_pgpservice_entryName, CryptoConfigEntry::ArgType_String, SingleValue, DoNotShowError);
auto const legacyEntry =
configEntry(s_pgpservice_legacy_componentName, s_pgpservice_legacy_entryName, CryptoConfigEntry::ArgType_String, SingleValue, DoNotShowError);
mOpenPGPServiceEntry = ((legacyEntry && legacyEntry->isSet()) || !newEntry) ? legacyEntry : newEntry;
if (!mOpenPGPServiceEntry) {
qCWarning(KLEOPATRA_LOG) << "Unknown or wrong typed config entries" << s_pgpservice_componentName << "/" << s_pgpservice_entryName << "and"
<< s_pgpservice_legacy_componentName << "/" << s_pgpservice_legacy_entryName;
} else if (mOpenPGPServiceEntry == legacyEntry) {
qCDebug(KLEOPATRA_LOG) << "Using config entry" << s_pgpservice_legacy_componentName << "/" << s_pgpservice_legacy_entryName;
} else {
qCDebug(KLEOPATRA_LOG) << "Using config entry" << s_pgpservice_componentName << "/" << s_pgpservice_entryName;
}
mOpenPGPKeyserverEdit.widget()->setText(mOpenPGPServiceEntry && mOpenPGPServiceEntry->isSet() ? mOpenPGPServiceEntry->stringValue() : QString());
- mOpenPGPKeyserverEdit.setEnabled(mOpenPGPServiceEntry && !mOpenPGPServiceEntry->isReadOnly());
+ mUseKeyServerCheckBox->setChecked(mOpenPGPKeyserverEdit.widget()->text() != QStringLiteral("none"));
+ mUseKeyServerCheckBox->setEnabled(mOpenPGPServiceEntry && !mOpenPGPServiceEntry->isReadOnly());
+ mOpenPGPKeyserverEdit.setEnabled(mOpenPGPServiceEntry && !mOpenPGPServiceEntry->isReadOnly() && mUseKeyServerCheckBox->isChecked());
if (newEntry && !newEntry->defaultValue().isNull()) {
mOpenPGPKeyserverEdit.widget()->setPlaceholderText(newEntry->defaultValue().toString());
} else {
if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.16") {
mOpenPGPKeyserverEdit.widget()->setPlaceholderText(QStringLiteral("hkp://keys.gnupg.net"));
} else {
mOpenPGPKeyserverEdit.widget()->setPlaceholderText(QStringLiteral("hkps://hkps.pool.sks-keyservers.net"));
}
}
}
// read LDAP timeout
// first try to read the config entry as int (GnuPG 2.3)
mTimeoutConfigEntry = configEntry(s_timeout_componentName, s_timeout_entryName, CryptoConfigEntry::ArgType_Int, SingleValue, DoNotShowError);
if (!mTimeoutConfigEntry) {
// if this fails, then try to read the config entry as unsigned int (GnuPG <= 2.2)
mTimeoutConfigEntry = configEntry(s_timeout_componentName, s_timeout_entryName, CryptoConfigEntry::ArgType_UInt, SingleValue, DoShowError);
}
if (mTimeoutConfigEntry) {
const int ldapTimeout = mTimeoutConfigEntry->argType() == CryptoConfigEntry::ArgType_Int ? mTimeoutConfigEntry->intValue()
: static_cast<int>(mTimeoutConfigEntry->uintValue());
const QTime time = QTime(0, 0, 0, 0).addSecs(ldapTimeout);
// qCDebug(KLEOPATRA_LOG) <<"timeout:" << mTimeoutConfigEntry->uintValue() <<" ->" << time;
mTimeout.widget()->setTime(time);
}
mTimeout.setEnabled(mTimeoutConfigEntry && !mTimeoutConfigEntry->isReadOnly());
// read max-replies config entry
// first try to read the config entry as int (GnuPG 2.3)
mMaxItemsConfigEntry = configEntry(s_maxitems_componentName, s_maxitems_entryName, CryptoConfigEntry::ArgType_Int, SingleValue, DoNotShowError);
if (!mMaxItemsConfigEntry) {
// if this fails, then try to read the config entry as unsigned int (GnuPG <= 2.2)
mMaxItemsConfigEntry = configEntry(s_maxitems_componentName, s_maxitems_entryName, CryptoConfigEntry::ArgType_UInt, SingleValue, DoShowError);
}
if (mMaxItemsConfigEntry) {
const int value = mMaxItemsConfigEntry->argType() == CryptoConfigEntry::ArgType_Int ? mMaxItemsConfigEntry->intValue()
: static_cast<int>(mMaxItemsConfigEntry->uintValue());
mMaxItems.widget()->blockSignals(true); // KNumInput emits valueChanged from setValue!
mMaxItems.widget()->setValue(value);
mMaxItems.widget()->blockSignals(false);
}
mMaxItems.setEnabled(mMaxItemsConfigEntry && !mMaxItemsConfigEntry->isReadOnly());
mFetchMissingSignerKeysCB->setChecked(settings.retrieveSignerKeysAfterImport());
mFetchMissingSignerKeysCB->setEnabled(!settings.isImmutable(QStringLiteral("RetrieveSignerKeysAfterImport")));
mQueryWKDsForAllUserIDsCB->setChecked(settings.queryWKDsForAllUserIDs());
mQueryWKDsForAllUserIDsCB->setEnabled(!settings.isImmutable(QStringLiteral("QueryWKDsForAllUserIDs")));
}
void DirectoryServicesConfigurationPage::Private::load()
{
load(Settings{});
}
namespace
{
void updateIntegerConfigEntry(QGpgME::CryptoConfigEntry *configEntry, int value)
{
if (!configEntry) {
return;
}
if (configEntry->argType() == CryptoConfigEntry::ArgType_Int) {
if (configEntry->intValue() != value) {
configEntry->setIntValue(value);
}
} else {
const auto newValue = static_cast<unsigned>(value);
if (configEntry->uintValue() != newValue) {
configEntry->setUIntValue(newValue);
}
}
}
}
void DirectoryServicesConfigurationPage::Private::setX509ServerEntry(const std::vector<KeyserverConfig> &servers)
{
const auto newEntry = configEntry(s_x509services_componentName, s_x509services_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError);
const auto legacyEntry =
configEntry(s_x509services_legacy_componentName, s_x509services_legacy_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError);
if ((newEntry && newEntry->isReadOnly()) || (legacyEntry && legacyEntry->isReadOnly())) {
// do not change the config entries if either config entry is read-only
return;
}
QList<QUrl> urls;
urls.reserve(servers.size());
std::transform(std::begin(servers), std::end(servers), std::back_inserter(urls), std::mem_fn(&KeyserverConfig::toUrl));
if (newEntry) {
// write all servers to the new config entry
newEntry->setURLValueList(urls);
// and clear the legacy config entry
if (legacyEntry) {
legacyEntry->setURLValueList({});
}
} else if (legacyEntry) {
// write all servers to the legacy config entry if the new entry is not available
legacyEntry->setURLValueList(urls);
} else {
qCWarning(KLEOPATRA_LOG) << "Could not store the X.509 servers. Unknown or wrong typed config entries" << s_x509services_componentName << "/"
<< s_x509services_entryName << "and" << s_x509services_legacy_componentName << "/" << s_x509services_legacy_entryName;
}
}
void DirectoryServicesConfigurationPage::Private::save()
{
if (mDirectoryServices && mDirectoryServices->isEnabled()) {
setX509ServerEntry(mDirectoryServices->keyservers());
}
if (mOpenPGPServiceEntry) {
const auto keyserver = mOpenPGPKeyserverEdit.widget()->text().trimmed();
if (keyserver.isEmpty()) {
mOpenPGPServiceEntry->resetToDefault();
} else if (keyserver == QLatin1StringView{"none"}) {
mOpenPGPServiceEntry->setStringValue(keyserver);
} else {
const auto keyserverUrl = keyserver.contains(QLatin1StringView{"://"}) ? keyserver : (QLatin1String{"hkps://"} + keyserver);
mOpenPGPServiceEntry->setStringValue(keyserverUrl);
}
}
const QTime time{mTimeout.widget()->time()};
updateIntegerConfigEntry(mTimeoutConfigEntry, time.minute() * 60 + time.second());
updateIntegerConfigEntry(mMaxItemsConfigEntry, mMaxItems.widget()->value());
mConfig->sync(true);
Settings settings;
settings.setRetrieveSignerKeysAfterImport(mFetchMissingSignerKeysCB->isChecked());
settings.setQueryWKDsForAllUserIDs(mQueryWKDsForAllUserIDsCB->isChecked());
settings.save();
}
void DirectoryServicesConfigurationPage::Private::defaults()
{
// these guys don't have a default, to clear them:
if (mDirectoryServices && mDirectoryServices->isEnabled()) {
setX509ServerEntry({});
}
if (mOpenPGPServiceEntry && !mOpenPGPServiceEntry->isReadOnly()) {
mOpenPGPServiceEntry->setStringValue(QString());
}
// these presumably have a default, use that one:
if (mTimeoutConfigEntry && !mTimeoutConfigEntry->isReadOnly()) {
mTimeoutConfigEntry->resetToDefault();
}
if (mMaxItemsConfigEntry && !mMaxItemsConfigEntry->isReadOnly()) {
mMaxItemsConfigEntry->resetToDefault();
}
Settings settings;
settings.setRetrieveSignerKeysAfterImport(settings.findItem(QStringLiteral("RetrieveSignerKeysAfterImport"))->getDefault().toBool());
settings.setQueryWKDsForAllUserIDs(settings.findItem(QStringLiteral("QueryWKDsForAllUserIDs"))->getDefault().toBool());
load(settings);
}
// Find config entry for ldap servers. Implements runtime checks on the configuration option.
CryptoConfigEntry *DirectoryServicesConfigurationPage::Private::configEntry(const char *componentName,
const char *entryName,
CryptoConfigEntry::ArgType argType,
EntryMultiplicity multiplicity,
ShowError showError)
{
CryptoConfigEntry *const entry = Kleo::getCryptoConfigEntry(mConfig, componentName, entryName);
if (!entry) {
if (showError == DoShowError) {
KMessageBox::error(
q,
i18n("Backend error: gpgconf does not seem to know the entry for %1/%2", QLatin1StringView(componentName), QLatin1String(entryName)));
}
return nullptr;
}
if (entry->argType() != argType || entry->isList() != bool(multiplicity)) {
if (showError == DoShowError) {
KMessageBox::error(q,
i18n("Backend error: gpgconf has wrong type for %1/%2: %3 %4",
QLatin1StringView(componentName),
QLatin1StringView(entryName),
entry->argType(),
entry->isList()));
}
return nullptr;
}
return entry;
}
DirectoryServicesConfigurationPage::DirectoryServicesConfigurationPage(QWidget *parent)
: KleoConfigModule(parent)
, d{new Private{this}}
{
}
DirectoryServicesConfigurationPage::~DirectoryServicesConfigurationPage() = default;
void DirectoryServicesConfigurationPage::load()
{
d->load();
}
void DirectoryServicesConfigurationPage::save()
{
d->save();
}
void DirectoryServicesConfigurationPage::defaults()
{
d->defaults();
}
#include "moc_dirservconfigpage.cpp"

File Metadata

Mime Type
text/x-diff
Expires
Sun, Feb 23, 7:34 PM (19 h, 32 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
71/6b/80f2d1c717a6c936fc3e0d11013c

Event Timeline