• Skip to content
  • Skip to link menu
KDE 4.1 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 <klocale.h>
00036 #include <kdebug.h>
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 {
00375       normalSocket->write( QLatin1String( "STARTTLS" ) );
00376     }
00377     encryptionMode = Transport::EnumEncryption::TLS;
00378     normalStage = tlsHandshakeStage;
00379     return;
00380   }
00381 
00382   // If we reach here, either the TLS authentication/capabilities query is finished
00383   // or the server does not support the STARTTLS/STLS command.
00384   tlsFinished = true;
00385   finalResult();
00386 }
00387 
00388 void ServerTestPrivate::slotReadSecure( const QString &text )
00389 {
00390   secureStage++;
00391   if ( testProtocol == POP_PROTOCOL ) {
00392     bool dummy;
00393     if ( handlePopConversation( secureSocket, Transport::EnumEncryption::SSL,
00394                                 secureStage, text, &dummy ) ) {
00395       return;
00396     }
00397   } else {
00398     if ( secureStage == 0 ) {
00399       sendInitialCapabilityQuery( secureSocket );
00400       return;
00401     }
00402     handleSMTPIMAPResponse( Transport::EnumEncryption::SSL, text );
00403   }
00404   secureSocketFinished = true;
00405   finalResult();
00406 }
00407 
00408 void ServerTestPrivate::slotNormalNotPossible()
00409 {
00410   normalSocketFinished = true;
00411   tlsFinished = true;
00412   finalResult();
00413 }
00414 
00415 void ServerTestPrivate::slotSslPossible()
00416 {
00417   secureSocketTimer->stop();
00418   connectionResults << Transport::EnumEncryption::SSL;
00419 }
00420 
00421 void ServerTestPrivate::slotSslNotPossible()
00422 {
00423   secureSocketFinished = true;
00424   finalResult();
00425 }
00426 
00427 void ServerTestPrivate::slotUpdateProgress()
00428 {
00429   if ( testProgress ) {
00430     testProgress->setValue( testProgress->value() + 1 );
00431   }
00432 }
00433 
00434 //---------------------- end private class -----------------------//
00435 
00436 ServerTest::ServerTest( QWidget *parent )
00437   : QWidget( parent ), d( new ServerTestPrivate( this ) )
00438 {
00439   d->normalSocketTimer = new QTimer( this );
00440   d->normalSocketTimer->setSingleShot( true );
00441   connect( d->normalSocketTimer, SIGNAL( timeout() ), SLOT( slotNormalNotPossible() ) );
00442 
00443   d->secureSocketTimer = new QTimer( this );
00444   d->secureSocketTimer->setSingleShot( true );
00445   connect( d->secureSocketTimer, SIGNAL( timeout() ), SLOT( slotSslNotPossible() ) );
00446 
00447   d->progressTimer = new QTimer( this );
00448   connect( d->progressTimer, SIGNAL( timeout() ), SLOT( slotUpdateProgress() ) );
00449 }
00450 
00451 ServerTest::~ServerTest()
00452 {
00453   delete d;
00454 }
00455 
00456 void ServerTest::start()
00457 {
00458   kDebug() << d;
00459 
00460   d->connectionResults.clear();
00461   d->authenticationResults.clear();
00462   d->capabilityResults.clear();
00463   d->popSupportsTLS = false;
00464   d->normalStage = -1;
00465   d->secureStage = -1;
00466   d->encryptionMode = Transport::EnumEncryption::None;
00467 
00468   if ( d->testProgress ) {
00469     d->testProgress->setMaximum( 20 );
00470     d->testProgress->setValue( 0 );
00471     d->testProgress->setTextVisible( true );
00472     d->testProgress->show();
00473     d->progressTimer->start( 1000 );
00474   }
00475 
00476   d->normalSocket = new MailTransport::Socket( this );
00477   d->secureSocket = new MailTransport::Socket( this );
00478   d->normalSocket->setObjectName( QLatin1String( "normal" ) );
00479   d->normalSocket->setServer( d->server );
00480   d->normalSocket->setProtocol( d->testProtocol );
00481   if ( d->testProtocol == IMAP_PROTOCOL ) {
00482     d->normalSocket->setPort( IMAP_PORT );
00483     d->secureSocket->setPort( IMAPS_PORT );
00484   } else if ( d->testProtocol == SMTP_PROTOCOL ) {
00485     d->normalSocket->setPort( SMTP_PORT );
00486     d->secureSocket->setPort( SMTPS_PORT );
00487   } else if ( d->testProtocol == POP_PROTOCOL ) {
00488     d->normalSocket->setPort( POP_PORT );
00489     d->secureSocket->setPort( POPS_PORT );
00490   }
00491 
00492   if ( d->customPorts.contains( Transport::EnumEncryption::None ) ) {
00493     d->normalSocket->setPort( d->customPorts.value( Transport::EnumEncryption::None ) );
00494   }
00495   if ( d->customPorts.contains( Transport::EnumEncryption::SSL ) ) {
00496     d->secureSocket->setPort( d->customPorts.value( Transport::EnumEncryption::SSL ) );
00497   }
00498 
00499   connect( d->normalSocket, SIGNAL(connected()), SLOT(slotNormalPossible()) );
00500   connect( d->normalSocket, SIGNAL(failed()), SLOT(slotNormalNotPossible()) );
00501   connect( d->normalSocket, SIGNAL(data(const QString&)),
00502            SLOT(slotReadNormal(const QString&)) );
00503   connect( d->normalSocket, SIGNAL(tlsDone()), SLOT(slotTlsDone()));
00504   d->normalSocket->reconnect();
00505   d->normalSocketTimer->start( 10000 );
00506 
00507   d->secureSocket->setObjectName( QLatin1String( "secure" ) );
00508   d->secureSocket->setServer( d->server );
00509   d->secureSocket->setProtocol( d->testProtocol + QLatin1Char( 's' ) );
00510   d->secureSocket->setSecure( true );
00511   connect( d->secureSocket, SIGNAL(connected()), SLOT(slotSslPossible()) );
00512   connect( d->secureSocket, SIGNAL(failed()), SLOT(slotSslNotPossible()) );
00513   connect( d->secureSocket, SIGNAL(data(const QString&) ),
00514            SLOT(slotReadSecure(const QString&)) );
00515   d->secureSocket->reconnect();
00516   d->secureSocketTimer->start( 10000 );
00517 }
00518 
00519 void ServerTest::setFakeHostname( const QString &fakeHostname )
00520 {
00521   d->fakeHostname = fakeHostname;
00522 }
00523 
00524 QString ServerTest::fakeHostname()
00525 {
00526   return d->fakeHostname;
00527 }
00528 
00529 void ServerTest::setServer( const QString &server )
00530 {
00531   d->server = server;
00532 }
00533 
00534 void ServerTest::setPort( Transport::EnumEncryption::type encryptionMode, uint port )
00535 {
00536   Q_ASSERT( encryptionMode == Transport::EnumEncryption::None ||
00537             encryptionMode == Transport::EnumEncryption::SSL );
00538   d->customPorts.insert( encryptionMode, port );
00539 }
00540 
00541 void ServerTest::setProgressBar( QProgressBar *pb )
00542 {
00543   d->testProgress = pb;
00544 }
00545 
00546 void ServerTest::setProtocol( const QString &protocol )
00547 {
00548   d->testProtocol = protocol;
00549 }
00550 
00551 QString ServerTest::protocol()
00552 {
00553   return d->testProtocol;
00554 }
00555 
00556 QString ServerTest::server()
00557 {
00558   return d->server;
00559 }
00560 
00561 int ServerTest::port( Transport::EnumEncryption::type encryptionMode )
00562 {
00563   Q_ASSERT( encryptionMode == Transport::EnumEncryption::None ||
00564             encryptionMode == Transport::EnumEncryption::SSL );
00565   if ( d->customPorts.contains( encryptionMode ) ) {
00566     return d->customPorts.value( static_cast<int>( encryptionMode ) );
00567   } else {
00568     return -1;
00569   }
00570 }
00571 
00572 QProgressBar *ServerTest::progressBar()
00573 {
00574   return d->testProgress;
00575 }
00576 
00577 QList< int > ServerTest::normalProtocols()
00578 {
00579   return d->authenticationResults[TransportBase::EnumEncryption::None];
00580 }
00581 
00582 QList< int > ServerTest::tlsProtocols()
00583 {
00584   return d->authenticationResults[TransportBase::EnumEncryption::TLS];
00585 }
00586 
00587 QList< int > ServerTest::secureProtocols()
00588 {
00589   return d->authenticationResults[Transport::EnumEncryption::SSL];
00590 }
00591 
00592 QList< ServerTest::Capability > ServerTest::capabilities() const
00593 {
00594   return d->capabilityResults.toList();
00595 }
00596 
00597 #include "servertest.moc"

mailtransport

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

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  • kabc
  • kblog
  • kcal
  • kimap
  • kioslave
  •   imap4
  •   mbox
  • kldap
  • kmime
  • kpimidentities
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.5.6
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