• Skip to content
  • Skip to link menu
KDE 4.4 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • Contact Us
 

mailtransport

servertest.cpp

00001 /*
00002     Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
00003     Copyright (C) 2007 KovoKs <info@kovoks.nl>
00004     Copyright (c) 2008 Thomas McGuire <thomas.mcguire@gmx.net>
00005 
00006     This library is free software; you can redistribute it and/or modify it
00007     under the terms of the GNU Library General Public License as published by
00008     the Free Software Foundation; either version 2 of the License, or (at your
00009     option) any later version.
00010 
00011     This library is distributed in the hope that it will be useful, but WITHOUT
00012     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00013     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
00014     License for more details.
00015 
00016     You should have received a copy of the GNU Library General Public License
00017     along with this library; see the file COPYING.LIB.  If not, write to the
00018     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00019     02110-1301, USA.
00020 */
00021 
00022 // Own
00023 #include "servertest.h"
00024 #include "socket.h"
00025 
00026 #include <mailtransport/transportbase.h>
00027 #include <mailtransport/mailtransport_defs.h>
00028 
00029 // Qt
00030 #include <QHostInfo>
00031 #include <QProgressBar>
00032 #include <QTimer>
00033 
00034 // KDE
00035 #include <KLocalizedString>
00036 #include <KDebug>
00037 
00038 using namespace MailTransport;
00039 
00040 namespace MailTransport
00041 {
00042 
00043 class ServerTestPrivate
00044 {
00045   public:
00046     ServerTestPrivate( ServerTest *test );
00047 
00048     ServerTest *const              q;
00049     QString                        server;
00050     QString                        fakeHostname;
00051     QString                        testProtocol;
00052 
00053     MailTransport::Socket         *normalSocket;
00054     MailTransport::Socket         *secureSocket;
00055 
00056     QSet< int >                    connectionResults;
00057     QHash< int, QList<int> >       authenticationResults;
00058     QSet< ServerTest::Capability > capabilityResults;
00059     QHash< int, uint >             customPorts;
00060     QTimer                        *normalSocketTimer;
00061     QTimer                        *secureSocketTimer;
00062     QTimer                        *progressTimer;
00063 
00064     QProgressBar                  *testProgress;
00065 
00066     bool                           secureSocketFinished;
00067     bool                           normalSocketFinished;
00068     bool                           tlsFinished;
00069     bool                           popSupportsTLS;
00070     int                            normalStage;
00071     int                            secureStage;
00072     int                            encryptionMode;
00073 
00074     void finalResult();
00075     void handleSMTPIMAPResponse( int type, const QString &text );
00076     void sendInitialCapabilityQuery( MailTransport::Socket *socket );
00077     bool handlePopConversation( MailTransport::Socket *socket, int type, int stage,
00078                                 const QString &response, bool *shouldStartTLS );
00079     QList< int > parseAuthenticationList( const QStringList &authentications );
00080 
00081     // slots
00082     void slotNormalPossible();
00083     void slotNormalNotPossible();
00084     void slotSslPossible();
00085     void slotSslNotPossible();
00086     void slotTlsDone();
00087     void slotReadNormal( const QString &text );
00088     void slotReadSecure( const QString &text );
00089     void slotUpdateProgress();
00090 };
00091 
00092 }
00093 
00094 ServerTestPrivate::ServerTestPrivate( ServerTest *test )
00095   : q( test ), testProgress( 0 ), secureSocketFinished( false ),
00096     normalSocketFinished( false ), tlsFinished( false )
00097 {
00098 }
00099 
00100 void ServerTestPrivate::finalResult()
00101 {
00102   if ( !secureSocketFinished || !normalSocketFinished || !tlsFinished ) {
00103     return;
00104   }
00105 
00106   kDebug() << "Modes:" << connectionResults;
00107   kDebug() << "Capabilities:" << capabilityResults;
00108   kDebug() << "Normal:" <<  q->normalProtocols();
00109   kDebug() << "SSL:" <<  q->secureProtocols();
00110   kDebug() << "TLS:" <<  q->tlsProtocols();
00111 
00112   if ( testProgress ) {
00113     testProgress->hide();
00114   }
00115   progressTimer->stop();
00116   secureSocketFinished =  false;
00117   normalSocketFinished =  false;
00118   tlsFinished = false ;
00119 
00120   emit q->finished( connectionResults.toList() );
00121 }
00122 
00123 QList< int > ServerTestPrivate::parseAuthenticationList( const QStringList &authentications )
00124 {
00125   QList< int > result;
00126   for ( QStringList::ConstIterator it = authentications.begin();
00127         it != authentications.end(); ++it )  {
00128     QString current = (*it).toUpper();
00129     if ( current == QLatin1String( "LOGIN" ) ) {
00130       result << Transport::EnumAuthenticationType::LOGIN;
00131     } else if ( current == QLatin1String( "PLAIN" ) ) {
00132       result << Transport::EnumAuthenticationType::PLAIN;
00133     } else if ( current == QLatin1String( "CRAM-MD5" ) ) {
00134       result << Transport::EnumAuthenticationType::CRAM_MD5;
00135     } else if ( current == QLatin1String( "DIGEST-MD5" ) ) {
00136       result << Transport::EnumAuthenticationType::DIGEST_MD5;
00137     } else if ( current == QLatin1String( "NTLM" ) ) {
00138       result << Transport::EnumAuthenticationType::NTLM;
00139     } else if ( current == QLatin1String( "GSSAPI" ) ) {
00140       result << Transport::EnumAuthenticationType::GSSAPI;
00141     } else if ( current == QLatin1String( "ANONYMOUS" ) ) {
00142       result << Transport::EnumAuthenticationType::ANONYMOUS;
00143     }
00144     // APOP is handled by handlePopConversation()
00145   }
00146   kDebug() << authentications << result;
00147 
00148   // LOGIN doesn't offer anything over PLAIN, requires more server
00149   // roundtrips and is not an official SASL mechanism, but a MS-ism,
00150   // so only enable it if PLAIN isn't available:
00151   if ( result.contains( Transport::EnumAuthenticationType::PLAIN ) ) {
00152     result.removeAll( Transport::EnumAuthenticationType::LOGIN );
00153   }
00154 
00155   return result;
00156 }
00157 
00158 void ServerTestPrivate::handleSMTPIMAPResponse( int type, const QString &text )
00159 {
00160   if ( !text.contains( QLatin1String( "AUTH" ), Qt::CaseInsensitive ) ) {
00161     kDebug() << "No authentication possible";
00162     return;
00163   }
00164 
00165   QStringList protocols;
00166   protocols << QLatin1String( "LOGIN" ) << QLatin1String( "PLAIN" )
00167             << QLatin1String( "CRAM-MD5" ) << QLatin1String( "DIGEST-MD5" )
00168             << QLatin1String( "NTLM" ) << QLatin1String( "GSSAPI" )
00169             << QLatin1String( "ANONYMOUS" );
00170 
00171   QStringList results;
00172   for ( int i = 0; i < protocols.count(); ++i ) {
00173     if ( text.contains( protocols.at( i ), Qt::CaseInsensitive ) ) {
00174       results.append( protocols.at( i ) );
00175     }
00176   }
00177 
00178   authenticationResults[type] = parseAuthenticationList( results );
00179   kDebug() << "For type" << type << ", we have:" << authenticationResults[type];
00180 }
00181 
00182 void ServerTestPrivate::slotNormalPossible()
00183 {
00184   normalSocketTimer->stop();
00185   connectionResults << Transport::EnumEncryption::None;
00186 }
00187 
00188 void ServerTestPrivate::sendInitialCapabilityQuery( MailTransport::Socket *socket )
00189 {
00190   kDebug();
00191   if ( testProtocol == IMAP_PROTOCOL ) {
00192     socket->write( QLatin1String( "1 CAPABILITY" ) );
00193   }
00194 
00195   else if ( testProtocol == SMTP_PROTOCOL ) {
00196 
00197       // Detect the hostname which we send with the EHLO command.
00198       // If there is a fake one set, use that, otherwise use the
00199       // local host name (and make sure it contains a domain, so the
00200       // server thinks it is valid).
00201     QString hostname;
00202     if ( !fakeHostname.isNull() ) {
00203       hostname = fakeHostname;
00204     } else {
00205       hostname = QHostInfo::localHostName();
00206       if( hostname.isEmpty() ) {
00207         hostname = QLatin1String( "localhost.invalid" );
00208       } else if ( !hostname.contains( QChar::fromAscii( '.' ) ) ) {
00209         hostname += QLatin1String( ".localnet" );
00210       }
00211     }
00212     kDebug() << "Hostname for EHLO is" << hostname;
00213 
00214     socket->write( QLatin1String( "EHLO " ) + hostname );
00215   }
00216 }
00217 
00218 void ServerTestPrivate::slotTlsDone()
00219 {
00220   kDebug();
00221 
00222   // The server will not send a response after starting TLS. Therefore, we have to manually
00223   // call slotReadNormal(), because this is not triggered by a data received signal this time.
00224   slotReadNormal( QString() );
00225 }
00226 
00227 bool ServerTestPrivate::handlePopConversation( MailTransport::Socket *socket, int type, int stage,
00228                                                const QString &response, bool *shouldStartTLS )
00229 {
00230   Q_ASSERT( shouldStartTLS != 0 );
00231 
00232   // Initial Greeting
00233   if ( stage == 0 ) {
00234 
00235     //Regexp taken from POP3 ioslave
00236     QString responseWithoutCRLF = response;
00237     responseWithoutCRLF.chop( 2 );
00238     QRegExp re( QLatin1String( "<[A-Za-z0-9\\.\\-_]+@[A-Za-z0-9\\.\\-_]+>$" ),
00239                 Qt::CaseInsensitive );
00240     if ( responseWithoutCRLF.indexOf( re ) != -1 ) {
00241       authenticationResults[type] << Transport::EnumAuthenticationType::APOP;
00242     }
00243 
00244     //Each server is supposed to support clear text login
00245     authenticationResults[type] << Transport::EnumAuthenticationType::CLEAR;
00246 
00247     // If we are in TLS stage, the server does not send the initial greeting.
00248     // Assume that the APOP availability is the same as with an unsecured connection.
00249     if ( type == Transport::EnumEncryption::TLS &&
00250          authenticationResults[Transport::EnumEncryption::None].
00251          contains( Transport::EnumAuthenticationType::APOP ) ) {
00252       authenticationResults[Transport::EnumEncryption::TLS]
00253         << Transport::EnumAuthenticationType::APOP;
00254     }
00255 
00256     socket->write( QLatin1String( "CAPA" ) );
00257     return true;
00258   }
00259 
00260   // CAPA result
00261   else if( stage == 1 ) {
00262 //     Example:
00263 //     CAPA
00264 //     +OK
00265 //     TOP
00266 //     USER
00267 //     SASL LOGIN CRAM-MD5
00268 //     UIDL
00269 //     RESP-CODES
00270 //     .
00271     if ( response.contains( QLatin1String( "TOP" ) ) ) {
00272       capabilityResults += ServerTest::Top;
00273     }
00274     if ( response.contains( QLatin1String( "PIPELINING" ) ) ) {
00275       capabilityResults += ServerTest::Pipelining;
00276     }
00277     if ( response.contains( QLatin1String( "UIDL" ) ) ) {
00278       capabilityResults += ServerTest::UIDL;
00279     }
00280     if ( response.contains( QLatin1String( "STLS" ) ) ) {
00281       connectionResults << Transport::EnumEncryption::TLS;
00282       popSupportsTLS = true;
00283     }
00284     socket->write( QLatin1String( "AUTH" ) );
00285     return true;
00286   }
00287 
00288   // AUTH response
00289   else if( stage == 2 ) {
00290 //     Example:
00291 //     C: AUTH
00292 //     S: +OK List of supported authentication methods follows
00293 //     S: LOGIN
00294 //     S: CRAM-MD5
00295 //     S:.
00296     QString formattedReply = response;
00297 
00298     // Get rid of trailling ".CRLF"
00299     formattedReply.chop( 3 );
00300 
00301     // Get rid of the first +OK line
00302     formattedReply = formattedReply.right( formattedReply.size() -
00303                                            formattedReply.indexOf( QLatin1Char( '\n' ) ) - 1 );
00304     formattedReply =
00305       formattedReply.replace( QLatin1Char( ' ' ), QLatin1Char( '-' ) ).
00306       replace( QLatin1String( "\r\n" ), QLatin1String( " " ) );
00307 
00308     authenticationResults[type] +=
00309       parseAuthenticationList( formattedReply.split( QLatin1Char( ' ' ) ) );
00310   }
00311 
00312   *shouldStartTLS = popSupportsTLS;
00313   return false;
00314 }
00315 
00316 // slotReadNormal() handles normal (no) encryption and TLS encryption.
00317 // At first, the communication is not encrypted, but if the server supports
00318 // the STARTTLS/STLS keyword, the same authentication query is done again
00319 // with TLS.
00320 void ServerTestPrivate::slotReadNormal( const QString &text )
00321 {
00322   Q_ASSERT( encryptionMode != Transport::EnumEncryption::SSL );
00323   static const int tlsHandshakeStage = 42;
00324 
00325   kDebug() << "Stage" << normalStage + 1 << ", Mode" << encryptionMode;
00326 
00327   // If we are in stage 42, we just do the handshake for TLS encryption and
00328   // then reset the stage to -1, so that all authentication modes and
00329   // capabilities are queried again for TLS encryption (some servers have
00330   // different authentication  methods in normal and in TLS mode).
00331   if ( normalStage == tlsHandshakeStage ) {
00332     Q_ASSERT( encryptionMode == Transport::EnumEncryption::TLS );
00333     normalStage = -1;
00334     normalSocket->startTLS();
00335     return;
00336   }
00337 
00338   bool shouldStartTLS = false;
00339   normalStage++;
00340 
00341   // Handle the whole POP converstation separatly, it is very different from
00342   // IMAP and SMTP
00343   if ( testProtocol == POP_PROTOCOL ) {
00344     if ( handlePopConversation( normalSocket, encryptionMode, normalStage, text,
00345                                 &shouldStartTLS ) ) {
00346       return;
00347     }
00348   } else {
00349     // Handle the SMTP/IMAP conversation here. We just send the EHLO command in
00350     // sendInitialCapabilityQuery.
00351     if ( normalStage == 0 ) {
00352       sendInitialCapabilityQuery( normalSocket );
00353       return;
00354     }
00355 
00356     if ( text.contains( QLatin1String( "STARTTLS" ), Qt::CaseInsensitive ) ) {
00357       connectionResults << Transport::EnumEncryption::TLS;
00358       shouldStartTLS = true;
00359     }
00360     handleSMTPIMAPResponse( encryptionMode, text );
00361   }
00362 
00363   // If we reach here, the normal authentication/capabilities query is completed.
00364   // Now do the same for TLS.
00365   normalSocketFinished = true;
00366 
00367   // If the server announced that STARTTLS/STLS is available, we'll add TLS to the
00368   // connection result, do the command and set the stage to 42 to start the handshake.
00369   if ( shouldStartTLS && encryptionMode == Transport::EnumEncryption::None ) {
00370     kDebug() << "Trying TLS...";
00371     connectionResults << Transport::EnumEncryption::TLS;
00372     if ( testProtocol == POP_PROTOCOL ) {
00373       normalSocket->write( QLatin1String( "STLS" ) );
00374     } else if ( testProtocol == IMAP_PROTOCOL ) {
00375       normalSocket->write( QLatin1String( "2 STARTTLS" ) );
00376     } else {
00377       normalSocket->write( QLatin1String( "STARTTLS" ) );
00378     }
00379     encryptionMode = Transport::EnumEncryption::TLS;
00380     normalStage = tlsHandshakeStage;
00381     return;
00382   }
00383 
00384   // If we reach here, either the TLS authentication/capabilities query is finished
00385   // or the server does not support the STARTTLS/STLS command.
00386   tlsFinished = true;
00387   finalResult();
00388 }
00389 
00390 void ServerTestPrivate::slotReadSecure( const QString &text )
00391 {
00392   secureStage++;
00393   if ( testProtocol == POP_PROTOCOL ) {
00394     bool dummy;
00395     if ( handlePopConversation( secureSocket, Transport::EnumEncryption::SSL,
00396                                 secureStage, text, &dummy ) ) {
00397       return;
00398     }
00399   } else {
00400     if ( secureStage == 0 ) {
00401       sendInitialCapabilityQuery( secureSocket );
00402       return;
00403     }
00404     handleSMTPIMAPResponse( Transport::EnumEncryption::SSL, text );
00405   }
00406   secureSocketFinished = true;
00407   finalResult();
00408 }
00409 
00410 void ServerTestPrivate::slotNormalNotPossible()
00411 {
00412   normalSocketFinished = true;
00413   tlsFinished = true;
00414   finalResult();
00415 }
00416 
00417 void ServerTestPrivate::slotSslPossible()
00418 {
00419   secureSocketTimer->stop();
00420   connectionResults << Transport::EnumEncryption::SSL;
00421 }
00422 
00423 void ServerTestPrivate::slotSslNotPossible()
00424 {
00425   secureSocketFinished = true;
00426   finalResult();
00427 }
00428 
00429 void ServerTestPrivate::slotUpdateProgress()
00430 {
00431   if ( testProgress ) {
00432     testProgress->setValue( testProgress->value() + 1 );
00433   }
00434 }
00435 
00436 //---------------------- end private class -----------------------//
00437 
00438 ServerTest::ServerTest( QWidget *parent )
00439   : QWidget( parent ), d( new ServerTestPrivate( this ) )
00440 {
00441   d->normalSocketTimer = new QTimer( this );
00442   d->normalSocketTimer->setSingleShot( true );
00443   connect( d->normalSocketTimer, SIGNAL( timeout() ), SLOT( slotNormalNotPossible() ) );
00444 
00445   d->secureSocketTimer = new QTimer( this );
00446   d->secureSocketTimer->setSingleShot( true );
00447   connect( d->secureSocketTimer, SIGNAL( timeout() ), SLOT( slotSslNotPossible() ) );
00448 
00449   d->progressTimer = new QTimer( this );
00450   connect( d->progressTimer, SIGNAL( timeout() ), SLOT( slotUpdateProgress() ) );
00451 }
00452 
00453 ServerTest::~ServerTest()
00454 {
00455   delete d;
00456 }
00457 
00458 void ServerTest::start()
00459 {
00460   kDebug() << d;
00461 
00462   d->connectionResults.clear();
00463   d->authenticationResults.clear();
00464   d->capabilityResults.clear();
00465   d->popSupportsTLS = false;
00466   d->normalStage = -1;
00467   d->secureStage = -1;
00468   d->encryptionMode = Transport::EnumEncryption::None;
00469 
00470   if ( d->testProgress ) {
00471     d->testProgress->setMaximum( 20 );
00472     d->testProgress->setValue( 0 );
00473     d->testProgress->setTextVisible( true );
00474     d->testProgress->show();
00475     d->progressTimer->start( 1000 );
00476   }
00477 
00478   d->normalSocket = new MailTransport::Socket( this );
00479   d->secureSocket = new MailTransport::Socket( this );
00480   d->normalSocket->setObjectName( QLatin1String( "normal" ) );
00481   d->normalSocket->setServer( d->server );
00482   d->normalSocket->setProtocol( d->testProtocol );
00483   if ( d->testProtocol == IMAP_PROTOCOL ) {
00484     d->normalSocket->setPort( IMAP_PORT );
00485     d->secureSocket->setPort( IMAPS_PORT );
00486   } else if ( d->testProtocol == SMTP_PROTOCOL ) {
00487     d->normalSocket->setPort( SMTP_PORT );
00488     d->secureSocket->setPort( SMTPS_PORT );
00489   } else if ( d->testProtocol == POP_PROTOCOL ) {
00490     d->normalSocket->setPort( POP_PORT );
00491     d->secureSocket->setPort( POPS_PORT );
00492   }
00493 
00494   if ( d->customPorts.contains( Transport::EnumEncryption::None ) ) {
00495     d->normalSocket->setPort( d->customPorts.value( Transport::EnumEncryption::None ) );
00496   }
00497   if ( d->customPorts.contains( Transport::EnumEncryption::SSL ) ) {
00498     d->secureSocket->setPort( d->customPorts.value( Transport::EnumEncryption::SSL ) );
00499   }
00500 
00501   connect( d->normalSocket, SIGNAL(connected()), SLOT(slotNormalPossible()) );
00502   connect( d->normalSocket, SIGNAL(failed()), SLOT(slotNormalNotPossible()) );
00503   connect( d->normalSocket, SIGNAL(data(const QString&)),
00504            SLOT(slotReadNormal(const QString&)) );
00505   connect( d->normalSocket, SIGNAL(tlsDone()), SLOT(slotTlsDone()));
00506   d->normalSocket->reconnect();
00507   d->normalSocketTimer->start( 10000 );
00508 
00509   d->secureSocket->setObjectName( QLatin1String( "secure" ) );
00510   d->secureSocket->setServer( d->server );
00511   d->secureSocket->setProtocol( d->testProtocol + QLatin1Char( 's' ) );
00512   d->secureSocket->setSecure( true );
00513   connect( d->secureSocket, SIGNAL(connected()), SLOT(slotSslPossible()) );
00514   connect( d->secureSocket, SIGNAL(failed()), SLOT(slotSslNotPossible()) );
00515   connect( d->secureSocket, SIGNAL(data(const QString&) ),
00516            SLOT(slotReadSecure(const QString&)) );
00517   d->secureSocket->reconnect();
00518   d->secureSocketTimer->start( 10000 );
00519 }
00520 
00521 void ServerTest::setFakeHostname( const QString &fakeHostname )
00522 {
00523   d->fakeHostname = fakeHostname;
00524 }
00525 
00526 QString ServerTest::fakeHostname()
00527 {
00528   return d->fakeHostname;
00529 }
00530 
00531 void ServerTest::setServer( const QString &server )
00532 {
00533   d->server = server;
00534 }
00535 
00536 void ServerTest::setPort( Transport::EnumEncryption::type encryptionMode, uint port )
00537 {
00538   Q_ASSERT( encryptionMode == Transport::EnumEncryption::None ||
00539             encryptionMode == Transport::EnumEncryption::SSL );
00540   d->customPorts.insert( encryptionMode, port );
00541 }
00542 
00543 void ServerTest::setProgressBar( QProgressBar *pb )
00544 {
00545   d->testProgress = pb;
00546 }
00547 
00548 void ServerTest::setProtocol( const QString &protocol )
00549 {
00550   d->testProtocol = protocol;
00551 }
00552 
00553 QString ServerTest::protocol()
00554 {
00555   return d->testProtocol;
00556 }
00557 
00558 QString ServerTest::server()
00559 {
00560   return d->server;
00561 }
00562 
00563 int ServerTest::port( Transport::EnumEncryption::type encryptionMode )
00564 {
00565   Q_ASSERT( encryptionMode == Transport::EnumEncryption::None ||
00566             encryptionMode == Transport::EnumEncryption::SSL );
00567   if ( d->customPorts.contains( encryptionMode ) ) {
00568     return d->customPorts.value( static_cast<int>( encryptionMode ) );
00569   } else {
00570     return -1;
00571   }
00572 }
00573 
00574 QProgressBar *ServerTest::progressBar()
00575 {
00576   return d->testProgress;
00577 }
00578 
00579 QList< int > ServerTest::normalProtocols()
00580 {
00581   return d->authenticationResults[TransportBase::EnumEncryption::None];
00582 }
00583 
00584 QList< int > ServerTest::tlsProtocols()
00585 {
00586   return d->authenticationResults[TransportBase::EnumEncryption::TLS];
00587 }
00588 
00589 QList< int > ServerTest::secureProtocols()
00590 {
00591   return d->authenticationResults[Transport::EnumEncryption::SSL];
00592 }
00593 
00594 QList< ServerTest::Capability > ServerTest::capabilities() const
00595 {
00596   return d->capabilityResults.toList();
00597 }
00598 
00599 #include "servertest.moc"

mailtransport

Skip menu "mailtransport"
  • Main Page
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kblog
  • kcal
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.6.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal