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