• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdepim API Reference
  • KDE Home
  • Contact Us
 

kleopatra

  • sources
  • kde-4.12
  • kdepim
  • kleopatra
  • utils
archivedefinition.cpp
Go to the documentation of this file.
1 /* -*- mode: c++; c-basic-offset:4 -*-
2  utils/archivedefinition.cpp
3 
4  This file is part of Kleopatra, the KDE keymanager
5  Copyright (c) 2009, 2010 Klarälvdalens Datakonsult AB
6 
7  Kleopatra is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11 
12  Kleopatra is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 
21  In addition, as a special exception, the copyright holders give
22  permission to link the code of this program with any edition of
23  the Qt library by Trolltech AS, Norway (or with modified versions
24  of Qt that use the same license as Qt), and distribute linked
25  combinations including the two. You must obey the GNU General
26  Public License in all respects for all of the code used other than
27  Qt. If you modify this file, you may extend this exception to
28  your version of the file, but you are not obligated to do so. If
29  you do not wish to do so, delete this exception statement from
30  your version.
31 */
32 
33 #include <config-kleopatra.h>
34 
35 #include "archivedefinition.h"
36 
37 #include <utils/input.h>
38 #include <utils/output.h>
39 #include <utils/path-helper.h>
40 #include <utils/kleo_assert.h>
41 
42 #include <kleo/exception.h>
43 #include <kleo/cryptobackendfactory.h>
44 
45 #include <KConfigGroup>
46 #include <KDebug>
47 #include <KLocalizedString>
48 #include <KGlobal>
49 #include <KConfig>
50 #include <KShell>
51 #include <KStandardDirs>
52 
53 #include <QProcess>
54 #include <QString>
55 #include <QStringList>
56 #include <QDir>
57 #include <QMutex>
58 #include <QCoreApplication>
59 
60 #include <boost/shared_ptr.hpp>
61 
62 using namespace GpgME;
63 using namespace Kleo;
64 using namespace boost;
65 
66 static QMutex installPathMutex;
67 Q_GLOBAL_STATIC( QString, _installPath )
68 QString ArchiveDefinition::installPath() {
69  const QMutexLocker locker( &installPathMutex );
70  QString * const ip = _installPath();
71  if ( ip->isEmpty() )
72  if ( QCoreApplication::instance() )
73  *ip = QCoreApplication::applicationDirPath();
74  else
75  kWarning() << "called before QCoreApplication was constructed";
76  return *ip;
77 }
78 void ArchiveDefinition::setInstallPath( const QString & ip ) {
79  const QMutexLocker locker( &installPathMutex );
80  *_installPath() =ip;
81 }
82 
83 
84 // Archive Definition #N groups
85 static const QLatin1String ID_ENTRY( "id" );
86 static const QLatin1String NAME_ENTRY( "Name" );
87 static const QLatin1String PACK_COMMAND_ENTRY( "pack-command" );
88 static const QLatin1String PACK_COMMAND_OPENPGP_ENTRY( "pack-command-openpgp" );
89 static const QLatin1String PACK_COMMAND_CMS_ENTRY( "pack-command-cms" );
90 static const QLatin1String UNPACK_COMMAND_ENTRY( "unpack-command" );
91 static const QLatin1String UNPACK_COMMAND_OPENPGP_ENTRY( "unpack-command-openpgp" );
92 static const QLatin1String UNPACK_COMMAND_CMS_ENTRY( "unpack-command-cms" );
93 static const QLatin1String EXTENSIONS_ENTRY( "extensions" );
94 static const QLatin1String EXTENSIONS_OPENPGP_ENTRY( "extensions-openpgp" );
95 static const QLatin1String EXTENSIONS_CMS_ENTRY( "extensions-cms" );
96 static const QLatin1String FILE_PLACEHOLDER( "%f" );
97 static const QLatin1String INSTALLPATH_PLACEHOLDER( "%I" );
98 static const QLatin1String NULL_SEPARATED_STDIN_INDICATOR( "0|" );
99 static const QLatin1Char NEWLINE_SEPARATED_STDIN_INDICATOR( '|' );
100 
101 namespace {
102 
103  class ArchiveDefinitionError : public Kleo::Exception {
104  const QString m_id;
105  public:
106  ArchiveDefinitionError( const QString & id, const QString & message )
107  : Kleo::Exception( GPG_ERR_INV_PARAMETER, i18n("Error in archive definition %1: %2", id, message ), MessageOnly ),
108  m_id( id )
109  {
110 
111  }
112  ~ArchiveDefinitionError() throw() {}
113 
114  const QString & archiveDefinitionId() const { return m_id; }
115  };
116 
117 }
118 
119 static QString try_extensions( const QString & path ) {
120  static const char exts[][4] = {
121  "", "exe", "bat", "bin", "cmd",
122  };
123  static const size_t numExts = sizeof exts / sizeof *exts ;
124  for ( unsigned int i = 0 ; i < numExts ; ++i ) {
125  const QFileInfo fi( path + QLatin1Char('.') + QLatin1String( exts[i] ) );
126  if ( fi.exists() )
127  return fi.filePath();
128  }
129  return QString();
130 }
131 
132 static void parse_command( QString cmdline, const QString & id, const QString & whichCommand,
133  QString * command, QStringList * prefix, QStringList * suffix, ArchiveDefinition::ArgumentPassingMethod * method, bool parseFilePlaceholder )
134 {
135  assert( prefix );
136  assert( suffix );
137  assert( method );
138 
139  KShell::Errors errors;
140  QStringList l;
141 
142  if ( cmdline.startsWith( NULL_SEPARATED_STDIN_INDICATOR ) ) {
143  *method = ArchiveDefinition::NullSeparatedInputFile;
144  cmdline.remove( 0, 2 );
145  } else if ( cmdline.startsWith( NEWLINE_SEPARATED_STDIN_INDICATOR ) ) {
146  *method = ArchiveDefinition::NewlineSeparatedInputFile;
147  cmdline.remove( 0, 1 );
148  } else {
149  *method = ArchiveDefinition::CommandLine;
150  }
151  if ( *method != ArchiveDefinition::CommandLine && cmdline.contains( FILE_PLACEHOLDER ) )
152  throw ArchiveDefinitionError( id, i18n("Cannot use both %f and | in '%1'", whichCommand) );
153  cmdline.replace( FILE_PLACEHOLDER, QLatin1String("__files_go_here__") )
154  .replace( INSTALLPATH_PLACEHOLDER, QLatin1String("__path_goes_here__") );
155  l = KShell::splitArgs( cmdline, KShell::AbortOnMeta|KShell::TildeExpand, &errors );
156  l = l.replaceInStrings( QLatin1String("__files_go_here__"), FILE_PLACEHOLDER );
157  if ( l.indexOf( QRegExp( QLatin1String(".*__path_goes_here__.*") ) ) >= 0 )
158  l = l.replaceInStrings( QLatin1String("__path_goes_here__"), ArchiveDefinition::installPath() );
159  if ( errors == KShell::BadQuoting )
160  throw ArchiveDefinitionError( id, i18n("Quoting error in '%1' entry", whichCommand) );
161  if ( errors == KShell::FoundMeta )
162  throw ArchiveDefinitionError( id, i18n("'%1' too complex (would need shell)", whichCommand) );
163  kDebug() << "ArchiveDefinition[" << id << ']' << l;
164  if ( l.empty() )
165  throw ArchiveDefinitionError( id, i18n("'%1' entry is empty/missing", whichCommand) );
166  const QFileInfo fi1( l.front() );
167  if ( fi1.isAbsolute() )
168  *command = try_extensions( l.front() );
169  else
170  *command = KStandardDirs::findExe( fi1.fileName() );
171  if ( command->isEmpty() )
172  throw ArchiveDefinitionError( id, i18n("'%1' empty or not found", whichCommand) );
173  if ( parseFilePlaceholder ) {
174  const int idx1 = l.indexOf( FILE_PLACEHOLDER );
175  if ( idx1 < 0 ) {
176  // none -> append
177  *prefix = l.mid( 1 );
178  } else {
179  *prefix = l.mid( 1, idx1-1 );
180  *suffix = l.mid( idx1+1 );
181  }
182  } else {
183  *prefix = l.mid( 1 );
184  }
185  switch ( *method ) {
186  case ArchiveDefinition::CommandLine:
187  kDebug() << "ArchiveDefinition[" << id << ']' << *command << *prefix << FILE_PLACEHOLDER << *suffix;
188  break;
189  case ArchiveDefinition::NewlineSeparatedInputFile:
190  kDebug() << "ArchiveDefinition[" << id << ']' << "find | " << *command << *prefix;
191  break;
192  case ArchiveDefinition::NullSeparatedInputFile:
193  kDebug() << "ArchiveDefinition[" << id << ']' << "find -print0 | " << *command << *prefix;
194  break;
195  case ArchiveDefinition::NumArgumentPassingMethods:
196  assert( !"Should not happen" );
197  break;
198  }
199 }
200 
201 namespace {
202 
203  class KConfigBasedArchiveDefinition : public ArchiveDefinition {
204  public:
205  explicit KConfigBasedArchiveDefinition( const KConfigGroup & group )
206  : ArchiveDefinition( group.readEntryUntranslated( ID_ENTRY ),
207  group.readEntry( NAME_ENTRY ) )
208  {
209  if ( id().isEmpty() )
210  throw ArchiveDefinitionError( group.name(), i18n("'%1' entry is empty/missing", ID_ENTRY ) );
211 
212  QStringList extensions;
213  QString extensionsKey;
214 
215  // extensions(-openpgp)
216  if ( group.hasKey( EXTENSIONS_OPENPGP_ENTRY ) )
217  extensionsKey = EXTENSIONS_OPENPGP_ENTRY;
218  else
219  extensionsKey = EXTENSIONS_ENTRY;
220  extensions = group.readEntry( extensionsKey, QStringList() );
221  if ( extensions.empty() )
222  throw ArchiveDefinitionError( id(), i18n("'%1' entry is empty/missing", extensionsKey ) );
223  setExtensions( OpenPGP, extensions );
224 
225  // extensions(-cms)
226  if ( group.hasKey( EXTENSIONS_CMS_ENTRY ) )
227  extensionsKey = EXTENSIONS_CMS_ENTRY;
228  else
229  extensionsKey = EXTENSIONS_ENTRY;
230  extensions = group.readEntry( extensionsKey, QStringList() );
231  if ( extensions.empty() )
232  throw ArchiveDefinitionError( id(), i18n("'%1' entry is empty/missing", extensionsKey ) );
233  setExtensions( CMS, extensions );
234 
235  ArgumentPassingMethod method;
236 
237  // pack-command(-openpgp)
238  if ( group.hasKey( PACK_COMMAND_OPENPGP_ENTRY ) )
239  parse_command( group.readEntry( PACK_COMMAND_OPENPGP_ENTRY ), id(), PACK_COMMAND_OPENPGP_ENTRY,
240  &m_packCommand[OpenPGP], &m_packPrefixArguments[OpenPGP], &m_packPostfixArguments[OpenPGP], &method, true );
241  else
242  parse_command( group.readEntry( PACK_COMMAND_ENTRY ), id(), PACK_COMMAND_ENTRY,
243  &m_packCommand[OpenPGP], &m_packPrefixArguments[OpenPGP], &m_packPostfixArguments[OpenPGP], &method, true );
244  setPackCommandArgumentPassingMethod( OpenPGP, method );
245 
246  // pack-command(-cms)
247  if ( group.hasKey( PACK_COMMAND_CMS_ENTRY ) )
248  parse_command( group.readEntry( PACK_COMMAND_CMS_ENTRY ), id(), PACK_COMMAND_CMS_ENTRY,
249  &m_packCommand[CMS], &m_packPrefixArguments[CMS], &m_packPostfixArguments[CMS], &method, true );
250  else
251  parse_command( group.readEntry( PACK_COMMAND_ENTRY ), id(), PACK_COMMAND_ENTRY,
252  &m_packCommand[CMS], &m_packPrefixArguments[CMS], &m_packPostfixArguments[CMS], &method, true );
253  setPackCommandArgumentPassingMethod( CMS, method );
254 
255  QStringList dummy;
256 
257  // unpack-command(-openpgp)
258  if ( group.hasKey( UNPACK_COMMAND_OPENPGP_ENTRY ) )
259  parse_command( group.readEntry( UNPACK_COMMAND_OPENPGP_ENTRY ), id(), UNPACK_COMMAND_OPENPGP_ENTRY,
260  &m_unpackCommand[OpenPGP], &m_unpackArguments[OpenPGP], &dummy, &method, false );
261  else
262  parse_command( group.readEntry( UNPACK_COMMAND_ENTRY ), id(), UNPACK_COMMAND_ENTRY,
263  &m_unpackCommand[OpenPGP], &m_unpackArguments[OpenPGP], &dummy, &method, false );
264  if ( method != CommandLine )
265  throw ArchiveDefinitionError( id(), i18n("cannot use argument passing on standard input for unpack-command") );
266 
267  // unpack-command(-cms)
268  if ( group.hasKey( UNPACK_COMMAND_CMS_ENTRY ) )
269  parse_command( group.readEntry( UNPACK_COMMAND_CMS_ENTRY ), id(), UNPACK_COMMAND_CMS_ENTRY,
270  &m_unpackCommand[CMS], &m_unpackArguments[CMS], &dummy, &method, false );
271  else
272  parse_command( group.readEntry( UNPACK_COMMAND_ENTRY ), id(), UNPACK_COMMAND_ENTRY,
273  &m_unpackCommand[CMS], &m_unpackArguments[CMS], &dummy, &method, false );
274  if ( method != CommandLine )
275  throw ArchiveDefinitionError( id(), i18n("cannot use argument passing on standard input for unpack-command") );
276  }
277 
278  private:
279  /* reimp */ QString doGetPackCommand( Protocol p ) const { return m_packCommand[p]; }
280  /* reimp */ QString doGetUnpackCommand( Protocol p ) const { return m_unpackCommand[p]; }
281  /* reimp */ QStringList doGetPackArguments( Protocol p, const QStringList & files ) const {
282  return m_packPrefixArguments[p] + files + m_packPostfixArguments[p];
283  }
284  /* reimp */ QStringList doGetUnpackArguments( Protocol p, const QString & file ) const {
285  QStringList copy = m_unpackArguments[p];
286  copy.replaceInStrings( FILE_PLACEHOLDER, file );
287  return copy;
288  }
289 
290  private:
291  QString m_packCommand[2], m_unpackCommand[2];
292  QStringList m_packPrefixArguments[2], m_packPostfixArguments[2];
293  QStringList m_unpackArguments[2];
294  };
295 
296 }
297 
298 ArchiveDefinition::ArchiveDefinition( const QString & id, const QString & label )
299  : m_id( id ),
300  m_label( label )
301 {
302  m_packCommandMethod[OpenPGP] = m_packCommandMethod[CMS] = CommandLine;
303 }
304 
305 ArchiveDefinition::~ArchiveDefinition() {}
306 
307 static QByteArray make_input( const QStringList & files, char sep ) {
308  QByteArray result;
309  Q_FOREACH( const QString & file, files ) {
310  result += QFile::encodeName( file );
311  result += sep;
312  }
313  return result;
314 }
315 
316 shared_ptr<Input> ArchiveDefinition::createInputFromPackCommand( GpgME::Protocol p, const QStringList & files ) const {
317  checkProtocol( p );
318  const QString base = heuristicBaseDirectory( files );
319  if ( base.isEmpty() )
320  throw Kleo::Exception( GPG_ERR_CONFLICT, i18n("Cannot find common base directory for these files:\n%1", files.join( QLatin1String("\n") ) ) );
321  kDebug() << "heuristicBaseDirectory(" << files << ") ->" << base;
322  const QStringList relative = makeRelativeTo( base, files );
323  kDebug() << "relative" << relative;
324  switch ( m_packCommandMethod[p] ) {
325  case CommandLine:
326  return Input::createFromProcessStdOut( doGetPackCommand( p ),
327  doGetPackArguments( p, relative ),
328  QDir( base ) );
329  case NewlineSeparatedInputFile:
330  return Input::createFromProcessStdOut( doGetPackCommand( p ),
331  doGetPackArguments( p, QStringList() ),
332  QDir( base ),
333  make_input( relative, '\n' ) );
334  case NullSeparatedInputFile:
335  return Input::createFromProcessStdOut( doGetPackCommand( p ),
336  doGetPackArguments( p, QStringList() ),
337  QDir( base ),
338  make_input( relative, '\0' ) );
339  case NumArgumentPassingMethods:
340  assert( !"Should not happen" );
341  }
342  return shared_ptr<Input>(); // make compiler happy
343 }
344 
345 shared_ptr<Output> ArchiveDefinition::createOutputFromUnpackCommand( GpgME::Protocol p, const QString & file, const QDir & wd ) const {
346  checkProtocol( p );
347  const QFileInfo fi( file );
348  return Output::createFromProcessStdIn( doGetUnpackCommand( p ),
349  doGetUnpackArguments( p, fi.absoluteFilePath() ),
350  wd );
351 }
352 
353 // static
354 std::vector< shared_ptr<ArchiveDefinition> > ArchiveDefinition::getArchiveDefinitions() {
355  QStringList errors;
356  return getArchiveDefinitions( errors );
357 }
358 
359 // static
360 std::vector< shared_ptr<ArchiveDefinition> > ArchiveDefinition::getArchiveDefinitions( QStringList & errors ) {
361  std::vector< shared_ptr<ArchiveDefinition> > result;
362  if ( KConfig * config = CryptoBackendFactory::instance()->configObject() ) {
363  const QStringList groups = config->groupList().filter( QRegExp(QLatin1String("^Archive Definition #")) );
364  result.reserve( groups.size() );
365  Q_FOREACH( const QString & group, groups )
366  try {
367  const shared_ptr<ArchiveDefinition> ad( new KConfigBasedArchiveDefinition( KConfigGroup( config, group ) ) );
368  result.push_back( ad );
369  } catch ( const std::exception & e ) {
370  kDebug() << e.what();
371  errors.push_back( QString::fromLocal8Bit( e.what() ) );
372  } catch ( ... ) {
373  errors.push_back( i18n("Caught unknown exception in group %1", group ) );
374  }
375  }
376  return result;
377 }
378 
379 void ArchiveDefinition::checkProtocol( Protocol p ) const {
380  kleo_assert( p == OpenPGP || p == CMS );
381 }
Kleo::heuristicBaseDirectory
QString heuristicBaseDirectory(const QStringList &files)
Definition: path-helper.cpp:69
installPathMutex
static QMutex installPathMutex
Definition: archivedefinition.cpp:66
Kleo::ArchiveDefinition::ArgumentPassingMethod
ArgumentPassingMethod
Definition: archivedefinition.h:63
Kleo::ArchiveDefinition::checkProtocol
void checkProtocol(GpgME::Protocol p) const
Definition: archivedefinition.cpp:379
output.h
input.h
try_extensions
static QString try_extensions(const QString &path)
Definition: archivedefinition.cpp:119
NEWLINE_SEPARATED_STDIN_INDICATOR
static const QLatin1Char NEWLINE_SEPARATED_STDIN_INDICATOR( '|')
path-helper.h
Kleo::ArchiveDefinition::NullSeparatedInputFile
Definition: archivedefinition.h:66
PACK_COMMAND_CMS_ENTRY
static const QLatin1String PACK_COMMAND_CMS_ENTRY("pack-command-cms")
PACK_COMMAND_ENTRY
static const QLatin1String PACK_COMMAND_ENTRY("pack-command")
PACK_COMMAND_OPENPGP_ENTRY
static const QLatin1String PACK_COMMAND_OPENPGP_ENTRY("pack-command-openpgp")
UNPACK_COMMAND_ENTRY
static const QLatin1String UNPACK_COMMAND_ENTRY("unpack-command")
Kleo::ArchiveDefinition::NumArgumentPassingMethods
Definition: archivedefinition.h:68
Kleo::ArchiveDefinition::~ArchiveDefinition
virtual ~ArchiveDefinition()
Definition: archivedefinition.cpp:305
kleo_assert.h
Kleo::makeRelativeTo
QStringList makeRelativeTo(const QDir &dir, const QStringList &files)
Definition: path-helper.cpp:88
boost::shared_ptr
Definition: encryptemailcontroller.h:51
Kleo::Class::OpenPGP
Definition: classify.h:49
parse_command
static void parse_command(QString cmdline, const QString &id, const QString &whichCommand, QString *command, QStringList *prefix, QStringList *suffix, ArchiveDefinition::ArgumentPassingMethod *method, bool parseFilePlaceholder)
Definition: archivedefinition.cpp:132
Kleo::ArchiveDefinition::CommandLine
Definition: archivedefinition.h:64
ID_ENTRY
static const QLatin1String ID_ENTRY("id")
Kleo::Class::CMS
Definition: classify.h:48
NAME_ENTRY
static const QLatin1String NAME_ENTRY("Name")
EXTENSIONS_CMS_ENTRY
static const QLatin1String EXTENSIONS_CMS_ENTRY("extensions-cms")
EXTENSIONS_OPENPGP_ENTRY
static const QLatin1String EXTENSIONS_OPENPGP_ENTRY("extensions-openpgp")
archivedefinition.h
Kleo::ArchiveDefinition::createOutputFromUnpackCommand
boost::shared_ptr< Output > createOutputFromUnpackCommand(GpgME::Protocol p, const QString &file, const QDir &wd) const
Definition: archivedefinition.cpp:345
EXTENSIONS_ENTRY
static const QLatin1String EXTENSIONS_ENTRY("extensions")
make_input
static QByteArray make_input(const QStringList &files, char sep)
Definition: archivedefinition.cpp:307
kleo_assert
#define kleo_assert(cond)
Definition: kleo_assert.h:84
FILE_PLACEHOLDER
static const QLatin1String FILE_PLACEHOLDER("%f")
Kleo::Output::createFromProcessStdIn
static boost::shared_ptr< Output > createFromProcessStdIn(const QString &command)
Definition: output.cpp:430
NULL_SEPARATED_STDIN_INDICATOR
static const QLatin1String NULL_SEPARATED_STDIN_INDICATOR("0|")
Kleo::Input::createFromProcessStdOut
static boost::shared_ptr< Input > createFromProcessStdOut(const QString &command)
Definition: input.cpp:241
UNPACK_COMMAND_OPENPGP_ENTRY
static const QLatin1String UNPACK_COMMAND_OPENPGP_ENTRY("unpack-command-openpgp")
UNPACK_COMMAND_CMS_ENTRY
static const QLatin1String UNPACK_COMMAND_CMS_ENTRY("unpack-command-cms")
INSTALLPATH_PLACEHOLDER
static const QLatin1String INSTALLPATH_PLACEHOLDER("%I")
Kleo::ArchiveDefinition::createInputFromPackCommand
boost::shared_ptr< Input > createInputFromPackCommand(GpgME::Protocol p, const QStringList &files) const
Definition: archivedefinition.cpp:316
Kleo::ArchiveDefinition::NewlineSeparatedInputFile
Definition: archivedefinition.h:65
Kleo::ArchiveDefinition
Definition: archivedefinition.h:57
Kleo::ArchiveDefinition::getArchiveDefinitions
static std::vector< boost::shared_ptr< ArchiveDefinition > > getArchiveDefinitions()
Definition: archivedefinition.cpp:354
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 22:56:40 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

kleopatra

Skip menu "kleopatra"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members

kdepim API Reference

Skip menu "kdepim API Reference"
  • akonadi_next
  • akregator
  • blogilo
  • calendarsupport
  • console
  •   kabcclient
  •   konsolekalendar
  • kaddressbook
  • kalarm
  •   lib
  • kdgantt2
  • kjots
  • kleopatra
  • kmail
  • knode
  • knotes
  • kontact
  • korgac
  • korganizer
  • ktimetracker
  • libkdepim
  • libkleo
  • libkpgp
  • mailcommon
  • messagelist
  • messageviewer

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal